Trading Execution (API Key mode)
Use this page when you want Auto queries to place orders (market_order, limit_order) on /v2/auto/*.
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 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
- Enable Auto in dev portal (Privy link + HMAC secret).
- Complete onboarding for the venue you want to trade (Hyperliquid and/or GMX) in the Elfa app with the same identity.
- 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 (
hyperliquidorgmx) is present and active. - 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. |
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:
| 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" | Yes | Required explicit execution venue; no default. Must match an active connection. |
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; 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. Hyperliquid only — rejected for gmx. |
params.leverage | number | No | Integer >= 1 |
params.marginType | "cross" | "isolated" | No | Hyperliquid only — rejected for gmx. When omitted, the asset's current mode is preserved. |
params.tp | string | No | Take profit ("5%" or "50000") |
params.sl | string | No | Stop 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.
hyperliquid | gmx | |
|---|---|---|
| Minimum order notional | 10 (USDC) | none |
reduceOnly | Supported | Not supported (rejected) |
marginType (cross / isolated) | Supported | Not 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:
| 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.