API Key + HMAC
Use this page for Auto integrations on /v2/auto/*.
If you still need to choose between API key and x402 models, start with Auto Overview.
Base URL
https://api.elfa.ai/v2/auto
Developer Portal Setup
Auto is enabled per API key in the Developer Portal. Use this flow:
- Open https://dev.elfa.ai/, select your API key, and open the Auto tab.
- Click Sign in to Enable Auto.
- You will be redirected to a Privy login screen. If you already have an ELFA account, sign in with the same login method so your existing plans and queries are linked.
- After successful sign-in, the Auto card updates to Auto is enabled and shows your API key details.
- Copy the initial HMAC secret when it is shown. The initial secret display is one-time.
- If needed, click Regenerate to rotate the HMAC secret.
Regenerating the HMAC secret can break existing clients until they are updated to sign requests with the new secret.
- For exchange-backed execution (for example Hyperliquid), open the ELFA app from Exchange Connections, complete setup there using the same account, then return and click Verify connection.
Expected UI progression: Sign in to Enable Auto -> Auto is enabled (API key + HMAC section) -> Exchange Connections and Quick Start visible.
Credential Hygiene (Required)
Never print or log ELFA_API_KEY, ELFA_HMAC_SECRET, x-elfa-signature, or full auth headers.
Do not use debug commands like echo $ELFA_API_KEY, printenv, or console.log(process.env).
If you must debug auth, log only redacted metadata (for example request path and timestamp), not secret values.
Execution Prerequisite (Order Actions)
market_order and limit_order actions require an active exchange connection on the linked user for the venue the action targets (hyperliquid or gmx), managed via /v2/auto/exchanges.
If no exchange is connected, a trade-action query may still be created, but order execution fails at runtime when the trigger fires.
Use this quick-start request pattern for POST /v2/auto/queries:
API_KEY="your_api_key"
HMAC_SECRET="your_hmac_secret"
TIMESTAMP=$(date +%s)
BODY='{"query":{"conditions":{"AND":[{"source":"price","method":"current","args":{"symbol":"BTC"},"operator":">","value":80000}]},"actions":[{"stepId":"step_1","type":"notify","params":{"message":"BTC crossed 80k"}}],"expiresIn":"24h"}}'
# Sign requests with HMAC-SHA256.
# Important: 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 -X POST https://api.elfa.ai/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}"
If you still see /v2/athena/* in a dashboard quick-start snippet, treat it as outdated and use /v2/auto/* from this page.
Required Headers
All /v2/auto/* routes require:
x-elfa-api-key
Trade-action mutations also require:
x-elfa-signaturex-elfa-timestamp
Notification-only mutations skip HMAC — see below.
HMAC Bypass for Notification-Only Mutations
Mutations whose EQL action is a pure notification skip the HMAC requirement. This lets agents onboard without first generating an HMAC secret. Trade execution and exchange linking continue to require HMAC unconditionally.
Notification action types (HMAC bypassed):
notifytelegram_botwebhookllmwhoseparams.callback.action.typeis one of the above
Trade action types (HMAC required):
market_orderlimit_orderllmwhoseparams.callback.action.typeismarket_orderorlimit_order
Decision is per-route:
| Route | Decision input |
|---|---|
POST /queries, POST /queries/drafts | Request body's query.actions[*].type |
POST /queries/drafts/:id/convert | Stored draft's actions |
POST /queries/:id/cancel, DELETE /queries/:id | Stored query's actions |
If the lookup fails or the action type is unknown, HMAC is enforced (fail-safe). Unknown action types added in future API versions default to requiring HMAC, so always-signing clients keep working.
POST /chat is fully ungated regardless of content because it produces drafts only — activation flows through convert, which is still gated when the draft is trade-flavoured.
POST /exchanges and DELETE /exchanges/:exchange always require HMAC — linking an exchange is the gateway to trade execution.
If your client signs every mutation, you do not need to opt into the bypass. Signed requests are accepted on every route. The bypass is purely an optimization for clients that want to skip the HMAC setup step.
HMAC Signing
Headers:
x-elfa-signature: <hex_hmac_sha256>
x-elfa-timestamp: <unix_seconds>
Payload format:
timestamp + method + path + body
Where:
timestamp: unix secondsmethod: uppercase HTTP methodpath: route path verified by Auto routerbody: exact JSON string body, or empty string
Replay window: +/-30 seconds.
Important mounted-router detail:
- Request URL may be
/v2/auto/queries - Signature
pathmust be/queries(the mounted router path)
Using /v2/auto/queries in the signed payload will fail verification.
URL Path vs Signature Path
| HTTP Method | Request URL path | Signature path |
|---|---|---|
POST | /v2/auto/queries | /queries |
DELETE | /v2/auto/queries/{id} | /queries/{id} |
POST | /v2/auto/chat | /chat |
POST | /v2/auto/exchanges | /exchanges |
DELETE | /v2/auto/exchanges/{exchange} | /exchanges/{exchange} |
Concrete Signing Example
Example target endpoint: POST /v2/auto/queries
import crypto from "crypto";
const hmacSecret = process.env.ELFA_HMAC_SECRET!;
const timestamp = Math.floor(Date.now() / 1000).toString();
const method = "POST";
const path = "/queries"; // mounted path (NOT /v2/auto/queries)
const bodyObj = {
title: "BTC Alert",
description: "Notify when BTC trades above the 100k level so I can review the breakout setup.",
query: {
conditions: {
AND: [
{
source: "price",
method: "current",
args: { symbol: "BTC" },
operator: ">",
value: 100000,
},
],
},
actions: [
{
stepId: "step_1",
type: "notify",
params: { message: "BTC crossed target" },
},
],
expiresIn: "24h",
},
};
const body = JSON.stringify(bodyObj);
const payload = `${timestamp}${method}${path}${body}`;
const signature = crypto
.createHmac("sha256", hmacSecret)
.update(payload)
.digest("hex");
console.log({ timestamp, path }); // Avoid logging secrets/signatures/bodies in production logs.
Then send:
POST /v2/auto/queries
x-elfa-api-key: <api_key>
x-elfa-timestamp: <timestamp>
x-elfa-signature: <signature>
content-type: application/json
<body exactly as signed>
No-body example (DELETE /v2/auto/queries/{id}): sign with path = "/queries/{id}" and body = "".
Exchange Mutation Signing Examples
POST /v2/auto/exchanges (sign with path = "/exchanges"):
import crypto from "crypto";
const hmacSecret = process.env.ELFA_HMAC_SECRET!;
const timestamp = Math.floor(Date.now() / 1000).toString();
const method = "POST";
const path = "/exchanges";
const body = JSON.stringify({
exchange: "hyperliquid",
credentialType: "agent_wallet",
metadata: {
masterAddress: "0x1111111111111111111111111111111111111111",
agentAddress: "0x2222222222222222222222222222222222222222",
},
});
const signature = crypto
.createHmac("sha256", hmacSecret)
.update(`${timestamp}${method}${path}${body}`)
.digest("hex");
DELETE /v2/auto/exchanges/{exchange} (sign with path = "/exchanges/{exchange}", empty body):
import crypto from "crypto";
const hmacSecret = process.env.ELFA_HMAC_SECRET!;
const timestamp = Math.floor(Date.now() / 1000).toString();
const method = "DELETE";
const exchange = "hyperliquid";
const path = `/exchanges/${exchange}`;
const body = "";
const signature = crypto
.createHmac("sha256", hmacSecret)
.update(`${timestamp}${method}${path}${body}`)
.digest("hex");
Enablement Requirements
API keys must be Auto-enabled and linked to a user identity in the Developer Portal.
If not enabled/linked, /v2/auto/* returns 403.
Endpoint Matrix
Mounted endpoints below are the routes inside /v2/auto.
"Conditional" below means HMAC is required only when the action is trade-flavoured (market_order, limit_order, or llm callback to those). Notification-only actions skip HMAC. See HMAC Bypass for Notification-Only Mutations for the full decision rule.
Queries and Drafts
| Endpoint | HMAC |
|---|---|
GET /queries | No |
GET /queries/:id | No |
GET /queries/:id/evaluations | No |
GET /queries/:id/stream | No |
GET /queries/:id/sessions | No |
GET /queries/:id/sessions/:sessionId | No |
POST /queries/validate | No |
POST /queries/preview | No |
POST /queries | Conditional (body) |
POST /queries/:id/cancel | Conditional (stored query) |
DELETE /queries/:id | Conditional (stored query) |
GET /queries/drafts | No |
GET /queries/drafts/:id | No |
POST /queries/drafts | Conditional (body) |
DELETE /queries/drafts/:id | No |
POST /queries/drafts/:id/preview | No |
POST /queries/drafts/:id/convert | Conditional (stored draft) |
Executions
| Endpoint | HMAC |
|---|---|
GET /executions | No |
GET /executions/:id | No |
Chat
| Endpoint | HMAC |
|---|---|
POST /chat | No |
Exchange Connections
| Endpoint | HMAC |
|---|---|
GET /exchanges | No |
POST /exchanges | Yes |
DELETE /exchanges/:exchange | Yes |
Common Errors
401: missing/invalid key, signature, timestamp, or clock skew403: Auto not enabled for the API key or no linked user422: validation failure (query content)
Related Docs
- Model selection: Auto Overview
- Trading readiness: Trading Execution (API Key mode)
- Keyless mode: x402 Instructions
- Cross-product auth: Authentication