Skip to main content

Trading Execution (API Key mode)

Use this page when you want Auto queries to place orders (market_order, limit_order) on /v2/auto/*.

Required: Verify exchange connection before creating trade queries

Before creating any query with market_order or limit_order actions, always call GET /v2/auto/exchanges to verify your exchange is connected. Without an active exchange connection, query creation may succeed but the trade action fails at execution time with AGENT_WALLET_REQUIRED. See API Checks for Bots/Agents below.

State Model

StateWhat It MeansWhat WorksWhat Fails
Auto disabledAPI key is not linked/enabled for AutoNothing on /v2/auto/*Requests fail with enablement/auth errors
Auto enabled (alerting-only)API key linked + HMAC configured, but no active exchange connectionQuery lifecycle + non-trade actions (webhook, notify, telegram_bot, llm)Trade actions fail at execution time
Trade-ready (exchange connected)Auto enabled + active exchange connection row for the target venueNon-trade actions and trade actions (market_order, limit_order)N/A (subject to runtime risk/balance/policy checks)

Trade actions can execute on either supported venue — hyperliquid or gmx — selected per action via the required params.exchange field. Each action requires an active connection for the venue it targets: a Hyperliquid order needs a Hyperliquid connection, a GMX order needs a GMX connection.

How to Reach Trade-Ready

  1. Enable Auto in dev portal (Privy link + HMAC secret).
  2. Complete onboarding for the venue you want to trade (Hyperliquid and/or GMX) in the Elfa app with the same identity.
  3. Return to portal and click Verify connection (writes the active exchange connection).

Related setup guides:

API Checks for Bots/Agents

Before creating a trade-action query:

  1. Call GET /v2/auto/exchanges.
  2. Confirm the exchange your action targets (hyperliquid or gmx) is present and active.
  3. If not connected, complete portal flow (or connect via POST /v2/auto/exchanges with HMAC).

After query creation:

  1. Poll GET /v2/auto/queries/{queryId} for query status and execution records.
  2. Use GET /v2/auto/executions and GET /v2/auto/executions/{executionId} for execution-level diagnostics.

Reading Account State (Balance, Positions, PnL)

Elfa Auto handles trade execution but does not proxy account state reads. To retrieve balance, leverage, positions, or unrealized PnL, query the venue directly. Each venue exposes its own read API — Elfa does not mirror or maintain these — so start from the venue's own reference:

VenueWhere to read account state
hyperliquidInfo endpoint — a POST /info REST API. A worked example is below.
gmxOn-chain — read positions and balances via the GMX Reader contract or the GMX subgraph (there is no REST info endpoint as on Hyperliquid). See the GMX docs.
Important for agent wallets

When querying account state, use the master account address (the address that owns the funds), not the agent wallet address. Using an agent wallet address returns empty results.

Hyperliquid example

As a reference, the following is a complete example implementation for reading account state on Hyperliquid — its endpoints and response shapes are Hyperliquid-specific. For GMX there is no equivalent inline example; follow the resources in the table above.

Endpoint

POST https://api.hyperliquid.xyz/info
Content-Type: application/json

clearinghouseState (Perpetuals Account)

Returns margin summary, open positions, leverage, and unrealized PnL.

Request:

{
"type": "clearinghouseState",
"user": "0x..."
}

Response fields:

FieldDescription
marginSummary.accountValueTotal account value (USD)
marginSummary.totalMarginUsedMargin currently in use
marginSummary.totalNtlPosTotal notional position
withdrawableAvailable balance for withdrawal
assetPositions[].coinAsset symbol
assetPositions[].sziSigned size (positive = long, negative = short)
assetPositions[].entryPxEntry price
assetPositions[].positionValueCurrent position value
assetPositions[].unrealizedPnlUnrealized profit/loss
assetPositions[].leverageLeverage settings
assetPositions[].liquidationPxLiquidation price

Working Example

# Get perpetuals account state
curl -X POST https://api.hyperliquid.xyz/info \
-H "Content-Type: application/json" \
-d '{"type": "clearinghouseState", "user": "0xYourMasterAddress"}'
import requests

def get_account_state(master_address: str) -> dict:
"""Fetch balance, positions, and unrealized PnL from Hyperliquid."""
resp = requests.post(
"https://api.hyperliquid.xyz/info",
json={"type": "clearinghouseState", "user": master_address}
)
resp.raise_for_status()
data = resp.json()

return {
"balance": float(data["marginSummary"]["accountValue"]),
"available": float(data["withdrawable"]),
"positions": [
{
"symbol": pos["position"]["coin"],
"size": float(pos["position"]["szi"]),
"entry_price": float(pos["position"]["entryPx"]),
"unrealized_pnl": float(pos["position"]["unrealizedPnl"]),
"leverage": pos["position"]["leverage"],
}
for pos in data["assetPositions"]
]
}

Additional Info Endpoints

TypeDescription
openOrdersList open orders
userFillsRecent fills and realized PnL
spotClearinghouseStateSpot balances (if trading spot)

See Hyperliquid Info Endpoint docs for the complete reference.

Canonical Template: Daily Suggest + Trade (amount: "10")

Use this end-to-end pattern when the user asks for a daily BTC suggestion plus execution intent.

Required preflight and lifecycle order:

  1. GET /v2/auto/exchanges and confirm hyperliquid is active.
  2. POST /v2/auto/queries/validate
  3. POST /v2/auto/queries
  4. Optional cleanup for tests: POST /v2/auto/queries/{queryId}/cancel

Canonical create payload:

{
"title": "Daily BTC suggest + trade 10",
"description": "Every 24h, generate a BTC trade suggestion and execute a 10-unit market order policy.",
"query": {
"conditions": {
"AND": [
{
"source": "cron",
"method": "every",
"args": { "period": "24h" },
"operator": "==",
"value": true
}
]
},
"actions": [
{
"stepId": "step_1",
"type": "llm",
"params": {
"objective": "Return one concise BTC trade suggestion (buy, sell, or no-trade) with confidence and rationale."
}
},
{
"stepId": "step_2",
"type": "market_order",
"params": {
"exchange": "hyperliquid",
"symbol": "BTC",
"side": "buy",
"amount": "10"
}
}
],
"expiresIn": "3d"
}
}

This template is intentionally strict for action-shape reliability:

  • schedule is cron.every with period: "24h"
  • suggestion action is type: "llm"
  • execution action is type: "market_order" with string amount: "10"

Trading Action Schemas

Canonical action parameter contracts are defined in Athena EQL spec. Use these structures for actions[*].

market_order

{
"stepId": "step_1",
"type": "market_order",
"params": {
"exchange": "hyperliquid",
"symbol": "BTC",
"side": "buy",
"amount": "10"
}
}
FieldTypeRequiredNotes
stepIdstringYesUnique step identifier in action list
type"market_order"YesAction type
params.exchange"hyperliquid" | "gmx"YesRequired explicit execution venue; no default. Must match an active connection.
params.symbolstringYesFor example BTC, ETH, SOL
params.side"buy" | "sell"YesOrder side
params.amountstringConditionalUSD/USDC notional; use exactly one of amount, size, or positionSizePercent. Minimum 10 on hyperliquid; gmx has no fixed minimum.
params.sizestringConditionalContracts/units; use exactly one of amount, size, or positionSizePercent.
params.positionSizePercentnumberConditionalPercent of account value, range (0, 100]; use exactly one of amount, size, or positionSizePercent. Resolved at execution as accountValue * (positionSizePercent / 100) * effectiveLeverage.
params.reduceOnlybooleanNoDefault false. Hyperliquid only — rejected for gmx.
params.leveragenumberNoInteger >= 1
params.marginType"cross" | "isolated"NoHyperliquid only — rejected for gmx. When omitted, the asset's current mode is preserved.
params.tpstringNoTake profit ("5%" or "50000")
params.slstringNoStop loss ("2%" or "48000")

The positionSizePercent notional is converted to size via the order's reference price — mark price for market_order, limit price for limit_order.

Choosing a venue (exchange)

exchange is required on every market_order / limit_order and selects where the order executes. There is no default — omitting it fails validation.

hyperliquidgmx
Minimum order notional10 (USDC)none
reduceOnlySupportedNot supported (rejected)
marginType (cross / isolated)SupportedNot supported (rejected)

The order's exchange is independent of the exchange arg used in price/ta conditions — you can monitor one venue's data and execute on the other.

GMX market-buy example:

{
"stepId": "step_1",
"type": "market_order",
"params": {
"exchange": "gmx",
"symbol": "SOL",
"side": "buy",
"amount": "25",
"leverage": 2
}
}

limit_order

{
"stepId": "step_1",
"type": "limit_order",
"params": {
"exchange": "hyperliquid",
"symbol": "BTC",
"side": "buy",
"amount": "10",
"price": "80000"
}
}

limit_order uses the same fields as market_order plus:

FieldTypeRequiredNotes
params.pricestringYesLimit price as absolute string

Complete Working Trading Example

Example: market-buy BTC with 10 USDC notional when BTC price is greater than 80000.

#!/usr/bin/env bash
set -euo pipefail

API_KEY="your_api_key"
HMAC_SECRET="your_hmac_secret"
BASE_URL="https://api.elfa.ai"
TIMESTAMP=$(date +%s)

# Preflight: make sure trade execution is enabled
curl -sS "${BASE_URL}/v2/auto/exchanges" \
-H "x-elfa-api-key: ${API_KEY}"

BODY='{
"title": "BTC > 80k market buy",
"description": "Buy 10 USDC notional of BTC once BTC trades above 80k.",
"query": {
"conditions": {
"AND": [
{
"source": "price",
"method": "current",
"args": { "symbol": "BTC" },
"operator": ">",
"value": 80000
}
]
},
"actions": [
{
"stepId": "step_1",
"type": "market_order",
"params": {
"exchange": "hyperliquid",
"symbol": "BTC",
"side": "buy",
"amount": "10"
}
}
],
"expiresIn": "24h"
}
}'

# Sign mounted path "/queries" (NOT "/v2/auto/queries")
SIGNATURE=$(echo -n "${TIMESTAMP}POST/queries${BODY}" | \
openssl dgst -sha256 -hmac "${HMAC_SECRET}" -hex | awk '{print $2}')

curl -sS -X POST "${BASE_URL}/v2/auto/queries" \
-H "x-elfa-api-key: ${API_KEY}" \
-H "x-elfa-signature: ${SIGNATURE}" \
-H "x-elfa-timestamp: ${TIMESTAMP}" \
-H "Content-Type: application/json" \
-d "${BODY}"

For a limit order, switch type to limit_order and include params.price as a string.

Important Mode Boundary

x402 (/x402/v2/auto/*) does not support order execution actions. For market_order and limit_order, use API key mode only.