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.
Trading Execution (API Key mode)
Use this page when you want Auto queries to place orders (market_order, limit_order) on /v2/auto/*.
This page covers order execution triggered by Auto queries. Direct synchronous order execution is not listed in these docs for now.
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
| State | What It Means | What Works | What Fails |
|---|---|---|---|
| Auto disabled | API key is not linked/enabled for Auto | Nothing on /v2/auto/* | Requests fail with enablement/auth errors |
| Auto enabled (alerting-only) | API key linked + HMAC configured, but no active exchange connection | Query 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 venue | Non-trade actions and trade actions (market_order, limit_order) | N/A (subject to runtime risk/balance/policy checks) |
Trade actions execute on one of four venues, 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 Binance order needs a Binance connection.
Supported Venues
params.exchange accepts hyperliquid, gmx, binance, or pacifica. What each venue supports differs — the rules below are enforced at query creation, so a rejected combination fails validation rather than at execution time.
| Venue | Min notional | reduceOnly | marginType | tp / sl | HIP-3 markets | price / ta data |
|---|---|---|---|---|---|---|
hyperliquid | 10 | Yes | cross, isolated | Yes | Yes | Yes |
gmx | none | Rejected | isolated only | Yes | Rejected | Yes — no volume |
binance | 5 | Yes | cross, isolated | Rejected | Rejected | Yes — perps only |
pacifica | 5 | Yes | cross, isolated | Rejected | Rejected | Not supported |
Minimums are USD notional and apply to params.amount. Binance trades perps only through Auto — spot markets are not exposed to trade actions.
The rightmost column is the venue's availability as a data source for price / ta conditions, which is a separate choice from where an order executes — see Exchange Argument. You can monitor one venue and execute on another.
Pacifica has no market-data feed behind price / ta conditions, so it cannot be used as a condition venue. Trigger Pacifica orders from another venue's data — for example a price condition on binance with a market_order on pacifica.
Connecting a venue
Connections are created either by POST /v2/auto/exchanges (HMAC required) or through the app onboarding flow below, depending on the venue. Credential fields use ccxt's own names:
| Venue | Credentials | Notes |
|---|---|---|
hyperliquid | agent wallet | Connect through the Elfa app, then Verify connection in the portal. |
gmx | agent wallet | Same flow as Hyperliquid. |
binance | apiKey + secret | Connect directly via POST /v2/auto/exchanges. Standard CEX API credentials. |
pacifica | privateKey + walletAddress | Connect directly via POST /v2/auto/exchanges. privateKey is a base58 Solana key. walletAddress is the trading wallet and is required — it is not derived from the key. |
Credentials are verified with the venue before the connection is stored, so an invalid or unauthorised key fails at connect time rather than surfacing later as a failed order.
credentialType remains a free-form label. The examples on this page use agent_wallet, api_key, and wallet as recommended values, but the backend does not currently enforce those labels as enums.
How to Reach Trade-Ready
- Enable Auto in dev portal (Privy link + HMAC secret).
- Connect the venue you want to trade — for Hyperliquid and GMX, complete onboarding in the Elfa app with the same identity; for Binance and Pacifica, connect via
POST /v2/auto/exchangeswith the credentials above. - 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:
- Call
GET /v2/auto/exchanges. - Confirm the exchange your action targets is present and active. See Supported Venues.
- If not connected, complete portal flow (or connect via
POST /v2/auto/exchangeswith HMAC).
After query creation:
- Poll
GET /v2/auto/queries/{queryId}for query status and execution records. - Use
GET /v2/auto/executionsandGET /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:
| Venue | Where to read account state |
|---|---|
hyperliquid | Info endpoint — a POST /info REST API. A worked example is below. |
gmx | On-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. |
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:
| Field | Description |
|---|---|
marginSummary.accountValue | Total account value (USD) |
marginSummary.totalMarginUsed | Margin currently in use |
marginSummary.totalNtlPos | Total notional position |
withdrawable | Available balance for withdrawal |
assetPositions[].coin | Asset symbol |
assetPositions[].szi | Signed size (positive = long, negative = short) |
assetPositions[].entryPx | Entry price |
assetPositions[].positionValue | Current position value |
assetPositions[].unrealizedPnl | Unrealized profit/loss |
assetPositions[].leverage | Leverage settings |
assetPositions[].liquidationPx | Liquidation 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
| Type | Description |
|---|---|
openOrders | List open orders |
userFills | Recent fills and realized PnL |
spotClearinghouseState | Spot 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:
GET /v2/auto/exchangesand confirmhyperliquidis active.POST /v2/auto/queries/validatePOST /v2/auto/queries- 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.everywithperiod: "24h" - suggestion action is
type: "llm" - execution action is
type: "market_order"with stringamount: "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"
}
}
| Field | Type | Required | Notes |
|---|---|---|---|
stepId | string | Yes | Unique step identifier in action list |
type | "market_order" | Yes | Action type |
params.exchange | "hyperliquid" | "gmx" | "binance" | "pacifica" | Yes | Required explicit execution venue; no default. Must match an active connection. See Supported Venues. |
params.symbol | string | Yes | For example BTC, ETH, SOL |
params.side | "buy" | "sell" | Yes | Order side |
params.amount | string | Conditional | USD/USDC notional; use exactly one of amount, size, or positionSizePercent. Minimum 10 on hyperliquid, 5 on binance and pacifica; gmx has no fixed minimum. |
params.size | string | Conditional | Contracts/units; use exactly one of amount, size, or positionSizePercent. |
params.positionSizePercent | number | Conditional | Percent of account value, range (0, 100]; use exactly one of amount, size, or positionSizePercent. Resolved at execution as accountValue * (positionSizePercent / 100) * effectiveLeverage. |
params.reduceOnly | boolean | No | Default false. Rejected for gmx; supported on every other venue. |
params.leverage | number | No | Integer >= 1 |
params.marginType | "cross" | "isolated" | No | gmx accepts isolated only — "cross" is rejected. When omitted, the asset's current mode is preserved. |
params.tp | string | No | Take profit ("5%" or "50000"). Rejected for binance and pacifica. |
params.sl | string | No | Stop loss ("2%" or "48000"). Rejected for binance and pacifica. |
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. It accepts hyperliquid, gmx, binance, or pacifica; per-venue minimums and parameter support are in Supported Venues.
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.
Trading HIP-3 markets (equities, indices, commodities)
Order actions are not limited to crypto. Any HIP-3 market — xyz:SP500,
xyz:NVDA, xyz:GOLD, xyz:CL, xyz:SPCX — is tradable through the same
market_order / limit_order schema, using its DEX-prefixed symbol:
{
"stepId": "step_1",
"type": "market_order",
"params": {
"exchange": "hyperliquid",
"symbol": "xyz:SP500",
"side": "buy",
"amount": "250"
}
}
Three things to know:
- Hyperliquid only. No other venue lists HIP-3 markets. The same order with
"exchange": "gmx"is rejected:EQL_INVALID_SYMBOL— "Order symbol 'xyz:SP500' is not supported for exchange 'gmx'". - They trade 24/7, including when the underlying cash market is halted — which is the point. See Catalyst Triggers.
- Max leverage is per-market, and the
10USDC minimum notional still applies.
Symbol format and asset classes: Symbols.
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:
| Field | Type | Required | Notes |
|---|---|---|---|
params.price | string | Yes | Limit 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.