mcpField
E
elfa-ai

elfa-ai

github.com/elfa-aiその他1179 回インストール1 週間前に更新
リポジトリ

elfa-ai について

Elfa API Skillは、暗号資産・株式の市場データと自動条件エンジンを扱うElfa APIをエージェントから利用するためのスキルです。ソーシャルセンチメント、イベント、ポジション、注目度ランキングに加え、条件監視とアクション実行のクエリも扱います。

主な機能

  1. 01トレンドトークン・言及・ナラティブの検索
  2. 02AI市場サマリーとイベント情報の取得
  3. 03市場のポジションと注目度ランキングの確認
  4. 04自動条件クエリの作成と管理
  5. 05APIキーまたはx402による認証

活用シーン

  1. 01トークンやキーワードのソーシャル言及を追跡
  2. 02暗号資産・株式市場のイベントを分析
  3. 03トレーダーのポジションや注目度の変化を監視
  4. 04価格やナラティブの条件に応じたアラート

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/elfa-ai/skills --skill elfa-ai

インストール後、Claude Code で/elfa-aiを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
swagger.json
scripts/
build-skill.sh
elfa_call.sh

README(原文)English

プロジェクトの README より

この README の日本語訳はまだありません。以下は英語の原文です。日本語の紹介はページ上部をご覧ください。

Elfa API Skill

This skill enables agents to work with the Elfa API — a social listening, market context layer, and automated condition engine for crypto. Elfa ingests real-time data from Twitter/X, Telegram, and other sources, then structures sentiment, narratives, and attention shifts into actionable trading insights. The Market Intelligence endpoints cover measurement (counts, engagement, accounts) and interpretation (chat, summaries, narratives). API v3 turns news and market chatter across crypto and equities into events, positions and rankings. The Auto subsystem adds a managed condition engine and trigger pipeline — describe what to watch for, and Auto evaluates continuously and fires actions when conditions are met.

Full documentation: docs.elfa.ai

Staying current — check weekly. This skill is updated on a weekly cadence to track Elfa API and Auto changes. Before relying on it, check whether your copy may be stale: if it has been more than 7 days since this skill was last fetched/installed (or you are unsure), re-fetch the latest version before acting —

When in doubt about a specific endpoint, parameter, or price, verify against docs.elfa.ai at call time rather than trusting a possibly-stale copy.

Environment and credentials

Elfa supports API-key auth and x402 keyless payments. API keys are optional when using x402.

VariableRequiredUse
ELFA_API_KEYNoAPI-key authenticated requests. Start a pay-as-you-go balance at https://go.elfa.ai/claude-skills.
ELFA_AGENT_SECRETNoPersistent agent identity secret for x402 Auto. Generate once with openssl rand -hex 32 and reuse for query lifecycle calls.

Do not reuse this as your webhook signing secret. webhook.params.signingSecret is a separate per-webhook secret that verifies deliveries from Elfa to your server. See Webhook signature verification.

x402 wallet signing is handled client-side by @x402/fetch or @x402/axios.

When to use this skill

  • User asks about trending tokens, narratives, or contract addresses in crypto
  • User wants social mentions for a specific ticker or keyword
  • User wants smart stats (smart followers, engagement) for a Twitter/X account
  • User wants an AI-generated market summary, macro overview, or token analysis — including streaming chat output
  • User wants to know what happened in crypto or equity markets and how prices reacted, who is long or short an asset, whose calls to trust, or which assets get the most attention (API v3)
  • User asks how to integrate, call, or use the Elfa API
  • User wants code examples (curl, Python, JavaScript/TypeScript) for Elfa endpoints
  • User mentions "elfa" in a crypto or trading data context
  • User wants to set up automated alerts or monitoring on price, indicators, or narratives
  • User wants to build condition-based triggers (e.g., "alert me when BTC crosses 100k")
  • User wants to watch the X follow graph — who followed a tracked account, who it started following, or several tracked accounts converging on the same account
  • User wants an alert when a specific trader opens or closes a position, or when attention on a token or stock spikes (Auto calls, mindshare)
  • User mentions Auto, EQL, condition engine, or trigger pipeline in a crypto context
  • User wants agent workflows that react to market conditions automatically
  • User wants to build queries with Builder Chat using natural language

API Overview

Base URL: https://api.elfa.ai Version: v2, plus API v3 (/v3/) Docs: docs.elfa.ai

Two access modes

Elfa supports two independent ways to authenticate requests:

ModeEndpoint prefixAuth headerBest for
API key/v2/, /v3/x-elfa-api-key: YOUR_KEYHumans & apps with a registered key
x402 (keyless)/x402/v2/PAYMENT-SIGNATURE: <signed-payload>Agents & wallets — no signup needed

Both modes access the same v2 data. Otherwise the only difference is how you authenticate:

  • API key — register at https://go.elfa.ai/claude-skills. Start on pay-as-you-go: the free tier is being phased out, so new integrations should top up a PAYG balance rather than build against free credits.
  • x402 — pay per request with USDC on Base, Arbitrum, Polygon, Avalanche, or Solana. No registration, no API key. Currently in beta.
Endpoints at a glance
Data endpoints

All endpoints below work with both /v2/ (API key) and /x402/v2/ (keyless) prefixes, except key-status which is API key mode only.

EndpointMethodDescriptionCredits
/v2/pingGETHealth check — no auth required (API key mode only)Free
/v2/key-statusGETAPI key usage & limits (API key only)Free
/v2/aggregations/trending-tokensGETTrending tokens by mention count1
/v2/account/smart-statsGETSmart follower & engagement stats — legacy, will be removed 28 Oct 20261
/v2/data/top-mentionsGETTop mentions for a ticker symbol1
/v2/data/keyword-mentionsGETSearch mentions by keywords or account1
/v2/data/event-summaryGETAI event summaries from keyword mentions5
/v2/data/trending-narrativesGETTrending narrative clusters (Grow+ / PAYG)5
/v2/data/token-newsGETToken-related news mentions (tweet source only)1
/v2/aggregations/trending-cas/twitterGETTrending contract addresses (Twitter)1
/v2/aggregations/trending-cas/telegramGETTrending contract addresses (Telegram)1
/v2/chatPOSTAI chat, complete JSON response (Grow+ / PAYG)Speed-based
/v2/chat/streamPOSTAI chat, incremental SSE stream (PAYG / Enterprise)Speed-based

/v2/ping, /v2/key-status, and /v2/chat/stream are API-key mode only — they have no /x402/v2/ counterpart.

API v3 endpoints

API v3 turns news and market chatter across crypto and equities into events, positions and rankings. Every endpoint is GET and authenticates with x-elfa-api-key (no x402). The v3 API is free on the Pay-as-you-go and Grow plans until 31 October 2026, and Enterprise keys can call it too; a Free key can call only /v3/key-status. See Step 2d for the rules they share.

QuestionEndpointReturns
Which id is this?/v3/entitiesThe id every other endpoint takes, from a ticker, name, handle, address, CoinGecko id, FIGI or CIK
What is happening?/v3/eventsEvents: what happened, what it names, how prices reacted. Searchable by topic
/v3/events/storylinesOne development followed over days
/v3/events/arcsStorylines grouped into a larger movement
Where is attention?/v3/market/crypto/mindshare, /v3/market/equity/mindshareAssets ranked by share of attention, with hot
/v3/market/crypto/trending-contractsContract addresses ranked by how many posts pasted them
Which way does the crowd lean?/v3/market/crypto/call-book, /v3/market/equity/call-bookThe whole market's long/short lean, hour by hour
/v3/calls/positioningOpen longs and shorts on one asset, now
What are accounts doing?/v3/callsEvery post that takes or changes a position
/v3/calls/episodesEach position from open to close, with prices and the author's track record
Who is worth reading?/v3/people/accountsAccounts ranked by standing, or by track record with metric
What can my key do?/v3/key-statusPlan, credits, limits and how far back it reads
Auto endpoints (Condition Engine)

Auto endpoints are available under /v2/auto/ (API key) and /x402/v2/auto/ (keyless). See Auto docs for full details.

Auth column legend (tables below). API key = x-elfa-api-key only.

API key mode (/v2/auto/*):

Query lifecycle:

EndpointMethodDescriptionAuth
/v2/auto/chatPOSTBuilder Chat — AI-assisted query building (produces drafts only)API key
/v2/auto/queries/validatePOSTValidate EQL and preview costAPI key
/v2/auto/queriesPOSTCreate and activate a queryAPI key
/v2/auto/queriesGETList queriesAPI key
/v2/auto/queries/:queryIdGETPoll query status and executions (resolves query or draft)API key
/v2/auto/queries/:queryId/cancelPOSTCancel an active query (returns 409 if status is terminal)API key
/v2/auto/queries/:queryIdDELETEDelete a terminal query — only when status is triggered / expired / cancelled / failed (returns 409 otherwise; active queries must be cancelled first)API key
/v2/auto/queries/streamGETStream notifications for all your queries on one connection (API-key only — not agent/x402)API key
/v2/auto/queries/:queryId/streamGETStream notifications for a single query via SSEAPI key

Query drafts (editable, not yet active):

EndpointMethodDescriptionAuth
/v2/auto/queries/draftsPOSTCreate or update (upsert) a query draftAPI key
/v2/auto/queries/draftsGETList editable query draftsAPI key
/v2/auto/queries/drafts/:draftIdGETGet a specific draft (legacy — prefer GET /queries/{queryId})API key
/v2/auto/queries/drafts/:draftIdDELETEDelete a query draftAPI key
/v2/auto/queries/drafts/:draftId/validatePOSTValidate a stored draftAPI key
/v2/auto/queries/drafts/:draftId/convertPOSTConvert a draft into an active queryAPI key

LLM sessions (for action.type: "llm" queries):

EndpointMethodDescriptionAuth
/v2/auto/queries/:queryId/sessionsGETList LLM sessionsAPI key
/v2/auto/queries/:queryId/sessions/:sessionIdGETGet full LLM session detailsAPI key

Executions (trigger fire records):

EndpointMethodDescriptionAuth
/v2/auto/executionsGETList execution recordsAPI key
/v2/auto/executions/:executionIdGETGet a single execution recordAPI key

Other:

EndpointMethodDescriptionAuth
/v2/auto/validate-symbol/:exchange/:symbolGETCheck whether a symbol is supported on a venue — pre-flight for price / ta data sourcesAPI key

x402 mode (/x402/v2/auto/*) — note: some routes use POST instead of GET:

EndpointMethodDescription
/x402/v2/auto/chatPOSTBuilder Chat
/x402/v2/auto/queries/validatePOSTValidate EQL and preview cost
/x402/v2/auto/queriesPOSTCreate and activate a query
/x402/v2/auto/queries/:queryIdPOSTPoll query status (POST, not GET)
/x402/v2/auto/queries/:queryId/cancelPOSTCancel an active query
/x402/v2/auto/queries/:queryId/streamGETStream notifications via SSE
/x402/v2/auto/queries/:queryId/sessionsPOSTList LLM sessions (POST, not GET)
/x402/v2/auto/queries/:queryId/sessions/:sessionIdPOSTGet LLM session details (POST, not GET)

Note on x402 Auto scope. Drafts, executions, and the terminal-query DELETE endpoint are API-key-mode only. x402 Auto covers the core monitoring lifecycle (chat, validate, create, poll, cancel, stream, sessions). The account-wide stream GET /v2/auto/queries/stream is not exposed on x402 — x402 (and agent identities) get the per-query stream only.

Auto no longer accepts order actions. New queries and drafts cannot use market_order or limit_order, at top level or in an llm callback — Auto rejects them with EQL_INVALID_ACTION. Existing stored trade queries stay readable, cancellable and deletable. Route follow-up execution through your own runner off a webhook, notify or telegram_bot action.

For full parameter details, see the Elfa API documentation.

Machine-readable sources (prefer these over scraping docs pages)

When you need exact endpoint metadata, load these instead of walking the docs page by page:

SourceURLUse for
Endpoint manifesthttps://docs.elfa.ai/agent/endpoints.manifest.jsonMethod/path, docs route, required headers, payment requirement, request/response examples
Docs maphttps://docs.elfa.ai/llms.txtCanonical page map + machine-readable links. A plain GET https://docs.elfa.ai/ returns the same index as text/plain to any client that does not ask for HTML
Full docs texthttps://docs.elfa.ai/llms-full.txtOnly when deeper page context is needed
OpenAPI specbundled at references/swagger.jsonExact schemas, params, and defaults. Ignore the per-operation cost lines on /v3; see API v3 endpoints for what it costs
Latest skillhttps://raw.githubusercontent.com/elfa-ai/skills/main/skills/elfa-ai/SKILL.mdRe-fetch this skill when you cannot install it

The manifest URL is stable — earlier releases served a rotating assets/files/endpoints.manifest-*.json path; use the /agent/ path above.

Install or refresh the skill with npx skills add elfa-ai/skills.

If you are running inside a client that cannot execute commands, such as Claude Desktop, there is an MCP server that exposes the API as tools: connect the hosted server at https://mcp.elfa.ai/mcp and sign in with Elfa, or run npx -y @elfa-ai/mcp with an API key. See docs.elfa.ai/tools/mcp. Where you can run commands, prefer this skill — it costs no context until you use it.

When you are writing TypeScript or Python rather than calling the API by hand, the official SDKs (@elfa-ai/sdk, elfa-sdk) wrap the same surface with typed responses, retries, and SSE streaming. See docs.elfa.ai/tools/sdks.

How to use this skill

Step 1: Determine the mode

Check whether the user wants to make a live call, get code/integration help, or set up automated monitoring.

  • If the user says things like "show me trending tokens", "what's the sentiment on SOL", "get me the top mentions for ETH" → they want live data. Proceed to Step 2a. If it is unclear which endpoint fits, or the ask needs more than one call, see Step 2c.
  • If the user asks what happened to an asset, how a story developed, who is long or short, whose calls to trust, or what equities are getting attention → use API v3. See Step 2d.
  • If the user says things like "how do I call the trending tokens endpoint", "give me a curl example", "help me integrate Elfa" → they want code snippets. Skip to Step 4.
  • If the user mentions x402, keyless, pay-per-request, or wallet-based access → they want x402 mode. See Step 2b for live calls or Step 4 for code snippets.
  • If the user mentions Auto, alerts, triggers, monitoring, conditions, "alert me when", "notify me if", EQL, or Builder Chat → they want Auto. Proceed to Step 3.
Step 2a: Making live API calls (API key mode)

Use the bash_tool to call the Elfa API via curl.

Getting the API key:

  1. Check if the ELFA_API_KEY environment variable is set. This is the preferred method.

  2. If the env var is not set, stop and prompt the user. Offer both options:

    To make live calls, you have two options:

    Option A — API key on pay-as-you-go (recommended): Register at https://go.elfa.ai/claude-skills and top up a PAYG balance — $0.0145 per credit, $20 minimum, card or USDC. Then set the key as the ELFA_API_KEY environment variable (do not paste it directly into the chat). The free tier is being phased out, so start on PAYG rather than free credits.

    Option B — x402 keyless payments: Pay per request with USDC on Base, Arbitrum, Polygon, Avalanche, or Solana — no signup needed. See the x402 docs for setup.

    Do not attempt any authenticated API calls without a key or x402 setup. Wait for the user.

  3. Credential safety:

    • Always read the API key from the ELFA_API_KEY environment variable, never ask the user to paste it into the conversation.
    • Never log or expose the full API key in outputs — mask it when displaying curl commands.
    • Never echo or print environment variables: Do not run echo $ELFA_API_KEY, env | grep ELFA, printenv, or similar commands that would expose credentials in the transcript.
    • If a user does paste a key in chat, warn them to rotate it and set it as an env var instead.

Plans, credits, and rate limits:

Pay-per-use first: PAYG is $0.0145/credit at 60 RPM with an API key, drawn down from a prepaid credit balance ($20 minimum top-up, card or USDC); x402 is $0.0145/credit at 1,000 RPM with no account. Same per-credit price — pick on integration style. The free tier is being phased out, so recommend PAYG (or x402) for any new integration.

Accounts already on PAYG keep $0.009 per credit until 28 September 2026, 16:00 UTC (29 September, 00:00 SGT). x402 has no accounts, so its price applies to every payer at once.

Subscription plans:

Free (phasing out)GrowEnterprise
Monthly$0$870Custom
Annual (15% off)$0$8,874 ($739.50/mo)Custom
Credits / month1,000120,000Custom
Overage—$0.00725 / creditCustom
Rate limit60 RPM120 RPMCustom
Agent Chat—✓✓
Agent Automation—✓ (up to 100 active plans)✓

Grow subscriptions started before October 2026 keep their existing price ($290/mo, or $2,958/yr billed annually), 40,000 credits per month, and $0.009 per overage credit.

What each tier unlocks:

  • Free (phasing out) — core social data: trending tokens, smart stats, top mentions, keyword mentions, event summaries, token news, trending contract addresses (Twitter + Telegram)
  • Grow — everything in Free, plus token mindshare, sentiment-weighted mentions, trending narratives, and Elfa Intelligence (Agent Chat and Agent Automation)
  • Enterprise — everything in Grow, plus custom credit and rate limits, streaming AI Chat (Ask Elfa, POST /v2/chat/stream), dedicated support, custom integrations, and priority support

Gate summary for the tier-restricted endpoints:

EndpointRequires
/v2/data/trending-narrativesGrow+ or PAYG
/v2/chatGrow+ or PAYG
/v2/chat/streamPAYG or Enterprise only
/v3/* except /v3/key-statusPAYG, Grow or Enterprise

On Grow, requests past the monthly credits keep working and are billed as overage, unless overage is turned off or a spend cap is set in the dashboard; on Free, they are rejected until credits reset next month. Exceeding the rate limit returns 429 (honor Retry-After). If a user hits an authorization error on a gated endpoint, they can move to PAYG, upgrade their plan, or use x402 instead (v2 only). Full details at https://go.elfa.ai/claude-skills.

Making the call:

curl -s -H "x-elfa-api-key: $ELFA_API_KEY" "https://api.elfa.ai/v2/aggregations/trending-tokens?timeWindow=24h&pageSize=10"
Step 2b: Making live API calls (x402 keyless mode)

x402 lets any wallet pay per request using USDC on Base, Arbitrum, Polygon, Avalanche, or Solana — no API key, no registration. This is ideal for agents, bots, and programmatic access.

How x402 works:

  1. Send a request to the /x402/v2/ version of any endpoint (no auth header).
  2. The server responds with HTTP 402 containing payment requirements.
  3. Your wallet signs a USDC transfer authorization (no gas fees).
  4. Resend the request with the signed payment in the PAYMENT-SIGNATURE header.
  5. Server verifies payment, serves the response, and settles on-chain.

x402 signing and security:

  • Signing happens entirely client-side using the @x402/fetch or @x402/axios libraries. The agent never handles, stores, or transmits private keys.
  • The user's wallet private key is used only locally by the x402 library to sign EIP-712 typed data authorizing a specific USDC amount for a specific request.
  • Never ask the user to share their wallet private key or seed phrase in the conversation.
  • When generating x402 code examples, use "0xYOUR_PRIVATE_KEY" as a placeholder and advise the user to load it from an environment variable (e.g., process.env.PRIVATE_KEY).

x402 details:

  • Networks: the server offers every supported network in the 402 response; your client signs on the first one it's registered for. Register the network(s) you hold USDC on.
  • Scheme: exact (fixed price per request), plus batch-settlement (prepaid channel, metered) on the chat endpoints on Base; asset is native Circle USDC (6 decimals). batch-settlement needs no token approval: the deposit is a signed USDC authorization. It is offered on Base only; every other network, including Solana, is exact only.
  • Errors: branch on the HTTP status first and read the body as the refinement. Most rejections return { code, message, errorId, requestId }, but the x402 surface also returns a short { error } with no code. requestId is also the x-request-id response header, present even on the short shape. On the Auto endpoints, EQL validation failures return { valid, errors, warnings } (at 422 from create, at 200 from validate, where you must read valid), and errors raised by the execution engine are passed through unchanged. A 402 is the payment handshake, not a billing error. See Error Responses.
  • Status: Currently in beta.
NetworkChain IDUSDC AddressFacilitator
Baseeip155:84530x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913payai.network
Arbitrumeip155:421610xaf88d065e77c8cC2239327C5EDb3A432268e5831payai.network
Polygoneip155:1370x3c499c542cEF5E3811e1192ce70d8cC03d5c3359payai.network
Avalancheeip155:431140xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6Epayai.network
Solanasolana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpEPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1vpayai.network

EVM signing costs no gas. On Solana the facilitator pays the transaction fee.

x402 pricing (data endpoints):

TierCreditsUSDC CostEndpoints
Standard1$0.0145trending-tokens, smart-stats, keyword-mentions, token-news, top-mentions, trending-cas
Extended5$0.0725event-summary, trending-narratives

Data endpoints are exact only. /x402/v2/chat is speed-based and offers both schemes — pay a flat price with exact, or pay from a prepaid batch-settlement channel and be charged what the turn used, up to the cap:

Speedexact (flat)batch-settlement (charged up to)
fast$1$2
expert (default)$2$6

batch-settlement: the first call deposits USDC into the channel (at least $6, one turn at the expert cap); later calls are paid with a signed voucher and no new onchain transaction. It is offered on Base only, and only when the 402 lists it. Unused deposit returns to the wallet once the channel has been idle for 3 days, or withdraw it onchain (it completes one hour after the request).

Making an x402 call with curl (manual flow):

# Step 1: Send request without payment — get 402 with payment requirements
curl -s https://api.elfa.ai/x402/v2/aggregations/trending-tokens?timeWindow=24h

# Step 2: After signing the payment payload with your wallet, resend with payment header
curl -s -H "PAYMENT-SIGNATURE: <base64-encoded-payment-payload>" \
  "https://api.elfa.ai/x402/v2/aggregations/trending-tokens?timeWindow=24h"

Recommended: use the @x402/fetch library which handles payment automatically:

import { wrapFetchWithPayment } from "@x402/fetch";
import { ExactEvmScheme, toClientEvmSigner } from "@x402/evm";
import { x402Client } from "@x402/core/client";
import { createPublicClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { base } from "viem/chains";

const account = privateKeyToAccount("0xYOUR_PRIVATE_KEY");
const publicClient = createPublicClient({ chain: base, transport: http() });
const signer = toClientEvmSigner(account, publicClient);

const client = new x402Client().register(
  "eip155:8453",
  new ExactEvmScheme(signer));

const x402Fetch = wrapFetchWithPayment(fetch, client);

// Use x402Fetch exactly like regular fetch — payment is handled automatically on 402 responses
const response = await x402Fetch(
  "https://api.elfa.ai/x402/v2/aggregations/trending-tokens?timeWindow=24h");
const data = await response.json();

To pay on another network, swap the base chain import and register that scheme instead — e.g. Arbitrum (arbitrum from viem/chains, eip155:42161). Register a scheme for each network you want to pay from; the client uses the first one the server also accepts.

x402 with the Chat endpoint (POST):

const response = await x402Fetch(
  "https://api.elfa.ai/x402/v2/chat",
  {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      message: "What is the current sentiment on BTC?",
      analysisType: "chat",
      speed: "fast", // exact: "fast" $1, "expert" $2 — batch-settlement: up to $2 / $6
    }),
  });
const data = await response.json();
console.log(data.data.message);

Presenting results:

  • Parse the JSON response and present it in a clean, readable format.
  • For trending tokens: show a ranked table with token name, mention count, and change %.
  • For mentions: show tweet links, engagement metrics, and account info. Note: Elfa returns tweet IDs but not tweet text content — let the user know they'll need their own X (Twitter) API key to fetch the actual tweet content.
  • For narratives/summaries: present the narrative text with source links.
  • For the chat endpoint: display the AI response cleanly.
  • If the response contains an error, explain what went wrong and suggest fixes.
Step 2c: Market Intelligence — picking the right endpoint

The data endpoints split into two tiers. Picking the wrong tier is the most common integration mistake.

TierEndpointsWhat comes back
Measurementsmart-stats (legacy), trending-cas/*, trending-tokens, top-mentions, keyword-mentions, token-newsCounts, engagement metrics, links, account metadata. Your system interprets them.
Interpretationchat, chat/stream, event-summary, trending-narrativesWritten analysis or summaries, with source links where available.

Measurement endpoints return no raw tweet text and no sentiment field. They measure attention, not meaning. If the user needs post content, they must fetch the linked post with their own X API bearer token. If they need meaning, route to Chat, Event Summary, or Trending Narratives instead of trying to infer it from counts.

Choose the surface:

User needsStart here
What happened to an asset and how the story developed/v3/events, /v3/events/storylines (Step 2d)
Who is long or short, and whose calls to trust/v3/calls/positioning, /v3/calls/episodes, /v3/people/accounts?metric=alphaScore (Step 2d)
A market question answered in plain languagePOST /v2/chat
What is moving right nowtrending-tokens, trending-cas/*, or /v3/market/crypto/mindshare and /v3/market/crypto/trending-contracts (Step 2d)
Who amplified a token or keywordtop-mentions, keyword-mentions, smart-stats
A defensible cause or theme behind a movetoken-news, event-summary, trending-narratives
Continuous watching + notify/execute on a conditionAuto (Step 3)
Agent playbooks (endpoint chains)

Single calls answer questions; chains produce signals. These are the documented combinations:

PlayChainWhy it works
Lead timetrending-cas/telegram → trending-cas/twitterTelegram tends to lead; Twitter confirms. A CA in the first and not yet the second is early.
Trust-weighted mentionskeyword-mentions → smart-statskeyword-mentions yields account.username; smart-stats says whether that account has real reach.
Move + amplificationtrending-tokens → top-mentionsFind what moved, then see who drove it.
Theme → instrumenttrending-narratives → trending-tokensTurn a narrative into specific tickers to watch.
Move + causetoken-news → event-summaryAttach a written cause to a price/attention move.
Question + evidencechat → targeted data endpointChat gives the conclusion; the data endpoint gives numbers to verify it.

When ranking accounts from smart-stats, use ratios (smart followers ÷ followers, average reach ÷ followers), not absolute counts — the endpoint returns raw metrics, not a reputation score.

smart-stats is legacy and will be removed on 28 October 2026. It still works until then, and is not being extended before then. Chains that end in it keep working today, but build new integrations on keyword-mentions, which returns account.username alongside each mention's engagement metrics.

Chat — JSON vs streaming
SurfaceEndpointPlanUse when
API key JSONPOST /v2/chatGrow+ or PAYGClient cannot consume SSE, or wants one complete JSON answer
API key SSEPOST /v2/chat/streamPAYG or EnterpriseClient can consume SSE and wants incremental output
x402 JSONPOST /x402/v2/chatPay-per-requestWallet-based agent, no API key

Both surfaces take the same request body. analysisType decides the mode:

TypeUse forRequired fields
chatOpen-ended questionsmessage
macroBroad market conditionsnone
summaryFast pulse checknone
tokenIntroWhat a token is and its narrativeassetMetadata.symbol or assetMetadata.chain + assetMetadata.contractAddress
tokenAnalysisEntries, exits, market + on-chain contextsame as tokenIntro
accountAnalysisVet an account's reach and agendaassetMetadata.username

message is required only for analysisType: "chat" — the other modes use the mode plus assetMetadata and ignore free-form message. Omit sessionId to start a thread; pass the returned sessionId back to continue it.

Sources (opt-in): send includeSources: true to receive the links the research behind the answer read — data.sources on /v2/chat and x402 chat, sources on the final complete event of the stream. In the order read, without duplicates, at most 50; each has a url, a kind (x_post, x_account, telegram, dex, explorer, prediction_market, web — treat any other value as web) and an optional title. Links only, never page or post text. A source was read, not necessarily cited. The flag costs nothing extra.

Speed: fast (shallower), expert (default), and adaptive (Elfa chooses) — adaptive is available on /v2/chat but not on /x402/v2/chat.

SSE event types on /v2/chat/stream (each arrives as a data: payload with a type):

EventPayload
session_infosessionId, analysisType
titletitle — generated session/analysis title
textcontent — incremental Markdown chunk
text_completeincremental text finished
statusprogress update (e.g. "Searching mentions…")
creditscredits — credits consumed so far
completefinal sessionId, success, creditsConsumed, and sources when includeSources: true
invalid_requestquery rejected by safety checks
errorstream failure

A : keep-alive comment may arrive during long gaps. The stream terminates with data: [DONE]. Returns 403 if the key is not PAYG or Enterprise.

curl -sN -X POST "https://api.elfa.ai/v2/chat/stream" \
  -H "x-elfa-api-key: $ELFA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"analysisType":"chat","message":"Why is BTC moving today?","speed":"expert"}'
Health check

GET /v2/ping needs no authentication — use it to confirm reachability before debugging credential problems.

curl -s https://api.elfa.ai/v2/ping
Step 2d: API v3 — events, market, calls and people

Use API v3 when the user wants to know what happened and how a story developed, who is long or short an asset and whose calls to trust, which assets get the most attention, or which accounts are worth reading — across crypto and equities. Endpoints are listed in API v3 endpoints. Full docs: docs.elfa.ai/api/v3/overview.

API v3 needs ELFA_API_KEY with v3 access (see API v3 endpoints), and the Step 2a key rules apply. Without one, offer one, or fall back to event-summary (any key, or x402) or chat via x402. To check eligibility, read scopes on /v3/key-status, not plan: a Free key reads payg there, with no v3 scope. The two plan 403 messages are stable and tell the cases apart: The v3 API isn't included in this key's plan. … is a Free key (upgrade in the Developer Portal); API key does not have the required scope: … is a paid key without that endpoint (email [email protected] with the requestId).

Resolve first. Entity filters take an id, not a ticker. Resolve the asset, read its last day of events, then see how accounts are positioned on it. Check the resolved name: the first match may not be the asset you meant. from is required, in seconds.

ID=$(curl -s "https://api.elfa.ai/v3/entities?q=btc&entityType=token&limit=1" \
  -H "x-elfa-api-key: $ELFA_API_KEY" | jq -r '.entities[0].id')

FROM=$(( $(date +%s) - 86400 ))
curl -s "https://api.elfa.ai/v3/events?entityIds=$ID&from=$FROM&limit=10" \
  -H "x-elfa-api-key: $ELFA_API_KEY"

curl -s "https://api.elfa.ai/v3/calls/positioning?entityId=$ID" \
  -H "x-elfa-api-key: $ELFA_API_KEY"
  • /v3/entities matches exactly; it is not a search. A prefix or a misspelling returns nothing.
  • A ticker is not unique: take the first row and check its name, or narrow with entityType (token, equity, project, person, organisation).
  • Forms of an asset are grouped into one family id where known: WBTC and cbBTC are Bitcoin, and a tokenised equity resolves to the equity. Not every form is grouped yet, so check name.
  • A person is not an account. Events filter on the person's entity id; calls and episodes take an X handle.

Rules every v3 endpoint follows:

RuleIn short
TimesUnix seconds, UTC, in requests and responses. Milliseconds are a 400: divide by 1000
Time rangesfrom is required, to defaults to now, both inclusive. No from is needed when fetching by id or searching with q; entities, arcs, positioning and top accounts take no range at all, and mindshare takes a period
History30 days (PAYG), 60 days (Grow) or 2 years (Enterprise) back. An earlier from is cut, and servedFrom says where; a to before it is a 400. /v3/key-status returns the start as historyFrom
PagingPass nextCursor back as cursor. hasMore: false marks the last page. No offset, no total. Change anything but limit and the cursor is a 400
ListsParameters marked a list take several values, repeated or comma-separated. At most 30 ids
By ideventIds, storylineIds, arcIds, callIds and episodeIds fetch up to 30, with no from
ErrorsA bad or unknown parameter is a 400 naming the field in details, and an unknown id in a filter is a 400, Unknown id: …. Unknown ids in an *Ids fetch are skipped. Nothing matching is a 200 with an empty list
ResponsesThe list is top-level and named for what it holds (events, entities, bars, …), with no data wrapper
Rate limitsPer key, shared with /v2. On 429, wait RateLimit-Reset seconds

No v3 endpoint returns post text. Events carry a written title and summary, and link to each post behind them.

What agents get wrong:

  • Events. sort=relevant ranks the last 24 hours and refuses from, to, eventIds, arcId, eventClass, eventType, implication, novelty and order=asc; assetClass, alertAction, hasStoryline, notable and entityIds (tokens and equities only) still filter. q searches events, storylines and arcs by topic, best match first. impacts is empty on most events; movedFirst or marketWide set to true means the move probably has another cause. novelty=new does not show each story once: keep one event per storylineId for that. A 503 with q means search is down: retry, or drop q.
  • Storylines. Prices and levels in title and summary are as of lastMaterialAt, not now.
  • Call books. Read the lean through crowdingPercentile, not longShare: the accounts lean long almost always. The newest row is a live tick (live: true), rewritten every five minutes.
  • Trending contracts. mentionCount counts posts, not accounts. A range over 30 days is cut to 30.
  • Calls. handle takes X accounts only; Telegram channels cannot be filtered on yet. /v3/calls/episodes?status=open&from=<now> reads what every matching account holds right now. Positioning takes one entityId, singular.
  • Top accounts. Standing (the default metric) measures attention, not accuracy. To rank callers by how their calls went, pass metric=alphaScore (return against the market, BTC for crypto and SPY for equities, and outright), hitRate or avgReturn. For "best callers" use alphaScore: avgReturn is not adjusted for the market, so a few big wins or a rally can top it. Track metrics rank only accounts with at least 20 scored positions, over every position since late June 2026, refreshed daily. Quote the number you ranked by (track.alphaScore and so on) with track.scoredEpisodes.

To be alerted when a new event matches your filters instead of polling, use an Auto events condition (Step 3); for one account's new calls, a calls condition; for an asset's mindshare share, rank or hot flag, a mindshare condition. Per-family docs: Entities, Events, Market, Calls, People.

Step 3: Auto — Condition Engine and Trigger Pipeline

Auto is a managed condition engine + trigger pipeline for agents. You describe what to watch for (price, technical indicators, LLM-evaluated conditions, scheduled checks, prediction-market activity, funding rates, liquidation cascades, market sentiment, social attention on an asset, one trader's calls), and Auto evaluates continuously and fires actions when conditions resolve to true. Assets are not crypto-only — HIP-3 perps cover equities, indices, commodities, FX, and pre-IPO names, 24/7.

Full Auto docs: docs.elfa.ai/auto/overview

Lifecycle Sequence (Enforced)

For API key lifecycle/cleanup calls, preserve this order when each operation applies:

  1. POST /v2/auto/queries/validate — validate EQL and preview cost
  2. POST /v2/auto/queries — create and activate
  3. POST /v2/auto/queries/{queryId}/cancel — only if stopping an active query before it reaches terminal status (returns 409 once terminal)
  4. DELETE /v2/auto/queries/{queryId} — only after status is terminal (triggered / expired / cancelled / failed); active queries must be cancelled first

Cancel and delete are distinct operations. POST /cancel flips an active query to cancelled (terminal). DELETE removes the record entirely and only works on terminal queries. Sending DELETE on an active query returns 409 Conflict.

x402 mode supports the same lifecycle except that x402 has no DELETE endpoint (cancel-only).

Intent Routing (Strict)

Pick the condition source by user intent before writing condition args:

IntentRequired sourceMinimum required fields
Account-anchored post intent (@user posts ...)source: "tweet"args.username (no @), args.text, args.minConfidence (use 80 if user gives no threshold)
Telegram-channel intent (when this channel posts ...)source: "telegram"one of args.chatUsername or args.chatId, plus args.text, args.minConfidence (use 80 if user gives no threshold)
News or world-event intent (ETF approval, exploit, listing, bearish news on an asset)source: "events"at least one filter (args.entity, args.eventType, args.implication, …) or args.text + args.minConfidence — see Events below
Official SEC filing intent (new 8-K, late 10-Q/10-K notice, offering, ownership filing, 8-K item)source: "sec"method (e.g. base_form_type, form_category, has_item_1_05), args.ticker as the issuer CIK, operator/value per the per-method allowlists
Prediction-market move/lifecycle on a named open Kalshi marketsource: "kalshi"method (e.g. yes_price, status, result), args.ticker (a currently-open Kalshi market), operator/value per the per-method allowlists
Prediction-market price/trade on a Polymarket outcome tokensource: "polymarket"method (price, bid, ask, size, side), args.ticker (outcome-token asset_id), operator/value per the per-method allowlists
Perp funding-rate intent (overheated funding, funding flips negative)source: "funding"method (prefer annualized_rate), args.ticker as SYMBOL:EXCHANGE (e.g. BTC:BINANCE)
Liquidation-flow intent (cascade, long/short flush)source: "liquidation"method (e.g. total_usd_5m, total_pct_oi_1h), args.ticker as SYMBOL:EXCHANGE
One X account's trading calls (@user opens a long, @user exits BTC)source: "calls"args.handle (no @); optional args.asset, args.direction, args.action — see Calls below
Social-attention intent on one token or stock (rises above a share, enters the top N, turns hot)source: "mindshare"method (e.g. share_24h, rank_24h, hot_4h), args.asset (ticker, name or id) and args.market when known
Market-wide sentiment (fear/greed regime)source: "fear_greed"method (value or classification), empty args: {}
Follow-graph intent (who followed a watched account, who it followed, or a tracked set converging on one account)source: "x_follow_received" / "x_follow_made" / "x_follow_overlap"method per source, args.account (or args.accounts for overlap), operator > or >=
Real-world catalyst moving an equity/index/commodity (rate decision, CPI, earnings)the catalyst's own source (kalshi / polymarket / events / price), optionally with a HIP-3 price / ta confirmationPick the catalyst source, then bridge to the asset class it moves — see Catalyst Triggers
Fuzzy world-state predicate not naturally expressible as a post or eventsource: "llm"method: "athena_condition", args.query, args.period (>= 1h)

When the prompt is account-anchored, start with tweet — do not route to events or llm first — unless it asks for that account's trading calls (@user opens a long, @user exits BTC), which route to calls. When it is Telegram-channel-anchored, start with telegram. When the prompt is news about a subject without a specific account, start with events — unless it is an official SEC filing for an issuer, which routes to sec. When the trigger maps to a concrete prediction market you can name (a Kalshi ticker or a Polymarket outcome-token id), use kalshi / polymarket (prefer them over llm for supported methods). Use llm (athena_condition) only when the predicate cannot reasonably be matched against a post, event, or named prediction market.

When to suggest Auto
  • User wants alerts based on price thresholds ("alert me when BTC crosses 100k")
  • User wants alerts based on technical indicators ("notify when RSI drops below 30")
  • User wants scheduled checks ("check every 4 hours") or calendar schedules ("every weekday at 9am New York time" — use cron.schedule)
  • User wants the same alert to keep firing on its own condition ("notify me every time BTC dips below 60k") — add the top-level repeat object (cooldown + maxTriggers)
  • User wants narrative/sentiment monitoring ("alert when AI token narrative shifts")
  • User wants multi-condition triggers ("BTC above 100k AND ETH above 3500")
  • User wants to compare live metrics ("alert when price crosses above Bollinger Band")
  • User wants LLM analysis on trigger ("when it triggers, run a full analysis")
  • User wants account-anchored social triggers ("notify me when @cz_binance posts that Binance Alpha is listing a new token") — use Signal: X/Twitter Post (source: "tweet")
  • User wants Telegram-channel triggers ("alert me when this channel posts a new Binance listing rumor") — use Signal: Telegram Channel (source: "telegram")
  • User wants news triggers ("alert me when SEC approves a spot ETH ETF", "bearish news on ETH", "any exchange hack") — use Events (source: "events")
  • User wants official SEC filing triggers ("tell me when Alphabet files a new 8-K", "alert on any late 10-Q notice") — use source: "sec" with the issuer CIK
  • User wants prediction-market triggers ("alert when this Kalshi market's YES probability crosses 60%", "notify when the market settles YES", "alert when this Polymarket outcome trades above 60c") — use Prediction Markets (source: "kalshi" or source: "polymarket")
  • User wants funding / liquidation / sentiment triggers ("alert when BTC funding flips negative", "notify on an ETH liquidation cascade", "alert when Fear & Greed drops below 20") — use source: "funding" / "liquidation" / "fear_greed"
  • User wants follow-graph triggers ("tell me when someone big follows @elfa_ai", "alert when cobie follows a new account", "notify when 3 funds I track all follow the same account") — use source: "x_follow_received" / "x_follow_made" / "x_follow_overlap"
  • User wants to follow one trader's calls ("ping me every time @3PeaksTrading opens a long", "tell me when this trader closes or is stopped out of BTC") — use Calls (source: "calls")
  • User wants attention triggers on a token or stock ("alert me when SOL turns hot on mindshare", "when ETH enters the top 10 by mindshare") — use Mindshare (source: "mindshare")
  • User wants to watch a macro catalyst on stocks/indices/commodities ("tell me when the market prices a Fed cut", "alert on gold if CPI runs hot") — fire on the catalyst source and reference the HIP-3 perp, which trades 24/7. See Catalyst Triggers
Auto access models
ModeRoute prefixAuthBest for
API key/v2/auto/*x-elfa-api-key on all routesApps, dashboards
x402 keyless/x402/v2/auto/*x402 payment + x-elfa-agent-secretAI agents, bots
x402 Auto (keyless agent mode)

For x402 Auto, no API key is needed. Instead:

  • Send the x402 payment header PAYMENT-SIGNATURE. x402 v2 only — X-PAYMENT is accepted as an alias for the header name, but the payload must be a v2 payment payload
  • Include x-elfa-agent-secret on all query lifecycle routes

Agent secret management: Generate a strong secret once and reuse it for all calls:

openssl rand -hex 32
# or: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"

Persist as ELFA_AGENT_SECRET. Do not rotate per request — x402 session ownership is derived from SHA256(secret). If you change secrets, your agent identity changes and existing queries/sessions may become inaccessible.

Auto pricing (both modes)

API-key mode (/v2/auto/*) — charged against your credit balance:

OperationCreditsNotes
POST /v2/auto/chat (Builder Chat)1 + dynamicBase 1 credit + ceil(request_cost * 750) dynamic charge based on LLM usage
POST /v2/auto/queries (Create)Simulation-drivenBaseline 5 + per simulated LLM call: fast +5, expert +18
POST /v2/auto/queries/validateFreeReturns cost estimate — always call before Create
Per-post X account (tweet on an account outside Elfa's real-time set)creditsPerPost (1) per post while the plan runsNot in estimatedCredits or x-elfa-credits; see tweetAccounts on Validate
Per-day follow account (x_follow_made / x_follow_overlap on an account Elfa doesn't monitor)creditsPerDay (1) per UTC day its follows are readNot in estimatedCredits or x-elfa-credits; see followAccounts on Validate
GET /v2/auto/queries/* (list, poll, stream, sessions)Free
POST /v2/auto/queries/:queryId/cancel (Cancel active query)Free
DELETE /v2/auto/queries/:queryId (Delete terminal query)Free
GET /v2/auto/validate-symbol/:exchange/:symbolFree

Reference USD values for Create: baseline $0.0725, fast call +$0.0725, expert call +$0.261. Use /queries/validate to preview exact cost before committing.

x402 mode (/x402/v2/auto/*) — pay-per-request in USDC on Base, Arbitrum, Polygon, Avalanche, or Solana:

Builder Chat is speed-based with two schemes — a flat exact price, or, on Base when the 402 lists it, a prepaid batch-settlement channel charged only what the turn used, up to the cap:

Speedexact (flat)batch-settlement (charged up to)
fast$1$2
expert (default)$2$6
OperationCreditsUSDC Cost
Query creation — baseline5$0.0725
Per fast LLM call+5+$0.0725
Per expert LLM call+18+$0.261
Validate, poll, cancel, sessions, streamFreeFree

x402 Auto example:

// Validate a query (x402 Auto)
const response = await x402Fetch(
  "https://api.elfa.ai/x402/v2/auto/queries/validate",
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "x-elfa-agent-secret": process.env.ELFA_AGENT_SECRET,
    },
    body: JSON.stringify({
      query: {
        conditions: { AND: [{ source: "price", method: "current", args: { symbol: "BTC", exchange: "hyperliquid" }, operator: ">", value: 100000 }] },
        actions: [{ stepId: "step_1", type: "notify", params: { message: "BTC crossed 100k" } }],
        expiresIn: "24h"
      }
    }),
  });
Recommended call sequence (deterministic agent path)

API key mode (/v2/auto/*):

  1. POST /v2/auto/chat — Ask Builder Chat to draft a query
  2. POST /v2/auto/queries/validate — Validate EQL and preview cost
  3. POST /v2/auto/queries — Create and activate
  4. GET /v2/auto/queries/{queryId}/stream — Stream notifications (or poll)
  5. GET /v2/auto/queries/{queryId}/sessions + /sessions/{sessionId} — Fetch LLM output (if using llm action)
  6. (Optional cleanup) POST /v2/auto/queries/{queryId}/cancel — Cancel only while active (returns 409 if already terminal)
  7. (Optional cleanup) DELETE /v2/auto/queries/{queryId} — Delete only after terminal (triggered / expired / cancelled / failed); rejects active queries with 409

x402 mode (/x402/v2/auto/*):

  1. POST /x402/v2/auto/chat — Ask Builder Chat to draft a query
  2. POST /x402/v2/auto/queries/validate — Validate EQL and preview cost
  3. POST /x402/v2/auto/queries — Create and activate
  4. GET /x402/v2/auto/queries/{queryId}/stream — Stream notifications (or poll via POST)
  5. POST /x402/v2/auto/queries/{queryId}/sessions + /sessions/{sessionId} — Fetch LLM output
  6. (Optional cleanup) POST /x402/v2/auto/queries/{queryId}/cancel — Cancel only while active. (x402 has no terminal-delete endpoint.)

Always validate before create. Validate returns structured errors you can iterate on without spending credits.

Failure handling order (apply in sequence):

  1. Retry transient network errors with exponential backoff. If 5xx responses persist across endpoints after a few retries, check status.elfa.ai before debugging further (https://status.elfa.ai/status.json serves the same data to scripts; https://status.elfa.ai/feed.xml announces outages and recoveries).
  2. On 400 / 422 validation failure → repair query using Validation Errors table, re-validate.
  3. On 401 / 403 auth failure → refresh credentials or re-check the key's permissions.
  4. On 402 x402 payment failure → re-price and retry with valid payment payload.
  5. On 410 (SSE stream closed) → re-open stream or fall back to polling.

Common agent flows:

Poll-based LLM flow:

POST /v2/auto/queries                               → create query with action.type = "llm"
GET  /v2/auto/queries/{queryId}                     → poll until execution with sessionId appears
                                                      (latestEvaluation.conditionStates[] shows
                                                       progress while you wait)
GET  /v2/auto/queries/{queryId}/sessions/{sessionId} → fetch full analysis

Webhook-based LLM flow:

POST /v2/auto/queries                               → create with action.type = "llm" and params.action
(wait for webhook)                                  → receive session reference / output
(optional) GET session fetch                        → /v2/auto/queries/{queryId}/sessions/{sessionId}
Builder Chat

Builder Chat (POST /v2/auto/chat or POST /x402/v2/auto/chat) uses AI to translate natural language into EQL queries. Use sessionId for multi-turn conversations.

{
  "message": "Alert me when BTC breaks 100k with RSI confirmation above 55",
  "speed": "expert",
  "sessionId": "optional-session-id"
}

Response (API-key mode):

{
  "sessionId": "session-uuid",
  "response": "I can help with that... (markdown + EQL JSON code block)",
  "title": "BTC Breakout Alert",
  "reasoning": null,
  "planIds": [],
  "credits": 100
}

credits carries what the turn cost — the same total as the x-elfa-credits header. Builder Chat is dynamically priced, so read it instead of assuming a flat per-call cost.

The response message contains the AI's reply in Markdown. When it generates EQL, it will be in a JSON code block — extract, validate via /queries/validate, then submit via /queries.

Prompting tips for Builder Chat:

  • Include title and description (shown in notifications so recipients know what fired hours/days later)
  • Specify symbols, timeframe, trigger behavior (one-time — the default — vs recurring), delivery target. For recurring on the same condition, ask for the repeat object (cooldown + maxTriggers); for a fixed schedule, use a cron condition
  • For Signal triggers, give a factual match description (avoid vague phrasing like "bullish vibes")
  • Append "If anything is unsupported, return the closest supported query and list substitutions" to handle edge cases gracefully
  • Prefer expiresIn of 24h–3d for fresh signals
  • Persist sessionId and reuse it for follow-up prompts so the model keeps context across turns

High-impact prompt pack — drop these into POST /v2/auto/chat as the message field:

1) Complex TA breakout with direction filter:

Build an Auto query:
- title + description: short human-readable summary and 1-2 sentence thesis
- symbols: BTC, ETH, SOL
- timeframe: 5m
- trigger when price breaks previous 1h range high or low
- confirm direction with RSI(14): >55 for upside, <45 for downside
- actions: telegram alert + webhook to https://your-runner.example/auto/events
- one-time trigger, expires in 48h
If anything is unsupported, return the closest supported query and list substitutions.

2) CEX + DEX monitoring pack:

Build an Auto query pack for supported CEX + DEX symbols:
- title + description per query: short summary and thesis sentence
- watchlist: WBTC, ETH, SOL, HYPE
- trigger when 15m volume surge aligns with price momentum
- action: webhook to https://your-runner.example/auto/events
- include symbol and trigger summary in payload
- expires in 24h
If any symbol/source is unsupported, skip it and report skipped items.

3) News + X sentiment context on trigger:

Build an Auto query:
- title + description: short summary and the thesis behind watching this
- monitor BTC and ETH for abnormal 1h move + volume confirmation
- on trigger run llm action that adds:
  - latest market news context
  - X sentiment summary
  - risk note
- also send telegram alert with a short summary
- expires in 24h

4) Prediction-market thesis watcher (Kalshi):

Build an Auto query:
- title + description: name the market thesis and the move you're watching for
- source: kalshi
- ticker: <a confirmed-open Kalshi market ticker, e.g. KXBTC-26APR0803-T77799.99>
- trigger when the YES implied probability crosses above 0.6
- action: llm to produce a catalyst hypothesis + invalidation level
- deliver to webhook: https://your-runner.example/auto/events
- include decision priority: high/medium/low
- expires in 2d
If the ticker is not open or anything is unsupported, return the closest supported query and list substitutions.

5) Portfolio risk guardrail:

Build an Auto query:
- title + description: portfolio guardrail label and the risk scenario it covers
- watch my portfolio symbols: BTC, ETH, SOL, HYPE
- trigger on downside acceleration and momentum weakness
- action: telegram alert with severity and suggested next check
- recurring checks, expires in 3d

6) Agent handoff with strict follow-up contract:

Build an Auto query:
- title + description: breakout playbook name and the follow-up intent
- trigger on breakout + trend-confirmation conditions
- action: webhook to https://your-runner.example/auto/events
- include fields: eventId, symbol, triggerReason, priority, queryId
- downstream agent decides next action under policy constraints
- expires in 24h

7) Signal — account-anchored X/Twitter Post watcher:

Build an Auto query:
- title + description: short summary anchored to the account + intent
- use Signal category:
  - condition source: tweet
  - username: cz_binance (no leading @)
  - match description: "Binance Alpha is listing a new token"
  - minConfidence: 80
- action: notify (or telegram_bot) with a concise message
- expires in 24h
If unsupported, return closest supported query and list substitutions.

8) Signal — event-first catalyst watcher:

Build an Auto query:
- title + description: catalyst name and why it matters now
- use an events condition:
  - condition source: events
  - filters: entity ETH, eventType regulatory
  - claim: "the SEC approves a spot ETH ETF"
  - minConfidence: 80
- action: webhook to https://your-runner.example/auto/events
- include queryId, eventId, and short trigger reason in payload
- expires in 24h
Query model (EQL)

A query contains conditions, actions, and expiresIn (plus an optional repeat sibling — by default a plan is one-shot; see Repeat below):

{
  "title": "BTC RSI oversold on 1h",
  "description": "Mean-reversion entry: if BTC 1h RSI dips under 30, consider scaling in.",
  "conditions": {
    "AND": [
      {
        "source": "ta",
        "method": "rsi",
        "args": { "symbol": "BTC", "timeframe": "1h", "period": 14, "exchange": "hyperliquid" },
        "operator": "<",
        "value": 30
      }
    ]
  },
  "actions": [
    { "stepId": "step_1", "type": "webhook", "params": { "url": "https://your-endpoint.example/events" } }
  ],
  "expiresIn": "24h"
}

Condition rules:

  • Root group must be AND or OR
  • Nest groups up to depth 3, max 10 leaf conditions
  • Multi-symbol: a single query can require BTC AND ETH AND SOL conditions jointly

Allowed expiresIn values: 1h, 2h, 4h, 8h, 12h, 24h, 48h (2d), 72h (3d), 120h (5d), 168h (7d), 240h (10d), 336h (14d), 504h (21d), 720h (30d — hard max). Prefer 24h–72h — signals decay and short windows force re-evaluation. Only reach for 240h–720h on slow structural theses (token unlocks, options expiries, monthly copy-trading), never on short-timeframe TA/scalps.

Plan limits (active plans per account): an account can only hold so many active plans at once, by API key tier — Free 2, Grow 50, PAYG/Enterprise unlimited. A hard ceiling of 500 active plans applies regardless of tier; exceeding either cap returns 409. Only active plans count — expired/cancelled/terminal plans free capacity, so cancelling stale plans (or choosing a shorter expiresIn) is the fastest way to make room.

Repeat (repeat) — recurring plans on their own conditions:

By default a plan is one-shot: it fires once and moves to a terminal state. The optional top-level repeat object (a sibling of expiresIn) keeps a plan active and makes it re-fire on its own conditions — the right tool for "notify me every time BTC dips below 60k" without a fixed cron cadence.

{
  "conditions": { "AND": ["..."] },
  "actions": ["..."],
  "expiresIn": "7d",
  "repeat": { "cooldown": "1h", "maxTriggers": 10 }
}

Both fields are mandatory when repeat is present:

FieldTypeMeaning
cooldownstringMinimum time between fires. "0" disables the rate limit.
maxTriggersnumberLifetime cap on fires (1–1000); reaching it makes the plan terminal.
  • cooldown allowed values (its own set — sub-hour is permitted, unlike the cron/llm 1h minimum): 1m, 5m, 15m, 30m, 1h, 2h, 4h, 8h, 12h, 24h, 1d, 7d, plus "0". Use "0" for "fire on every distinct event" (e.g. every matching tweet or Telegram message).
  • maxTriggers counts how many times the plan fires over its lifetime (distinct from the number of action steps). Sanity-check cooldown × maxTriggers against expiresIn — if there isn't enough runway, the plan expires before reaching the cap. A longer expiresIn buys patience through quiet periods; it does not raise the fire cap.
  • Single reset rule. A repeat plan re-fires only once its triggering signal has cleared. Level conditions (price / ta / llm-boolean) disarm on fire and re-arm when the whole condition tree next evaluates false; event conditions (tweet.semantic / telegram.semantic / events.match / sec) consume the matched event and re-fire on the next distinct event.

Still rejected: repeat combined with a recurring cron condition (cron.every / cron.schedule) — both provide recurrence, so combining them fails validation as EQL_INVALID_REPEAT. For a fixed-cadence recurring plan, use the cron condition on its own; for a condition-driven recurring plan, use repeat.

Avoiding over-triggering: the main risk is a condition that keeps flipping true — a metric flapping around its threshold, or a chatty account. cooldown is your rate-limit floor (never use "0" on level/metric conditions); maxTriggers is your hard circuit breaker (set it conservatively so a runaway condition self-limits). A level condition like price < 60000 fires on every re-crossing, so prefer transition operators (crosses_below / crosses_above over < / >), give the threshold room from the current value, match cooldown to the signal's timeframe, and start conservative then loosen.

Recommended starting points:

Condition typecooldownmaxTriggersNotes
Price / TA level (price, ta)1h–4h (≥ your timeframe)3–10Prefer crosses_* operators; avoid "0" here.
LLM predicate (llm.athena_condition)≥ period (min 1h)3–5The LLM already re-checks on period — don't re-alert faster than it re-evaluates.
Event — single / quiet account or channel (tweet, telegram)0–15m10–20Each distinct post/message is a discrete event; "0" is fine unless the source is chatty.
Event — noisy account/channel or broad events filters15m–1h10–20A small cooldown collapses a burst of mentions, or follow-up events on one story, into one alert.
SEC filing event (sec)0–1h10–20Use "0" when you want every matching filing; add cooldown for noisy form categories.
Liquidation cascade (liquidation)1h+5–10Windows decay to zero, so the plan re-arms per cascade; the cooldown stops one long cascade re-alerting.

Allowed action types: webhook, notify, telegram_bot, llm. The actions array is exactly one step per query — run standalone LLM work with params.action; chain follow-up work via llm action with params.callback.action, or have your runner fan out from the trigger event.

Action params shape (key fields):

typeRequired paramsOptional params
notifymessage (1–1000 chars)—
webhookurl (https only, allowlisted host)signingSecret (write-only — set this in production), allNotifications (default false)
telegram_botbotToken, chatIdallNotifications (default false)
llmaction ∈ chat / summary / macro / accountAnalysis / tokenDiscovery / tokenAnalysiscallback.action for chained follow-up (a callback webhook also takes includeAnalysis), speed (fast / expert), per-action extras (for example message on chat)

telegram_bot does not take a message field — the message body is auto-composed from the query title + description + trigger context. Use notify (in-app push) when you want to specify the message text yourself. allNotifications: true on webhook / telegram_bot opts the destination into lifecycle notifications (failed/expired/run-failed) in addition to the trigger fire.

Triggers — condition sources

Market-data venue (exchange) — REQUIRED on every price and ta condition:

Every price and ta condition must include an exchange arg that selects which venue's market data backs the condition. Allowed values: hyperliquid, binance (exact lowercase enum). gmx is no longer accepted: create and validate reject it with EQL_INVALID_ARG_VALUE. Drafts still store a gmx condition, and convert rejects it with EQL_VALIDATION_ERROR. This arg only affects the data source of the condition. A price/ta condition without exchange fails validation.

Per-venue data notes:

  • binance reads its USD-M perp markets and uses Binance's own base-symbol convention (1000PEPE, not kPEPE), validated against Binance's live perp catalog at create time.
{ "source": "price", "method": "current", "args": { "symbol": "BTC", "exchange": "hyperliquid" }, "operator": ">", "value": 100000 }

Price source (price):

MethodArgsReturnsDescription
currentsymbol, exchangenumberCurrent price
changesymbol, exchange, periodnumber% change over period
highsymbol, exchange, periodnumberHigh in period
lowsymbol, exchange, periodnumberLow in period
volumesymbol, exchange, periodnumberVolume (USD) over period

TA source (ta) — technical indicators: every method also requires symbol, timeframe, and exchange.

MethodRequired argsOptional argsReturns
rsisymbol, timeframe, exchangeperiod (default 14)RSI (0-100)
macd_valuesymbol, timeframe, exchange—MACD line
macd_signalsymbol, timeframe, exchange—MACD signal line
macd_histogramsymbol, timeframe, exchange—MACD histogram
bbands_uppersymbol, timeframe, exchangeperiod (default 20)Upper Bollinger Band
bbands_middlesymbol, timeframe, exchangeperiod (default 20)Middle Bollinger Band
bbands_lowersymbol, timeframe, exchangeperiod (default 20)Lower Bollinger Band
emasymbol, timeframe, exchange, period—Exponential MA
smasymbol, timeframe, exchange, period—Simple MA
atrsymbol, timeframe, exchangeperiod (default 14)Average True Range
stoch_ksymbol, timeframe, exchange—Stochastic %K
stoch_dsymbol, timeframe, exchange—Stochastic %D
ccisymbol, timeframe, exchangeperiod (default 20)CCI
willrsymbol, timeframe, exchangeperiod (default 14)Williams %R

TA critical rules:

  • Every ta (and price) condition requires exchange (hyperliquid or binance)
  • ema and sma require period — it is NOT optional
  • rsi, bbands_*, atr, cci, and willr accept optional period with documented defaults above
  • period must be a JSON number (14), not a string ("14")
  • Use period not length — length is not a recognized alias
  • timeframe values: 1m, 5m, 15m, 30m, 1h, 2h, 4h, 8h, 12h, 1d

Cron source (cron):

MethodArgsDescription
onceperiodTrue on first due evaluation at/after creation + period
onceRemainTrueperiodTrue on first due evaluation and stays true
everyperiodTrue at each period interval
scheduleexpression, timezone?True whenever the 5-field cron expression matches in timezone

once, onceRemainTrue, and every are interval methods measured from query creation. schedule is a calendar/wall-clock method:

  • expression is a classic 5-field cron string (minute hour day-of-month month day-of-week) — e.g. 0 9 * * 1-5 (weekdays at 09:00).
  • timezone is optional and defaults to UTC. When set it must be a valid IANA name (e.g. America/New_York, Asia/Singapore) so daylight-saving transitions are handled correctly. Fixed-offset strings like +02:00 are not accepted.
  • schedule enforces the same 1h-minimum cadence as the other cron methods: the minute field must be a single fixed value (0–59). Wildcards, steps, lists, or ranges in the minute field are rejected.
    • Allowed: 7 * * * * (hourly at minute 7), 0 9 * * 1-5 (weekdays 09:00), 30 8 * * * (daily 08:30).
    • Not allowed: * * * * * (every minute), */15 * * * * (every 15 min), 0,30 * * * * (twice per hour).
  • schedule takes no period arg.

Builder Chat support pending: cron.schedule is available on the direct EQL/API path (Validate / Create). Builder Chat does not yet reliably generate cron.schedule — author calendar schedules directly and confirm with /v2/auto/queries/validate.

LLM source (llm):

MethodArgsDescription
athena_conditionquery, period, speed?LLM-evaluated condition (natural language)

Signal source — X/Twitter Post (tweet):

In the Builder Chat catalog this is the Signal category, X/Twitter Post.

MethodRequired argsReturnsDescription
semanticusername, text, minConfidencebooleanMatches posts from a specific X/Twitter account when semantic confidence meets threshold

Rules:

  • username must be passed without @ (e.g. cz_binance, not @cz_binance).
  • username can be any public X account. Validate's tweetAccounts says how each is billed: included (Elfa's real-time set) costs nothing extra; per_post costs creditsPerPost for every post while the plan runs (posts, quotes, notes and articles; not replies or reposts), matched or not, and is not in estimatedCredits. Tell the user before Create. A key without a paid API plan, and x402, can watch only included accounts; a rejected account is an errors[] entry (see Validation Errors). Builder Chat drafts tweet conditions only for included accounts: write any other yourself. Details: X Accounts and Per-Post Billing.
  • minConfidence must be a JSON integer between 0 and 100; use 80 when the user gives no threshold.
  • method, operator, and value are auto-filled defaults (semantic, ==, true) — do not include them in the condition JSON.
{
  "source": "tweet",
  "args": {
    "username": "cz_binance",
    "text": "Binance Alpha is listing a new token",
    "minConfidence": 80
  }
}

Signal source — Telegram Channel (telegram):

In the Builder Chat catalog this is the Signal category, Telegram Channel.

MethodRequired argsReturnsDescription
semanticchatUsername and/or chatId, text, minConfidencebooleanMatches messages from a monitored public Telegram channel when semantic confidence meets threshold

Rules:

  • Provide chatUsername, chatId, or both on the full method form. The shorthand condition form requires exactly one identifier; if both are present on a full condition they must resolve to the same channel.
  • Prefer chatUsername without @ or t.me/ when you know the public channel username.
  • Use chatId only when it came from tooling or an existing plan; it must be a signed 64-bit integer string.
  • The channel must resolve to a monitored public Telegram channel at create-time, otherwise validation/create fails.
  • minConfidence must be a JSON integer between 0 and 100; use 80 when the user gives no threshold.
{
  "source": "telegram",
  "args": {
    "chatUsername": "<monitored_public_channel_username>",
    "text": "new Binance listing rumor",
    "minConfidence": 80
  }
}

Events (events):

In the Builder Chat catalog this is the Signal category, Events.

Triggers on events: news about an asset, project, company, person, or macro — listings, hacks, regulatory decisions, macro releases, company news, large flows, bullish or bearish news. These are the events GET /v3/events (API v3) serves, each written up from many posts, and the condition takes that endpoint's filter values, matched the same way. Use it for news about a subject; use tweet for posts from an account. Not yet in the app's visual builder catalog; Builder Chat and the API both author it. Full reference: https://docs.elfa.ai/auto/events — the events themselves: https://docs.elfa.ai/api/v3/events

MethodArgs (all optional; at least one filter or text)Returns
matchentity, entityType, eventClass, eventType, implication, alertAction, assetClass, novelty, relation, storylineId, text + minConfidenceboolean
  • entity: one id from GET /v3/entities (exact: the same id /v3/events takes in entityIds), or one ticker or name ("ETH", "Hyperliquid"). A ticker or name resolves ticker first: "Elon Musk" resolves to a token named MUSK and "Fed" to a stock with the ticker FED, so set entityType (token, equity, project, person, organisation) for anything that is not a token or a stock. Central banks, regulators, and macro themes are not entities — use eventType + text.
  • eventClass: market (news, including about one asset) or call (trader positioning). For one asset use entity, not asset.
  • eventType: security, listing, regulatory, macro, protocol, company, marketFlow, price, social, other.
  • implication: bullish, bearish, mixed, unclear — the likely direction for the assets named. Put stance here, never in text.
  • alertAction: alert (judged impactful) or monitor (worth watching). assetClass: crypto or equity (events that name no asset pass). novelty: new or update — does not reliably filter repeats: most later readings of a story still say new; use relation.
  • relation: where the event sits in its story — origin, update, confirmation, contradiction, mitigation, consequence, reaction, context. origin is the event that started a story (a story that grew out of an earlier one is a story of its own, so its first event is origin too): add relation: "origin" for one alert per story. About 6 in 10 events start a story, so origin keeps more than half of what the plan would fire on and drops the follow-ups; with repeat, the plan fires once per new story.
  • storylineId: the later events of one story. Take the id from the API, never compose it: an event's storylineId in /v3/events, or storylines[0].id from GET /v3/events/storylines?q=<topic>. Validate and Create reject an id that names no storyline. Most stories never get a second event, so follow one that is still developing; add relation to narrow it (e.g. contradiction for reports that dispute the story so far). A story that grows out of the followed one has its own id and is not followed.
  • Call events, and the few other events not threaded into a story, match neither relation nor storylineId.
  • text + minConfidence (both or neither, start at 80): one specific claim, checked against the event's title and summary. Filter-only conditions consume no credits.
  • One value per argument; several assets or types → several events conditions under OR. An array is rejected with EQL_INVALID_JSON.
  • Shorthand only in practice: the full form takes just operator: "==", value: true. No dynamic values.
  • Runtime: events arrive minutes to hours after the first post (to be first, watch the account with tweet); only events less than an hour old are matched; each event fires a plan at most once; about 1 in 9 events continues a story already reported that day. To fire once per story, add relation: "origin", or use repeat.cooldown to space alerts out.
  • Preview before creating: GET /v3/events with the same filters and from=<now - 1d> (entityIds=<id> for entity) returns what the condition would have fired on. q has no counterpart: text checks a claim per event instead. GET /v3/events/storylines?storylineIds=<id> maps to "storylineId": "<id>", one storyline per condition. One value per argument, no time range. Mapping: https://docs.elfa.ai/auto/events#from-v3-events
  • match in the trigger payload: eventId, title (the event's alertTitle), eventClass, eventType, implication, alertAction, novelty, timeSensitivity, citedSourceCount, analyzedAt (ISO 8601), entities[] (symbol, name, entityType; up to 10), and confidence when text was checked. No link, no summary.
  • match.eventId is the event's id in /v3/events — not the webhook delivery id (X-Auto-Event-Id, the one to dedupe deliveries on). GET /v3/events?eventIds=<match.eventId> returns the summary, cited posts with links, price impacts and storylineId. To be alerted once per story, filter on relation: "origin" rather than comparing storylineId values yourself; to follow the story an alert belongs to, create a condition on its storylineId.
  • The /v3 endpoints need a Pay-as-you-go, Grow or Enterprise key. Without one, name the entity by ticker or name and read the Validate response for what it resolved to.
{
  "source": "events",
  "args": { "entity": "ETH", "implication": "bearish" }
}

Calls (calls):

Triggers on the trading calls one X account makes: each time it opens a long or short, adds to it, trims it, closes it, gets stopped out, or reverses it. These are the calls GET /v3/calls (API v3) serves, so a plan fires on the same call the endpoint returns, as soon as Elfa records it. Use calls for an account's calls, tweet for its posts in general, and events with eventClass: "call" for many traders calling the same asset. A calls condition reads the call Elfa recorded, so it needs no text and costs no credits. Full reference: https://docs.elfa.ai/auto/calls

MethodArgs (handle required, the rest optional)Returns
matchhandle, asset, market, direction, actionboolean
  • handle: the caller's X handle, with or without @; write it without. One account per condition; several callers → several calls conditions under OR.
  • asset: an id from GET /v3/entities, a ticker or a name of the token or stock the call is on, stored as assetId (the asset's family id, the asset.id a call carries). market (crypto or equity, requires asset) settles a ticker that names both a coin and a stock; without it, the one that leads its own market wins. A project, person or organisation is rejected. When other assets match asset just as closely, the market leader wins and Validate returns a warning naming the others; only a tie that nothing breaks is rejected, as "Ambiguous asset".
  • direction: long or short. action: open, add, trim, reverse, close, stopOut; leave it out to fire on every action. One value per argument: for "close or stop out", or "BTC or ETH", write an OR of conditions.
  • Shorthand { "source": "calls", "args": { … } } expands to method: "match", operator: "==", value: true. No text, no minConfidence, no dynamic values.
  • Which accounts: Elfa records calls from the X accounts it collects posts from, and Validate/Create reject any other with EQL_INVALID_ARG_VALUE. Find callers with GET /v3/calls or GET /v3/people/accounts?metric=alphaScore; for an account outside them, use tweet. Some accounts are collected with a delay: Validate accepts them with a warning, and their calls are recorded hours after the post, typically two to five. Telegram channels cannot be followed yet.
  • Runtime: usually within a minute of the post (hours later for a delayed account). Each call fires a plan once; with repeat, each new call fires again. Only calls made after the plan was created, and never a call recorded more than 12 hours after its post. Restating an open position (reaffirm in /v3/calls) is not an action, and neither is a position expiring at the end of its horizon, which is how most positions end: "tell me when the position ends" hears only explicit exits. A reversal is two calls (reverse on the side it leaves, open on the side it takes), so a condition without action fires twice; a post naming several assets is one call per asset.
  • match in the trigger payload: callId and episodeId (the call's id and episodeId in /v3/calls; read the position with GET /v3/calls/episodes), handle, source (twitter), callAction, direction, horizon, asset (id, symbol, name, entityType), setup, statedTarget, statedStop, entryPrice, occurredAt (ISO 8601 here; Unix seconds on /v3/calls) and postUrl. exitPrice and closeReason appear on close, stopOut and reverse; fields the call does not have are left out.
{
  "source": "calls",
  "args": { "handle": "3PeaksTrading", "direction": "long", "action": "open" }
}

Signal selection policy (which source to pick):

  • One account's trading calls (@user opens a long, @user exits BTC) → calls.
  • Account-anchored intent (handle in the prompt) → tweet.
  • Channel-anchored intent (a public Telegram channel in the prompt) → telegram.
  • News about a subject (no specific account) → events.
  • Official SEC filing intent (issuer CIK, form type, 8-K item) → sec.
  • Prefer tweet/telegram/events over llm when the trigger is plausibly expressed as a post or event.
  • Fall back to llm (athena_condition) only for predicates that are not naturally a post/event match.

Signal args.text authoring rubric — match quality is dominated by text quality. Write a short factual claim, not a monitoring command.

Weak phrasing (bad)Actionable phrasing (good)
Bearish vibesOpens a short position on oil
Something bullishAnnounces a new stake in TSLA
Bullish on a coinPosts that they're bullish on $HYPE and $SOL
Alpha group updatenew Binance listing rumor
Team newsteam confirms mainnet launch
Market crash (events text)Major DeFi protocol suffers a $200M exploit
War conflict (events text)US imposes new sanctions on Russia
Bearish ETH news (events text)no text — { "entity": "ETH", "implication": "bearish" }

Additional constraints:

  • Keep text atomic — one event/theme per condition.
  • For tweet, do not restate account identity in text — username already scopes that.
  • For telegram, do not restate channel identity in text — chatUsername or chatId already scopes that.
  • Split A or B intents into multiple Signal conditions joined by OR; split A and B into multiple conditions joined by AND.
  • Prefer separate queries for event-driven intents (tweet/telegram/events) and recurring schedule intents (cron.every) for clearer runtime semantics.

minConfidence tuning: default 80; raise to 85–90 for fewer false positives; lower to 70–75 if you need higher recall.

Scheduling period (cron / llm): for cron interval methods (once, onceRemainTrue, every) and llm sources, period is a scheduling interval with a minimum of 1h. Allowed: 1h, 2h, 4h, 8h, 12h, 24h, 1d, 7d. cron.schedule does not take a period — it is driven by its 5-field cron expression and follows the same 1h-minimum cadence (at most one fire per hour).

Signal sources are event-driven, not schedule-driven — they evaluate when relevant mention events arrive, not on a polling interval. They can still be combined with other condition types via AND/OR.

SEC filings source (sec):

Trigger on enriched SEC filing metadata — a new 8-K, a late periodic filing notice, an offering form, or a specific 8-K item. sec conditions use the full EQL condition form and obey the standard limits (depth 3, 10 leaves), so they combine with other sources in AND/OR groups.

CIK routing: args.ticker is the issuer's CIK, not the stock ticker. It must be 1–10 digits, is trimmed, and is normalized to a 10-digit zero-padded CIK ("1652044" → "0001652044"). Validate/create checks the CIK against SEC submissions metadata and rejects unknown or malformed values rather than creating a plan that can never fire.

MethodReturnsOperatorsDescription
base_form_typeenum== !=Normalized base form — amendments stripped (10-K/A → 10-K), 424B* → 424B
form_categoryenum== !=current_report, periodic_report, ownership, offering, proxy_ma, listing, registered_fund, sec_review, other
is_amendmentboolean== !=Form ends in /A
is_late_filingboolean== !=Late notification form (NT 10-K, NT 10-Q, NT 20-F)
is_offeringboolean== !=Offering, registration, or prospectus-style filing
is_ownership_filingboolean== !=Insider, beneficial, or institutional ownership filing
has_item_1_03boolean== !=8-K Item 1.03 — bankruptcy or receivership
has_item_1_05boolean== !=8-K Item 1.05 — material cybersecurity incident
has_item_2_02boolean== !=8-K Item 2.02 — results of operations
has_item_2_05boolean== !=8-K Item 2.05 — exit or disposal costs
has_item_3_01boolean== !=8-K Item 3.01 — delisting or listing-compliance notice
has_item_4_01boolean== !=8-K Item 4.01 — change of certifying accountant
has_item_5_02boolean== !=8-K Item 5.02 — director/officer changes
has_item_8_01boolean== !=8-K Item 8.01 — other reported events
has_primary_documentboolean== !=Primary filing document name was found
has_xbrlboolean== !=Archive includes XBRL artifacts
submission_match_foundboolean== !=Accession matched inside SEC submissions metadata
document_countnumber> < >= <= == !=Number of archive documents discovered
{
  "source": "sec",
  "method": "base_form_type",
  "args": { "ticker": "0001652044" },
  "operator": "==",
  "value": "8-K"
}

Use base_form_type when the user names a specific form, form_category for a filing family, and the boolean methods for normalized flags (is_late_filing for NT notices, is_offering for registrations and prospectuses, has_item_1_05 for cybersecurity disclosures). sec is event-driven like the Signal sources, so with repeat it re-fires on the next distinct filing. Full reference: SEC Filings.

Prediction Markets source (kalshi):

Trigger on Kalshi prediction-market activity — live trade prints and market lifecycle. Each kalshi condition takes exactly one arg, ticker: a full open Kalshi market ticker (e.g. KXBTC-26APR0803-T77799.99 = series + event + strike). Prefer kalshi over llm when the trigger is a supported Kalshi method, and over price/ta when you care about an event's implied probability rather than an asset's spot price.

Trade-backed methods — evaluate on the latest executed trade print:

MethodReturnsDescription
yes_pricenumber 0–1Executed YES price = live implied probability of YES
no_pricenumber 0–1Executed NO price (implied probability of NO)
trade_sizenumber ≥ 0Contracts on that print
taker_outcome_sideenum yes / noWhich outcome the aggressor (taker) positioned for
taker_book_sideenum bid / askbid = YES-side pressure, ask = NO-side pressure
is_block_tradebooleantrue for large off-book negotiated block trades

Market-backed methods — evaluate on market lifecycle updates:

MethodReturnsDescription
statusenum active / closed / determined / settled / finalizedLifecycle state (active = only tradeable state)
resultenum yes / noSettlement outcome (empty until the market resolves)
settlement_valuenumber ≥ 0Final settlement value in dollars (populated on settlement)

Operators by method (restricted per method):

Method(s)Allowed operators
yes_price, no_price> < >= <= == != crosses_above crosses_below
trade_size, settlement_value> < >= <= == !=
taker_outcome_side, taker_book_side, status, result, is_block_trade== !=

Only the price methods (yes_price / no_price) support the transition operators crosses_above / crosses_below; enum and boolean methods are equality-only. value must be a literal matching the method's type (no dynamic field-vs-field values), and is_block_trade takes a JSON boolean true / false (not the strings "true" / "false").

Open markets only: at create/validate time args.ticker is checked against the catalog of currently-open Kalshi markets. A closed / determined / settled / finalized ticker is rejected with EQL_INVALID_ARG_VALUE ("was not found or is not open"). Resolve a real, currently-open ticker first — never invent or guess tickers. Lifecycle conditions are still useful on a market you already watch: a plan created while a market is active can fire as it later moves to settled (status) or resolves (result / settlement_value) within its expiresIn window.

{
  "source": "kalshi",
  "method": "yes_price",
  "args": { "ticker": "KXBTC-26APR0803-T77799.99" },
  "operator": "crosses_below",
  "value": 0.4
}

Full method tables, value enums, and example automations: Prediction Markets → Kalshi.

Prediction Markets source (polymarket):

Trigger on live Polymarket outcome-token activity: last traded price, best bid/ask, and the size and aggressor side of the most recent trade. Unlike Kalshi, Polymarket exposes price and trade methods only — there are no market-lifecycle (status / result) methods.

Ticker = outcome-token asset_id. For Polymarket, args.ticker is the outcome token asset_id — the long numeric id of a single outcome (e.g. the YES token), not the top-level Polymarket market id (which is shared by both sides of a binary market). At create time the token is validated against Polymarket's live markets; an unknown or inactive token is rejected ("was not found or is not active"). Resolve a real, currently-active outcome-token id first — never invent or guess ids.

Each method takes exactly one arg, ticker:

MethodReturnsDescription
pricenumber in [0, 1]Last traded probability / price for the outcome token
bidnumber in [0, 1]Best bid for the outcome token (when present on the event)
asknumber in [0, 1]Best ask for the outcome token (when present on the event)
sizenumber ≥ 0Size of the last-trade update (when present on the event)
sideenum "BUY" / "SELL"Aggressor side of the last trade, in uppercase

The feed mixes several event subtypes (price_change, best_bid_ask, last_trade_price, book), and not every field updates on every event — best_bid_ask updates bid/ask only, while last_trade_price updates price/size/side only. Design a condition around the specific field you need rather than assuming all fields move together.

Operators by method (restricted per method):

Method(s)Allowed operators
price, bid, ask> < >= <= == != crosses_above crosses_below
size> < >= <= == !=
side== !=

value must be a literal matching the method's type (0–1 for price/bid/ask, ≥ 0 for size, uppercase "BUY" / "SELL" for side). Polymarket conditions do not support dynamic (field-vs-field) values — dynamic Polymarket targets are rejected at validation.

{
  "source": "polymarket",
  "method": "price",
  "args": { "ticker": "115556263888245616435851357148058235707004733438163639091106356867234218207169" },
  "operator": "crosses_below",
  "value": 0.4
}

Standard EQL limits apply to both prediction-market sources: combinable with other sources inside AND/OR groups, up to depth 3 and 10 leaf conditions. Full method tables and example automations: Prediction Markets → Polymarket.

Market-structure source (funding) — perp funding rate:

Trigger on a perp's funding rate — an overheated long bias, a flip to negative, or a cross-venue divergence. Funding and liquidation flow often lead the spot price reaction.

Ticker = composite SYMBOL:EXCHANGE (not symbol + exchange args). SYMBOL is the base asset (BTC, not BTCUSDT); the venue rides in the ticker. Venues: binance, hyperliquid. Examples: BTC:BINANCE, ETH:HYPERLIQUID.

MethodArgsReturnsOperatorsDescription
annualized_ratetickernumberall 8Canonical. Funding as annualized percent (APR): 32.85 = 32.85% APR. Positive = longs pay shorts.
interval_ratetickernumberall 8Percent per settlement interval.
interval_hourstickernumberlevel onlySettlement interval in hours: 8 (Binance) or 1 (Hyperliquid). Venue metadata.
exchangetickerenum== / !=The venue: "binance" or "hyperliquid".
  • Prefer annualized_rate — Binance settles every 8h and Hyperliquid every 1h, so the same interval_rate threshold means different things per venue; annualizing removes that trap. "Funding flips negative" is exactly annualized_rate crosses_below 0.
  • "level only" = > < >= <= == != (no crosses_*).
  • HIP-3 dex-prefixed symbols (xyz:TSLA) are rejected here (EQL_INVALID_ARG_VALUE, details.hip3: true) — the funding feed never publishes keys for them.
{ "source": "funding", "method": "annualized_rate", "args": { "ticker": "BTC:BINANCE" }, "operator": "crosses_below", "value": 0 }

Market-structure source (liquidation) — liquidation flow:

Trigger on liquidation flow in a trailing window — a cascade crossing a USD threshold, a one-sided flush, or liquidations as a share of open interest. Same composite SYMBOL:EXCHANGE ticker as funding. Venues: binance, bybit, hyperliquid.

MethodArgsReturnsOperatorsDescription
total_usd_1m / total_usd_5m / total_usd_1htickernumberall 8Total USD liquidated in the trailing window.
long_usd_1m / long_usd_5m / long_usd_1htickernumberall 8Longs liquidated (forced selling).
short_usd_1m / short_usd_5m / short_usd_1htickernumberall 8Shorts liquidated (forced buying).
largest_order_usd_1htickernumberall 8Single biggest liquidation order in the trailing hour, USD.
count_1htickernumberlevel onlyNumber of liquidation orders in the trailing hour.
total_pct_oi_1m / total_pct_oi_5m / total_pct_oi_1htickernumberall 8Window total as a percent of open interest (compares across symbols).
exchangetickerenum== / !=The venue: "binance" / "bybit" / "hyperliquid".
  • Feed quality differs by venue. Binance is sampled (≤1 liquidation/symbol/second) so its values are a lower bound and undercount most during the cascades you care about; Bybit and Hyperliquid are complete (exact totals). Set a lower threshold on Binance for the same real-world event.
  • Windows decay to zero once a cascade subsides, which re-arms the condition — so a repeat plan fires once per cascade, not once ever.
  • total_pct_oi_* magnitudes are small: ~0.1%/hr is already heavy for a major; start around 0.5–1 (percent), not raw fractions. HIP-3 symbols are rejected here too.
{ "source": "liquidation", "method": "total_usd_5m", "args": { "ticker": "ETH:BYBIT" }, "operator": "crosses_above", "value": 500000 }

Full method tables, feed-quality table, and examples: Funding and Liquidations.

Market-sentiment source (fear_greed) — Crypto Fear & Greed Index:

Trigger on the market-wide Crypto Fear & Greed Index. This is a keyless source — one global reading, no per-market identity — so its methods take no ticker. Pass args: {}.

MethodArgsReturnsOperatorsDescription
value(none)numberall 8Index score, integer 0–100 (0 = extreme fear, 100 = extreme greed).
classification(none)enum== / !=Bucket label (see below).
  • classification value ∈ "Extreme fear" (0–19), "Fear" (20–39), "Neutral" (40–59), "Greed" (60–79), "Extreme greed" (80–100). Bands map to fixed value ranges — prefer value for a precise threshold.
  • Updates ~every 15 min. A crosses_* leaf needs a prior reading, so it can only fire on the second reading after activation. No dynamic values.
{ "source": "fear_greed", "method": "value", "args": {}, "operator": "crosses_below", "value": 20 }

Mindshare source (mindshare) — social attention to one token or stock:

Trigger on an asset's share of its market's attention, its rank, how fast that share is changing, and whether it is hot: the numbers GET /v3/market/{market}/mindshare (API v3) returns. One asset per condition; several → several conditions under OR (there is no "any asset turns hot" condition). Write the full form. A match carries the condition, the resolved assetId and the observed value — no post, no event, no name; read the board or /v3/events for the reason behind a move. Full reference: https://docs.elfa.ai/auto/mindshare

MethodArgsReturnsOperatorsDescription
share_1h, share_4h, share_24h, share_7dasset, market?numberall 8Share of the market's mentions in the period, weighted by who posted, 0–100 (5 = 5%). The endpoint's share.
rank_1h … rank_7dasset, market?numberall 8Place by share, 1 = most attention. The endpoint's rank.
change_pct_1h … change_pct_7dasset, market?numberall 8Change of its share against the previous period, as a percent of itself (100 = doubled). The endpoint's changePct.
hot_4h, hot_24h, hot_7dasset, market?boolean== / !=Heating up in the last hour, as the endpoint's hot defines it. No hot_1h.
  • asset (required): an id from GET /v3/entities, a ticker or a name, stored as assetId (the asset's family id, the id the mindshare endpoint returns). market (crypto or equity): set it whenever you know the asset class — without it, a ticker that names both a coin and a stock goes to whichever leads its own market (Coinbase is "asset": "COIN", "market": "equity"). Projects, people and organisations are rejected. When other assets match just as closely, the market leader wins and Validate warns; only a tie that nothing breaks is rejected, as "Ambiguous asset".
  • Periods end at the last whole five minutes. Use 24h when the user gives no period. "Enters the top N" is rank_<p> crosses_below N; "drops out of the top N" is crosses_above N + 1. No dynamic values.
  • Off the board, readings stay numeric: no mentions in the period reads share 0, rank 1000000, change_pct -100 (or 0 if it had no share before either) and hot false; a new entrant's change_pct is 0, so catch a first appearance with share_* or rank_*.
  • Runtime: readings arrive every five minutes. A condition fires on the reading where it turns true (an already-true one about five minutes after creation); crosses_* needs two readings. With repeat, it fires again only after turning false and true again. An asset with no mentions in 7 days validates and waits.
  • Preview: read GET /v3/market/crypto/mindshare?period=24h&limit=50 and set the threshold beyond the current values, so the plan does not fire on its first reading.
  • match.observedValue is the reading that fired (a hot_* reading is 1 or 0).
{ "source": "mindshare", "method": "rank_24h", "args": { "asset": "SOL", "market": "crypto" }, "operator": "crosses_below", "value": 10 }

Follow-graph sources (x_follow_received, x_follow_made, x_follow_overlap) — new X follow edges:

Three sources ask three questions about the same data. The account you name is always the watched account; the method describes the account on the other end of the edge.

SourceMethodArgsReturnsOperatorsMeasures
x_follow_receivedfollower_countaccount, window?, period?number> / >=Follower count of the account that followed the watched account.
x_follow_madefollowee_follower_countaccount, window?, period?number> / >=Follower count of the account the watched account followed.
x_follow_overlapdistinct_followersaccounts (2–25), window?, period?number> / >=How many watched accounts followed the same account.

Only > and >= are accepted — no crosses_*, no dynamic field-vs-field values. Accounts are bare X usernames (1–15 letters, digits or underscores); a leading @ is stripped, routing is lowercased, and duplicates in accounts collapse to one.

ArgValuesDefault
window6h, 12h, 24h, 48h, 72h, 7d24h (72h for x_follow_overlap)
period1h, 2h, 4h, 8h, 12h, 24h, 1d, 7d1h

window is the lookback over detection time; period is the minimum interval between reads. A period longer than window is silently clamped down to window rather than rejected.

{ "source": "x_follow_received", "method": "follower_count", "args": { "account": "elfa_ai", "window": "24h" }, "operator": ">", "value": 10000 }
{ "source": "x_follow_overlap", "method": "distinct_followers", "args": { "accounts": ["pantheracapital", "a16z", "paradigm"], "window": "72h" }, "operator": ">=", "value": 2 }

On x_follow_received / x_follow_made, >= 0 matches every detected follow and a higher value narrows to accounts with a real audience. On x_follow_overlap the value floor is 2 and it cannot exceed the size of accounts (one below it with >) — either is rejected at create time.

Coverage is bounded and the limits change how you should write the plan: only follows by accounts Elfa reads are seen — the accounts it monitors, plus the per-day accounts your own queries watch with x_follow_made or x_follow_overlap — data is collected on a recurring check rather than streamed so alerts are not real time (a per-day account's follows are read once a day, so a new follow can take up to a day to fire), at most 500 matches land per evaluation, and unfollows and re-follows never fire. A quiet plan means no account Elfa reads made that follow — it does not prove nobody did.

x_follow_made and x_follow_overlap read the watched account's own following list and can watch any public X account. Accounts Elfa already monitors for follows cost nothing extra; any other account needs a paid API key and is billed for each day its follows are read. x_follow_received works for any public account at no extra cost, because it is the followers that must be monitored. Validate is free and tells you which applies.

Watched account (x_follow_made, x_follow_overlap)Free API keyPaid API keyx402
Monitored for followsIncludedIncludedIncluded
Any other public accountRejected (paid_plan_required)Billed per dayRejected
  • followAccounts[] (Validate and Create): one entry per watched account — path (counting leaf conditions in order: conditions[1] is the second, whatever AND/OR groups hold them, unlike tweetAccounts[].path, which includes the group; an x_follow_overlap condition has one entry per account, all with the same path), username (as stored: lowercase, without @), accountSourceRefId, billing (included or per_day) and creditsPerDay (per_day only: 1). Match entries on path and username. Absent when the query has no such condition, on x402, or when an account was rejected.
  • Cost: each UTC day on which a per_day account's follows are read costs creditsPerDay, whether or not the query fires; follows are read once a day, and a day with no read is not billed. The first read, when a query starts watching the account, is free. A key pays once per account and day, however many of its queries watch it. Not in estimatedCredits or x-elfa-credits — check /v2/key-status. billing is a snapshot. Example: 3 per_day accounts for 30 days cost about 90 credits, even if the query never fires.
  • If a charge is refused (credits exhausted, spend cap or monthly limit reached with overage off, or the key is no longer on a paid tier), every query of that key watching the account stops: Poll returns status: "failed" with failure.code SUBSCRIPTION_CREDITS_EXHAUSTED and source: "billing", and the lifecycle notification (status: "stopped") carries the same text in reasonUser. Top up, upgrade or raise the limit, then create the query again.

Each matched account is a distinct event: without repeat the first match triggers the plan once, and with repeat it fires once per distinct matched account (cooldown: "0" for "notify me about every one"). A matched account will not fire again while it stays inside window, and a follow condition reports no value until its first scheduled evaluation, so there is no preview before execution. Builder Chat cannot author these yet; build them against Validate/Create directly. Read Follow Graph before using these sources.

Supported operators: >, <, >=, <=, ==, !=, crosses_above, crosses_below

Dynamic comparisons: value can reference another live data source instead of a literal:

{
  "source": "price",
  "method": "current",
  "args": { "symbol": "ETH", "exchange": "hyperliquid" },
  "operator": "crosses_above",
  "value": {
    "source": "ta",
    "method": "bbands_upper",
    "args": { "symbol": "ETH", "timeframe": "4h", "exchange": "hyperliquid" }
  }
}
Copy-paste query templates

1) Breakout alert (webhook):

{
  "title": "BTC breakout above 100k",
  "description": "Notify runner when BTC spot trades above the 100k level.",
  "conditions": { "AND": [{ "source": "price", "method": "current", "args": { "symbol": "BTC", "exchange": "hyperliquid" }, "operator": ">", "value": 100000 }] },
  "actions": [{ "stepId": "step_1", "type": "webhook", "params": { "url": "https://your-endpoint.example/events" } }],
  "expiresIn": "24h"
}

2) Downside guardrail (telegram_bot):

{
  "title": "ETH downside guardrail (< 2500)",
  "description": "Risk-off alert: flag if ETH breaks below 2500. The notification body is auto-composed from title + description + trigger context.",
  "conditions": { "AND": [{ "source": "price", "method": "current", "args": { "symbol": "ETH", "exchange": "hyperliquid" }, "operator": "<", "value": 2500 }] },
  "actions": [{ "stepId": "step_1", "type": "telegram_bot", "params": { "botToken": "<TELEGRAM_BOT_TOKEN>", "chatId": "<TELEGRAM_CHAT_ID>" } }],
  "expiresIn": "24h"
}

3) Triggered LLM analysis:

{
  "title": "BTC > 100k — LLM review",
  "description": "On BTC breakout, run an LLM pass to decide the next follow-up action.",
  "conditions": { "AND": [{ "source": "price", "method": "current", "args": { "symbol": "BTC", "exchange": "hyperliquid" }, "operator": ">", "value": 100000 }] },
  "actions": [{
    "stepId": "step_1",
    "type": "llm",
    "params": { "action": "chat", "message": "Analyze trigger context and return the next follow-up action." }
  }],
  "expiresIn": "24h"
}

4) Multi-symbol confirmation:

{
  "title": "BTC + ETH joint breakout",
  "description": "Confirm majors moving together before acting.",
  "conditions": {
    "AND": [
      { "source": "price", "method": "current", "args": { "symbol": "BTC", "exchange": "hyperliquid" }, "operator": ">", "value": 100000 },
      { "source": "price", "method": "current", "args": { "symbol": "ETH", "exchange": "hyperliquid" }, "operator": ">", "value": 3500 }
    ]
  },
  "actions": [{ "stepId": "step_1", "type": "notify", "params": { "message": "BTC and ETH confirmation fired" } }],
  "expiresIn": "24h"
}

5) Dynamic comparison (price vs Bollinger Band):

{
  "title": "ETH breakout above 4h upper BBand",
  "description": "Dynamic: fire when ETH price crosses above its own 4h upper Bollinger Band.",
  "conditions": {
    "AND": [{
      "source": "price", "method": "current", "args": { "symbol": "ETH", "exchange": "hyperliquid" },
      "operator": "crosses_above",
      "value": { "source": "ta", "method": "bbands_upper", "args": { "symbol": "ETH", "timeframe": "4h", "exchange": "hyperliquid" } }
    }]
  },
  "actions": [{ "stepId": "step_1", "type": "webhook", "params": { "url": "https://your-endpoint.example/events" } }],
  "expiresIn": "2d"
}

6) Scheduled cron check:

{
  "title": "Every 4h: portfolio sweep",
  "description": "Recurring LLM pass every 4h.",
  "conditions": { "AND": [{ "source": "cron", "method": "every", "args": { "period": "4h" }, "operator": "==", "value": true }] },
  "actions": [{
    "stepId": "step_1",
    "type": "llm",
    "params": { "action": "chat", "message": "Summarize BTC/ETH/SOL context and flag any risk shifts." }
  }],
  "expiresIn": "3d"
}

7) LLM-evaluated narrative condition:

{
  "title": "AI narrative shift watcher",
  "description": "Fire when dominant AI-token narrative shifts based on news + X sentiment.",
  "conditions": {
    "AND": [{
      "source": "llm", "method": "athena_condition",
      "args": { "query": "Has the dominant narrative around AI-sector tokens shifted materially in the last 6 hours?", "period": "1h" },
      "operator": "==", "value": true
    }]
  },
  "actions": [{ "stepId": "step_1", "type": "telegram_bot", "params": { "botToken": "<TELEGRAM_BOT_TOKEN>", "chatId": "<TELEGRAM_CHAT_ID>" } }],
  "expiresIn": "2d"
}

8) Signal — X/Twitter Post (account-anchored):

{
  "title": "Binance Alpha listing post watcher",
  "description": "Fire when cz_binance posts that Binance Alpha is listing a new token so the runner can review follow-through.",
  "conditions": {
    "AND": [{
      "source": "tweet",
      "args": {
        "username": "cz_binance",
        "text": "Binance Alpha is listing a new token",
        "minConfidence": 80
      }
    }]
  },
  "actions": [{ "stepId": "step_1", "type": "webhook", "params": { "url": "https://your-runner.example/auto/events" } }],
  "expiresIn": "24h"
}

9) Events (news-driven catalyst):

{
  "title": "ETH ETF approval event watcher",
  "description": "Fire when an event reports a spot ETH ETF approval so I can kick off a post-event playbook.",
  "conditions": {
    "AND": [{
      "source": "events",
      "args": {
        "entity": "ETH",
        "eventType": "regulatory",
        "text": "the SEC approves a spot ETH ETF",
        "minConfidence": 80
      }
    }]
  },
  "actions": [{ "stepId": "step_1", "type": "notify", "params": { "message": "Event fired: spot ETH ETF approval" } }],
  "expiresIn": "168h"
}

10) Prediction Market — Kalshi (YES probability crossing):

{
  "title": "Kalshi YES crosses 60%",
  "description": "Fire when the market's implied YES probability crosses up through 60%, signalling the market now expects this outcome.",
  "conditions": { "AND": [{ "source": "kalshi", "method": "yes_price", "args": { "ticker": "KXBTC-26APR0803-T77799.99" }, "operator": "crosses_above", "value": 0.6 }] },
  "actions": [{ "stepId": "step_1", "type": "webhook", "params": { "url": "https://your-runner.example/auto/events" } }],
  "expiresIn": "48h"
}

11) Prediction Market — Kalshi (settlement recap via LLM):

{
  "title": "Kalshi market resolved YES — recap",
  "description": "When the market settles with a YES result, generate a concise recap of what happened and why.",
  "conditions": { "AND": [{ "source": "kalshi", "method": "result", "args": { "ticker": "KXBTC-26APR0803-T77799.99" }, "operator": "==", "value": "yes" }] },
  "actions": [{ "stepId": "step_1", "type": "llm", "params": { "action": "chat", "message": "This Kalshi market just resolved YES. Write a concise recap explaining what resolved and what to watch next." } }],
  "expiresIn": "48h"
}

12) Prediction Market — Polymarket (outcome-token price crossing):

{
  "title": "Polymarket outcome crosses 60%",
  "description": "Fire when the outcome token's last traded price crosses up through 0.60, signalling the market now expects this outcome.",
  "conditions": { "AND": [{ "source": "polymarket", "method": "price", "args": { "ticker": "115556263888245616435851357148058235707004733438163639091106356867234218207169" }, "operator": "crosses_above", "value": 0.6 }] },
  "actions": [{ "stepId": "step_1", "type": "webhook", "params": { "url": "https://your-runner.example/auto/events" } }],
  "expiresIn": "48h"
}

13) Recurring trigger (repeat):

Re-fire on the plan's own condition instead of a fixed cron cadence — notify every time BTC crosses down through 60k, rate-limited to once per hour, capped at 10 fires. Note the safer-pattern choices: crosses_below (reacts to the crossing edge, not a value sitting below the line) plus a 1h cooldown and a low maxTriggers ceiling.

{
  "title": "Notify when BTC crosses below 60k",
  "description": "Recurring risk alert: fire each time BTC price crosses down through 60000, no more than once per hour, up to 10 times.",
  "conditions": { "AND": [{ "source": "price", "method": "current", "args": { "symbol": "BTC", "exchange": "hyperliquid" }, "operator": "crosses_below", "value": 60000 }] },
  "actions": [{ "stepId": "step_1", "type": "notify", "params": { "message": "BTC dipped below 60k again" } }],
  "expiresIn": "7d",
  "repeat": { "cooldown": "1h", "maxTriggers": 10 }
}

14) Calendar schedule (cron.schedule):

Run on a wall-clock schedule — every weekday at 09:00 New York time. Direct EQL/API path only (Builder Chat support pending).

{
  "title": "Weekday 9am NY: market open recap",
  "description": "Every weekday at 09:00 America/New_York, run an LLM market-open recap.",
  "conditions": { "AND": [{ "source": "cron", "method": "schedule", "args": { "expression": "0 9 * * 1-5", "timezone": "America/New_York" }, "operator": "==", "value": true }] },
  "actions": [{ "stepId": "step_1", "type": "llm", "params": { "action": "chat", "message": "Give a concise market-open recap for BTC/ETH/SOL and flag overnight risk shifts." } }],
  "expiresIn": "7d"
}

15) Funding flip (funding):

Fire when Binance BTC funding turns negative — shorts start paying longs (crowded-short signal). Note the composite SYMBOL:EXCHANGE ticker and venue-comparable annualized_rate.

{
  "title": "BTC funding flips negative",
  "description": "Shorts are now paying longs on Binance BTC — a crowded-short signal worth reviewing.",
  "conditions": { "AND": [{ "source": "funding", "method": "annualized_rate", "args": { "ticker": "BTC:BINANCE" }, "operator": "crosses_below", "value": 0 }] },
  "actions": [{ "stepId": "step_1", "type": "notify", "params": { "message": "BTC funding on Binance flipped negative" } }],
  "expiresIn": "7d"
}

16) Liquidation cascade (liquidation + repeat):

Fire each time ETH liquidations cross $500k in a 5-minute window. Windows decay to zero once a cascade subsides, so with repeat this fires once per cascade.

{
  "title": "ETH liquidation cascade (5m > $500k)",
  "description": "Alert on each distinct ETH liquidation cascade on Bybit, capped at 10 alerts.",
  "conditions": { "AND": [{ "source": "liquidation", "method": "total_usd_5m", "args": { "ticker": "ETH:BYBIT" }, "operator": "crosses_above", "value": 500000 }] },
  "actions": [{ "stepId": "step_1", "type": "webhook", "params": { "url": "https://your-runner.example/auto/events" } }],
  "expiresIn": "7d",
  "repeat": { "cooldown": "1h", "maxTriggers": 10 }
}

17) Catalyst → equity/index alert (HIP-3):

The prediction-market-to-perp bridge: when a Fed-decision market reprices, alert your runner on the S&P perp — including overnight and at weekends, when cash equities are closed. xyz:SP500 is a HIP-3 market on Hyperliquid. Resolve a real open Kalshi ticker first — don't guess.

{
  "title": "Fed cut odds → S&P alert",
  "description": "When the market prices a cut as more likely than not, notify my runner to review the S&P perp.",
  "conditions": { "AND": [{ "source": "kalshi", "method": "yes_price", "args": { "ticker": "<an open Kalshi Fed-decision market ticker>" }, "operator": "crosses_above", "value": 0.6 }] },
  "actions": [{ "stepId": "step_1", "type": "webhook", "params": { "url": "https://your-runner.example/auto/events" } }],
  "expiresIn": "168h",
  "repeat": { "cooldown": "4h", "maxTriggers": 3 }
}

18) Asset enters the mindshare top 10 (mindshare):

A rank falls as the asset climbs, so "enters the top 10" is crosses_below 10: its rank goes from 11 or more to 10 or less.

{
  "title": "SOL enters the mindshare top 10",
  "description": "Notify when SOL enters the top 10 crypto assets by 24h mindshare.",
  "conditions": { "AND": [{ "source": "mindshare", "method": "rank_24h", "args": { "asset": "SOL", "market": "crypto" }, "operator": "crosses_below", "value": 10 }] },
  "actions": [{ "stepId": "step_1", "type": "notify", "params": { "message": "SOL entered the top 10 by 24h mindshare" } }],
  "expiresIn": "7d"
}

19) Every long a caller opens (calls):

Fires each time one X account opens a long, up to 50 times, as soon as Elfa records the call.

{
  "title": "3PeaksTrading goes long",
  "description": "Notify each time 3PeaksTrading opens a long.",
  "conditions": { "AND": [{ "source": "calls", "args": { "handle": "3PeaksTrading", "direction": "long", "action": "open" } }] },
  "actions": [{ "stepId": "step_1", "type": "notify", "params": { "message": "3PeaksTrading opened a long" } }],
  "expiresIn": "720h",
  "repeat": { "cooldown": "0", "maxTriggers": 50 }
}
Poll response shape

All query and execution identifiers are UUIDs (e.g. a12d20ff-6cb2-433e-afed-cc2e6a0380b6) — not prefixed strings like q_123 or exec_123. The short q_123 forms used elsewhere in this skill are placeholders for readability only; never construct or pattern-match on a prefixed ID.

GET /v2/auto/queries/{queryId} (and the x402 POST equivalent) returns queryId, status, an optional credits, latestEvaluation (current state), and executions (what actually fired). A query that failed with a recorded reason also carries failure (code, message, which may be null, and source); source: "billing" means a per-post X account or per-day follow account charge was refused, so top up, upgrade or raise the limit, then create the plan again.

Distinguish the two runtime surfaces — this is the most common polling mistake:

SurfaceAnswersRead for the value
latestEvaluation.conditionStates[]"what is true right now"currentValue vs targetValue
Auto Trigger Context on an execution"what fired, and why"trigger.matchedConditions[].match.observedValue

latestEvaluation is null until the query has evaluated at least once:

{
  "evaluatedAt": "2026-04-01T12:00:00.000Z",
  "conditionStates": [
    {
      "index": 0,
      "source": "price",
      "method": "current",
      "args": { "symbol": "BTC" },
      "currentValue": 97250.5,
      "targetValue": 100000,
      "operator": "<",
      "isMet": true,
      "lastUpdated": "2026-04-01T12:00:00.000Z"
    }
  ],
  "wouldTriggerNow": true,
  "matchingConditions": 1,
  "totalConditions": 1
}

conditionStates[] entries may also carry reason, and for llm conditions conversationSessionId, runId, and cacheHit. Use matchingConditions / totalConditions to show partial progress ("2 of 3 conditions met") without re-deriving it yourself.

Auto Trigger Context appears on execution records once the query has fired. It is the stable public representation of why the query fired — raw stored trigger payloads are not part of the public API, so do not depend on them.

{
  "id": "a12d20ff-6cb2-433e-afed-cc2e6a0380b6",
  "queryId": "76e2e824-fc47-4d60-99b1-c227fbd2d3f5",
  "type": "notification",
  "status": "success",
  "details": { "notification": { "channel": "webhook" } },
  "triggerTime": "2026-04-01T12:00:00.000Z",
  "conditionsMet": 1,
  "trigger": {
    "type": "price",
    "time": "2026-04-01T12:00:00.000Z",
    "matchedConditions": [
      {
        "condition": {
          "source": "price",
          "method": "current",
          "args": { "symbol": "BTC" },
          "operator": "<",
          "value": 100000
        },
        "match": { "observedValue": 97250.5 }
      }
    ]
  },
  "createdAt": "2026-04-01T12:00:01.000Z"
}
  • trigger.matchedConditions[].condition.value is the threshold; .match.observedValue is the value seen at trigger time. Report both when explaining a fire to a user.
  • For Signal sources (tweet / telegram), match also carries provenance — url, mentionedAt, confidence, accountHandle, and for Telegram chatUsername, chatTitle, messageId / messageIds, groupId. Use url to link the user straight to the post that fired the plan.
  • For events, match carries the event instead — eventId (its /v3/events id, not the delivery id), title, its classification, entities, analyzedAt, and confidence when text was checked; there is no url. Fetch the rest with GET /v3/events?eventIds=<eventId>.
  • details is keyed by action kind (trade, notification, llm, auto, script).
  • Failed executions carry error { code, message }, e.g. LLM_ACTION_UPSTREAM_ERROR.

Auto Trigger Context is also included in outbound delivery payloads and script trigger context — so a webhook receiver can explain a fire without a follow-up poll.

Use polling for debugging or backfills. For production delivery, prefer webhook or SSE notifications. Store sessionId values from executions so you can fetch full LLM analysis only when needed via GET /v2/auto/queries/:queryId/sessions/:sessionId — for example when a callback webhook with includeAnalysis reports analysisTruncated: true.

Notifications — delivery channels

After a query triggers, Auto delivers events via one of three channels:

ChannelBest forSetup
WebhookProduction agent automationaction.type = "webhook" with signature verification + queue/worker
TelegramFast human-readable alertsaction.type = "telegram_bot" with params.botToken + params.chatId (direct), or webhook→bot relay for custom formatting
SSE StreamReal-time event consumers; always available regardless of the chosen actionTwo streams, both free and GET, both using the same auth as query creation: GET /v2/auto/queries/stream (account-wide — every query on one connection, default, API-key only) or GET /v2/auto/queries/{queryId}/stream (single query; the x402 / agent-identity option). See SSE frame format.

Query title and description in notifications: both fields are embedded in every outbound notification (Telegram, webhook, SSE). Recipients often see an alert hours or days after the query was set up — these fields are what make it clear what fired and why it was set up. Always set them.

Notification payload (what Auto actually sends)

Auto's outbound webhook notification object has this shape:

{
  "id": 12345,
  "type": "athena_query_notify_only",
  "category": "alerts",
  "title": "BTC breakout above 100k",
  "body": "price > threshold",
  "data": { "queryId": "q_123" },
  "priority": "high",
  "createdAt": "2026-04-01T12:00:00.000Z"
}

id is the numeric notification ID; correlate back to a query via data.queryId. title and body come from the query's title / description plus trigger context. SSE delivery uses a different, more compact frame — see SSE frame format below.

Canonical event payload contract (internal normalization)

It's useful to normalize incoming events (webhook, Telegram relay, SSE) to a single internal shape so downstream processing is channel-agnostic. This is a recommended runner-side convention, not the wire format Auto sends (see the payload above):

{
  "version": "1.0",
  "eventType": "query.triggered",
  "eventId": "evt_01J...",
  "timestamp": "2026-04-01T12:00:00.000Z",
  "queryId": "q_123",
  "channel": "webhook",
  "trigger": {
    "symbol": "BTC",
    "reason": "price > threshold"
  },
  "evaluation": { "triggered": true },
  "action": { "type": "webhook" }
}

Webhook request headers (what your receiver must read):

HeaderPurpose
X-Auto-Event-IdUnique event ID for deduplication
X-Auto-Signature-TimestampUnix seconds for replay-window check
X-Auto-Signaturev1=<hex_hmac_sha256> — verify against raw body
SSE frame format

There are two SSE streams; both are free, both are GET, and both use the same auth used to create the query (x-elfa-api-key for API-key queries, the x402 secret for x402 queries — not limited to x-elfa-api-key). For minimal setup, attach a notify action with a message — those notifications are retrievable only via SSE or poll.

EndpointScopeUse when
GET /v2/auto/queries/streamEvery query you own, on one connectionDefault. Correlate events by the payload's queryId. API-key only — agent identities (x-elfa-agent-secret) and x402 get 403.
GET /v2/auto/queries/{queryId}/streamA single queryYou only care about one query, or you are on x402 / an agent identity.

Prefer the account-wide stream over one connection per query. Open it after creating at least one query — it returns 410 when you have none.

Both streams emit the same frames — event: notification, id = the notification outbox event UUID (not a query ID), and a : keep-alive comment every 15s. Events are live-only: there is no replay and Last-Event-ID is ignored.

id: 9df34377-4d82-4b3c-a016-b0ba27556aa1
event: notification
data: {"status":"triggered","title":"Plan Triggered","body":"BTC RSI Breakout","queryId":"q_123","executionId":"2dbf0d70-a85f-4f67-9bd3-876e8fd89f86","triggerTime":"2026-04-01T12:00:00.000Z","conditionsMet":2,"timestamp":1774328400000}

status is one of triggered, stopped, ended, or update. queryId is a top-level field on the payload — use it to correlate with poll results via /v2/auto/queries/{queryId}. The payload also carries execution context when present (executionId, triggerTime, conditionsMet, autoDetails).

Stream lifecycle. Each stream closes once there is nothing left to deliver, emitting a final end event:

/queries/stream/queries/{queryId}/stream
410 on connectYou have no active queriesThe query is already terminal and drained
Closes whenNo active query and no pending execution, held 30sThe query reaches a terminal state and its notifications drain
Final framedata: {"code":"USER_STREAM_CLOSED"}data: {"code":"QUERY_STREAM_CLOSED","status":"<terminalStatus>","queryId":"<uuid>"}

A recurring (repeat) query is never terminal, so its stream stays open across triggers. If a stream fails after it has started, it emits event: error with {"code":"STREAM_UNAVAILABLE"}.

Telegram relay job format (when transforming webhook → Telegram Bot API):

{
  "eventId": "evt_01J...",
  "queryId": "q_123",
  "channel": "telegram",
  "chatId": "<CHAT_ID>",
  "text": "BTC trigger fired: price > threshold",
  "priority": "high"
}
Webhook signature verification

Set an explicit signingSecret on every production webhook. It is a webhook action param, write-only — Elfa stores it for delivery signing and never returns it in query, execution, webhook, SSE, or LLM-callback payloads. Without it, signature headers may be absent entirely depending on access mode and server configuration, so a receiver that requires signatures will reject every delivery.

{
  "stepId": "step_1",
  "type": "webhook",
  "params": {
    "url": "https://your-runner.example/auto/events",
    "signingSecret": "<openssl rand -hex 32>",
    "allNotifications": true
  }
}

LLM callback webhooks take the same destination params under params.callback.action.params, plus includeAnalysis (default false). Without it, the callback tells your endpoint the query fired, not what the llm step wrote. Set it to true and the payload also carries the answer as analysisText (at most 32,000 characters) and analysisTruncated. Validate rejects includeAnalysis on a top-level webhook, and telegram_bot callbacks never carry the answer. See LLM Analysis in a Callback Webhook.

Which secret to use — two distinct secrets, never interchange them:

SecretDirectionPurpose
x-elfa-api-keyyou → ElfaAuthenticates your client
webhook.params.signingSecretElfa → youSigns Elfa's outbound webhook deliveries to your server

Generate a separate high-entropy secret per webhook action (openssl rand -hex 32). Do not reuse the API key or the x402 agent secret here.

Signing inputs:

expected = HMAC_SHA256(signingSecret, timestamp + "." + eventId + "." + rawBody)

The signingSecret is the HMAC key directly — do not hash it first. Earlier revisions of this skill documented HMAC_SHA256(SHA256(secret), ...). That is wrong for explicit signing secrets and will fail verification.

Legacy x402/agent fallback: if an x402/agent webhook omits signingSecret, older deliveries may be signed with SHA256(x-elfa-agent-secret) as the key. Treat that as a legacy fallback, not a supported setup — set an explicit signingSecret instead.

Node.js verification:

import crypto from "crypto";

export function verifyAutoWebhook(
  signingSecret: string,
  rawBody: string,
  signatureHeader: string,
  timestamp: string,
  eventId: string,
): boolean {
  if (!signatureHeader?.startsWith("v1=")) return false;
  const givenHex = signatureHeader.slice(3);
  if (!/^[0-9a-f]{64}$/i.test(givenHex)) return false;

  const payload = `${timestamp}.${eventId}.${rawBody}`;
  const expectedHex = crypto
    .createHmac("sha256", signingSecret)
    .update(payload)
    .digest("hex");

  const given = Buffer.from(givenHex, "hex");
  const expected = Buffer.from(expectedHex, "hex");
  if (given.length !== expected.length) return false;
  return crypto.timingSafeEqual(given, expected);
}

Operational checklist:

  • Enforce a bounded replay window on X-Auto-Signature-Timestamp (reject drift >30s).
  • Deduplicate by X-Auto-Event-Id in durable storage.
  • Return 2xx fast, then process asynchronously (queue + worker).
Telegram bot setup (for action.type: "telegram_bot" or relay)
  1. Open @BotFather in Telegram → /newbot → save bot token (treat as secret).
  2. Send any message to the bot (or in a group where the bot is present).
  3. curl "https://api.telegram.org/bot<BOT_TOKEN>/getUpdates" → read message.chat.id.
  4. Send messages via:
curl -X POST "https://api.telegram.org/bot<BOT_TOKEN>/sendMessage" \
  -H "Content-Type: application/json" \
  -d '{"chat_id": "<CHAT_ID>", "text": "Auto trigger fired for BTC RSI"}'

Direct chats, groups, and supergroups: telegram_bot delivers to private chats, groups, and supergroups. Channels are not supported and are rejected at create time. Two gotchas for groups: (1) group chat IDs are negative (-1001234567890) — copy verbatim from getUpdates, including the leading -; (2) the bot must be able to post — at create time Auto checks the bot's membership/permissions, and if it can't post, create fails with Telegram bot cannot send messages in this chat (check its permissions). If a group is later upgraded to a supergroup (new chat ID), Auto detects the migration, updates the stored ID, and retries — no action needed.

Agent runner — reference architecture

Auto handles query evaluation + event emission. Your runner handles event ingestion, verification, deduplication, strategy continuation, and audit logging.

Auto Query
  → Auto Event Ingress (Webhook / SSE)
  → Signature Verification + Idempotency
  → Job Queue
  → Agent Decision Worker
  → Action Adapter (Telegram / Internal API / Order Router)
  → Logs + Metrics + Alerts

Core processing loop:

  1. Receive event (webhook or SSE frame).
  2. Verify signature (webhook only).
  3. Check idempotency — is eventId already processed?
  4. Enqueue job and ACK 2xx fast.
  5. Worker resolves extra context (poll query, fetch LLM session).
  6. Apply policy, decide next step.
  7. Execute downstream action.
  8. Record result for replay/debug.

Instruction envelope — structured contract passed from ingress to worker:

{
  "eventId": "evt_123",
  "queryId": "q_123",
  "objective": "Handle trigger and decide next action",
  "allowedActions": ["notify", "fetch_session", "execute_adapter"],
  "constraints": {
    "maxExecutionSeconds": 30,
    "riskMode": "conservative"
  }
}

Minimal TypeScript worker skeleton:

type AutoJob = { eventId: string; queryId: string; raw: unknown };
const queue: AutoJob[] = [];
const processed = new Set<string>();

function onWebhook(eventId: string, queryId: string, raw: unknown) {
  if (processed.has(eventId)) return; // idempotent
  queue.push({ eventId, queryId, raw });
}

async function workerLoop() {
  while (true) {
    const job = queue.shift();
    if (!job) { await new Promise((r) => setTimeout(r, 250)); continue; }
    // 1) Pull latest query/execution state if needed
    // 2) Fetch session details for llm actions
    // 3) Decide next action (policy + agent logic)
    // 4) Execute action (notify, relay, order adapter)
    processed.add(job.eventId);
  }
}

Deployment patterns:

PatternBest for
Single process (API + worker)Early-stage prototypes
API + queue + workersProduction reliability at scale
Serverless consumer + queue workerSpiky workloads with managed ops

Local (Docker Compose) stack:

version: "3.9"
services:
  redis: { image: redis:7-alpine, ports: ["6379:6379"] }
  ingress:
    build: ./ingress
    environment:
      AUTO_SECRET: ${AUTO_SECRET}
      REDIS_URL: redis://redis:6379
    depends_on: [redis]
    ports: ["3000:3000"]
  worker:
    build: ./worker
    environment:
      AUTO_SECRET: ${AUTO_SECRET}
      REDIS_URL: redis://redis:6379
      ELFA_BASE_URL: https://api.elfa.ai/v2/auto
    depends_on: [redis]

Minimal environment contract (same keys local + cloud):

AUTO_SECRET=<event-signing secret>
ELFA_BASE_URL=https://api.elfa.ai/v2/auto
QUEUE_URL=<redis/sqs/pubsub endpoint>
RUNNER_MODE=local|cloud

Reliability checklist:

  • Store dedupe keys by eventId in durable storage
  • Retry policy with dead-letter queue
  • Keep webhook handler fast and non-blocking
  • Log decision input/output for every run
  • Health checks + alerting on worker lag

Recommended setup by stage:

StagePattern
PrototypeAuto Telegram + local worker
ProductionAuto webhook + queue + worker
Real-time operationsAuto SSE + worker service

Full detail: Notifications | Agent Runner

Notification troubleshooting
SymptomLikely CauseFix
400 / 401 when polling or streamingMissing/invalid API key or auth headersSend x-elfa-api-key
Webhook signature mismatchSigning wrong payload (not raw body), or hashing the secret before useKey is the raw signingSecret, payload is timestamp + "." + eventId + "." + rawBody. Do not use SHA256(secret) as the key
No signature headers on webhook deliveriesNo explicit signingSecret set on the webhook actionSet params.signingSecret and recreate the query
Duplicate downstream actionsNo idempotency on event processingDedupe by eventId before enqueue/execute
Event received but agent does nothingIngress processes inline and times outACK fast, push to queue, process in worker
SSE disconnect/reconnect loopsNo retry/backoff or unstable consumerAdd reconnect backoff + heartbeat monitoring
Missing triggers after some timeQuery expired or was cancelledPoll status; check expiresIn, status, latestEvaluation
"Why hasn't it fired yet?"Condition is close but not metRead latestEvaluation.conditionStates[] — compare currentValue vs targetValue, and matchingConditions vs totalConditions. Do not infer this from executions
"Why did it fire?"Need the value at trigger time, not the current valueRead the execution's trigger.matchedConditions[] — .match.observedValue vs .condition.value. latestEvaluation has since moved on
Signature timestamp rejectedRunner clock skewSync clock (NTP); enforce bounded replay window
Telegram bot cannot send messages in this chat at createBot isn't in the group, or the group revokes can_send_messages for itAdd the bot to the chat and grant send permission (or make it an admin), then retry
Telegram create fails for a groupchatId missing the leading -, or the target is a channelGroup IDs are negative (-1001234567890); channels are not supported
Validation errors — next action table

When /v2/auto/queries/validate (or Create) rejects, the error is almost always a phrasing issue, not a capability gap. Iterate on Validate instead of abandoning the query.

Error signalWhat it meansNext action
EQL_MISSING_ARGA required arg is absent (e.g. period on ema/sma, or exchange on any price/ta condition)Add the missing arg from the TA Args Contract, re-validate
Missing/invalid exchange on price/taEvery price/ta condition needs exchangeAdd exchange (hyperliquid or binance) to the condition args, re-validate
EQL_INVALID_ARG / type errorsWrong type ("14" instead of 14) or unrecognized key (length vs period)Use exact key names + JSON numeric types
Unknown methodIndicator name not supportedPick nearest supported method; ask Builder Chat to substitute
Unsupported timeframe / periodValue outside enumSnap to nearest allowed value
Unsupported symbol / sourceAsset not indexed or DEX pair unsupportedSkip symbol and report it; proceed with supported subset
Depth / leaf-count exceededMore than depth 3 or 10 leavesSplit into two queries joined by your runner
cron / llm period too shortBelow 1h minimumRaise to 1h or higher
tweet username, details.reason: paid_plan_requiredThe key has no paid API plan, and the account isn't in Elfa's real-time set (a misspelled handle lands here too on such a key)Check the spelling; use a paid API key, or pick an included account
tweet username, details.reason: not_foundNo X account has that handle, or the value isn't a handleCheck the spelling and pass the bare handle (no @)
tweet username, details.reason: protectedThe account is protectedPick a public account
tweet username, details.reason: capacity_reachedThe per-post account pool is fullRetry later, or pick an included account
tweet username on x402, EQL_INVALID_ARG_TYPEx402 watches only accounts in the real-time setPick another account, or use a paid API key
503 ERR_SERVICE_UNAVAILABLE, error.message: ACCOUNT_LOOKUP_UNAVAILABLEThe X account lookup (tweet or follow account) failed; the account may well existRetry after a few seconds
Invalid minConfidence (tweet / telegram / events)Non-integer or outside 0..100Use a JSON integer between 0 and 100 (start with 80)
events unknown / ambiguous entityNo entity matches, or several match equally (details.candidates)Set entityType, pass a candidate's publishedId as entity, or drop entity and use eventType + text
events with no filter and no text, or text/minConfidence/entityType unpairedAt least one filter or text; text and minConfidence together; entityType only with entityAdd a filter, or pair the args
events storylineId, "expected a storyline id"storylineId is not shaped like a storyline idCopy the id from an event's storylineId or a storyline's id
events "Unknown storylineId"No storyline has that idCheck it with GET /v3/events/storylines?storylineIds=<id>
calls handle, "Unknown X account" / "not an account Elfa extracts calls from"No X account has that handle, or Elfa does not record its calls, so the plan would never fireCheck the spelling; pick a caller from GET /v3/calls, or follow its posts with tweet
calls handle, "expected an X handle"Not letters, digits and underscores, at most 15Pass the bare handle
calls / mindshare asset, "Unknown asset" / "Ambiguous asset" / "not a token or equity"No token or stock matches; several match equally and none leads the others (details.candidates); or it resolved to a project, person or organisationSet market, pass the publishedId you mean, or name its token or stock
calls market, "'asset' is required"market without assetAdd asset, or drop market
calls direction / action out of setOften reaffirm or stop_outUse long/short, and open, add, trim, reverse, close or stopOut
calls with text or minConfidenceA calls condition matches by its filters, not by textDrop them; narrow with asset, direction or action
calls warning "… arrive hours after the post"Elfa collects the account with a delayExpect alerts hours after the call, or follow another caller
mindshare "is not one mindshare tracks", or market "is in the … market"The asset is on no mindshare board, or an id's market differs from marketPick another asset; drop market or pass the id in the market you mean
mindshare EQL_INVALID_METHOD / EQL_INVALID_OPERATOR on hot_*Often hot_1h; or an operator other than == / != on hot_*Use hot_4h for the last hour; use == true / == false
mindshare / calls warning "… can also mean …"asset resolved to the one that leads its market, but other assets match it just as closelyConfirm the resolved asset; otherwise set market or pass an id
x_follow_made / x_follow_overlap account, details.reason: paid_plan_required, not_found, protected, capacity_reached, too_many_follow_accountsFree key on an account Elfa doesn't monitor (a misspelling lands here too); no such account; protected; the per-day pool is full; more than 25 distinct accounts across the query's made/overlap conditionsCheck the spelling, use a paid key or x_follow_received or a monitored account; pick a public account; retry later; drop or split accounts
x402 follow account, "Elfa does not index this account" / "does not track this account's follows"x402 watches only accounts Elfa tracks or monitors for followsUse x_follow_received, a monitored account, or a paid API key
Unresolved telegram channelchatUsername / chatId does not resolve to a monitored public channel, or shorthand carried both identifiersUse one identifier on shorthand, drop @ and t.me/, and pick a monitored public channel
Invalid sec CIKargs.ticker is not 1–10 digits, or the CIK is absent from SEC submissions metadataPass the issuer CIK (not the stock ticker) and re-validate
EQL_INVALID_ACTIONThe query or draft uses market_order / limit_order, at top level or in an llm callbackReplace with webhook, notify, telegram_bot or llm, and run execution from your own runner
kalshi ticker not open (EQL_INVALID_ARG_VALUE on args.ticker)Ticker is not a currently-open Kalshi market (closed/settled/unknown)Resolve a currently-open full ticker and re-validate; don't guess tickers
kalshi invalid enum / operatorEnum value outside its set, or operator not in the method's allowlist (e.g. crosses_above on trade_size)Use the per-method operator allowlists and enum sets from the kalshi source reference
kalshi is_block_trade type errorvalue passed as a string instead of a JSON booleanUse JSON true / false, not "true" / "false"
polymarket ticker not found / not activeargs.ticker is not a live Polymarket outcome token (asset_id) — unknown or inactiveResolve a currently-active outcome-token asset_id and re-validate; don't guess ids
polymarket invalid operator / dynamic valueOperator not in the method's allowlist (e.g. crosses_above on size, > on side), or a dynamic (field-vs-field) value was usedUse the per-method operator allowlists; use a literal value (dynamic values are unsupported for polymarket)
cron.schedule cadence too fastMinute field isn't a single fixed value (e.g. */15 * * * *), or a sub-hour cadenceUse a single fixed minute — see the cron.schedule allowed/not-allowed examples
EQL_INVALID_REPEATrepeat was combined with a recurring cron condition (cron.every / cron.schedule) — both provide recurrenceRemove repeat for recurring cron (cron already recurs), or drop the cron condition
EQL_INVALID_ARG_VALUE with details.hip3: trueA HIP-3 (dex-prefixed) symbol was used on a funding / liquidation condition — those feeds don't publish HIP-3 keysUse a plain base symbol (BTC:BINANCE). HIP-3 stays valid on price/ta
Unknown symbol in a composite tickerSymbol not listed on that venue's perp catalog (often a naming mismatch)Check the venue's own base-symbol convention (1000PEPE on Binance/Bybit vs KPEPE on Hyperliquid)
SYMBOL_CATALOG_UNAVAILABLE (HTTP 503)Market catalog was momentarily unreachable, so the symbol couldn't be checked — validation fails closedRetry after the interval in the Retry-After header; this is transient — do not reshape the query
Dynamic value in action paramsDynamic values only allowed in condition valueMove dynamic reference into condition; keep action params literal

Cross-operator semantics: crosses_above = previous < threshold AND current >= threshold. crosses_below = previous > threshold AND current <= threshold. Both require previous-state tracking server-side.

Substitution ladder — if Auto doesn't fit

Before concluding a use case is out of scope, walk this ladder. Most intents resolve at rung 1 or 2.

  1. Rephrase through Builder Chat — append "If anything is unsupported, return the closest supported query and list substitutions" to your chat prompt.
  2. Iterate on Validate Query — loop: validate → reshape → re-validate. Do NOT jump to "this isn't possible" after one rejection.
  3. Split into multiple queries joined by your runner (for depth 3 / 10-leaf limits).
  4. Use source: "llm" with athena_condition for fuzzy/narrative predicates.
  5. Pre-compute in your own service, use Auto as control plane — only after rungs 1–4 fail.

Worked example — "alert on descending trendline break":

  • Rung 1: Rephrase as a supported proxy — "alert when BTC price crosses above 4h upper Bollinger Band AND 1h RSI > 55".
  • Rung 4: If the proxy isn't acceptable, use source: "llm" with a scheduled natural-language predicate — "has BTC broken its recent descending trendline on the 4h chart?".
  • Rung 5: Only if both fail: compute trendline-break externally, feed a boolean into an Auto cron + llm query as the condition trigger.

Do not build your own monitoring/evaluation/trigger stack before walking rungs 1–4.

Query drafts

Drafts let you stage an Auto query without activating it (and without spending credits). Useful for human-in-the-loop approval workflows, dashboards where users edit before committing, or batch authoring flows.

Draft lifecycle:

  1. POST /v2/auto/queries/drafts — create or update a draft (idempotent upsert).
  2. GET /v2/auto/queries/drafts — list editable drafts.
  3. POST /v2/auto/queries/drafts/:draftId/validate — validate stored draft.
  4. POST /v2/auto/queries/drafts/:draftId/convert — promote draft → active query.
  5. DELETE /v2/auto/queries/drafts/:draftId — discard draft.

GET /v2/auto/queries/drafts/:draftId still works but is legacy — prefer GET /v2/auto/queries/{queryId} which resolves both active queries and drafts.

Drafts are API-key-mode only. Not available via x402.

Symbols

Auto is not crypto-only. Crypto majors and on-chain tokens are plain uppercase tickers (BTC, ETH, SOL, HYPE, long-tail tokens). HIP-3 assets on Hyperliquid — tokenized equities, indices, commodities, FX, and pre-IPO names — use a DEX-prefixed form <dex>:<SYMBOL> and trade 24/7, even when the underlying cash market is closed.

Two DEX prefixes matter in practice: xyz lists nearly everything and carries almost all the volume; mkts lists a smaller overlapping set. Hyperliquid hosts other HIP-3 DEXs whose markets are dormant (listed but not trading) — Auto rejects them exactly like a typo (EQL_INVALID_SYMBOL). A symbol being listed on Hyperliquid is not enough; only the liquid listing is usable (GOLD appears on six DEXs). A bare HIP-3 symbol is rejected — {"symbol": "GOLD"} fails with EQL_INVALID_SYMBOL. Only plain crypto tickers work without a prefix (they live on Hyperliquid's main DEX).

ClassExamples
Crypto majorsBTC, ETH, SOL, HYPE (no prefix — main DEX)
On-chain / long-tail tokensRAVE, TAO, niche memes
Indices (HIP-3)xyz:SP500, xyz:XYZ100 (Nasdaq 100), xyz:JP225, mkts:US500, mkts:USTECH
Sector / country baskets (HIP-3)xyz:SMH (semis), xyz:XLE (energy), xyz:EWY, xyz:EWJ
Equities — mega-cap (HIP-3)xyz:NVDA, xyz:TSLA, xyz:AAPL, xyz:MSFT, xyz:GOOGL, xyz:META
Equities — semis / high-beta (HIP-3)xyz:AMD, xyz:TSM, xyz:ARM, xyz:COIN, xyz:MSTR, xyz:PLTR
Pre-IPO / private (HIP-3)xyz:SPCX (SpaceX), xyz:ZHIPU, xyz:MINIMAX
Commodities (HIP-3)xyz:CL (WTI crude), xyz:BRENTOIL, xyz:NATGAS, xyz:GOLD, xyz:SILVER, xyz:COPPER
FX (HIP-3)xyz:EUR, xyz:JPY, xyz:GBP

Illustrative, not exhaustive — and listings change. Markets are added and go dormant. Never hardcode this list: confirm any HIP-3 symbol with GET /v2/auto/validate-symbol/{exchange}/{symbol} before building on it, and read a rejection as "that market is dead", not "Auto is missing the asset". HIP-3 market data comes from hyperliquid only — HIP-3 markets are listed only on Hyperliquid, so a HIP-3 symbol paired with any other exchange value is rejected.

Composite tickers (funding / liquidation only): those two sources do not take a symbol + exchange arg — they take a single SYMBOL:EXCHANGE ticker (e.g. BTC:BINANCE, ETH:HYPERLIQUID, SOL:BYBIT). Use each venue's own base-symbol convention: 1000PEPE on Binance/Bybit vs KPEPE on Hyperliquid. HIP-3 symbols are rejected for these two sources.

Tracking coverage: conditions, alerts and webhooks cover DEX/on-chain assets — effectively unbounded (long-tail tokens, pre-CEX-listing assets, niche memes). Symbol support is per venue, so pre-flight anything unusual with GET /v2/auto/validate-symbol/{exchange}/{symbol} (which checks hyperliquid or binance) before building a price/ta condition on it. If a symbol is unsupported on the venue you picked, switch venue or symbol — or keep the condition on a supported pair and route follow-up work through notify, webhook or telegram_bot.

Full reference: Symbols and Catalyst Triggers.

Executions

Every trigger fire produces an execution record — use these endpoints to audit trigger history, debug failed actions, or reconcile with your runner's audit log.

EndpointMethodDescription
/v2/auto/executionsGETList execution records (filterable)
/v2/auto/executions/:executionIdGETGet a single execution record

Execution records are also embedded in the poll response (GET /v2/auto/queries/:queryId) under the executions array, but the dedicated endpoints are useful for cross-query audits and pagination.

Executions are API-key-mode only. Not available via x402.

Step 4: Generating code snippets

When the user wants integration help, generate correct, production-ready code. See the Elfa API documentation for the full parameter specs.

Principles for code generation:

  • For /v2, always mention both access modes (API key and x402) so developers know their options
  • For /v3, the API key is the only mode (no x402). Use from/to in unix seconds (there is no timeWindow; mindshare takes period), page with nextCursor → cursor, and read the list at the top level of the response. See Step 2d
  • Include the signup link https://go.elfa.ai/claude-skills as a comment near the API key placeholder, and link to https://docs.elfa.ai/get-started/x402 for x402
  • Always include proper error handling
  • For API key mode: show the x-elfa-api-key header (use a placeholder like YOUR_API_KEY)
  • For x402 mode: show the /x402/v2/ prefix and recommend @x402/fetch or @x402/axios
  • For Auto mutations in x402 mode: include the agent-secret header
  • Include TypeScript types when generating TS code
  • Add comments explaining each parameter
  • For pagination endpoints, show how to paginate through results
  • For /v2 time-windowed endpoints, explain the timeWindow vs from/to pattern

Language priorities (use unless the user specifies otherwise):

  1. TypeScript/JavaScript (fetch) — most Elfa integrators are web/Node devs
  2. Python (requests)
  3. curl

The Chat endpoint deserves special attention — it's the most complex:

  • It supports multiple analysisType values: chat, macro, summary, tokenIntro, tokenAnalysis, accountAnalysis
  • Session management via sessionId for multi-turn conversations
  • Different assetMetadata requirements per analysis type (message is required only for analysisType: "chat")
  • Three speed modes: fast, expert (default), and adaptive (/v2/chat only)
  • Two transports: POST /v2/chat (JSON) and POST /v2/chat/stream (SSE, PAYG/Enterprise) — same body, different plan gate. See Step 2c for the SSE event types.

Auto code generation guidance:

  • Always include the validate → create flow (never create without validating first)
  • For API key mode: send x-elfa-api-key on every call, including mutations
  • For x402 mode: include x-elfa-agent-secret header on all query lifecycle calls
  • Include Builder Chat examples when the user wants natural-language query building
  • Show how to poll or stream for results after query creation
Common patterns

These patterns are for /v2. API v3 follows the rules in Step 2d.

Time window parameters: Many endpoints accept either timeWindow (e.g., "30m", "1h", "4h", "24h", "7d", "30d") OR from/to unix timestamps. If both are provided, from/to takes priority.

Pagination: Aggregation endpoints (trending-tokens, trending-cas, top-mentions, token-news) use page + pageSize. The keyword-mentions endpoint uses cursor-based pagination instead (cursor + limit).

Defaults differ per endpoint — do not assume a global default. From the current spec:

EndpointtimeWindowpagepageSize / limitOther
trending-tokens7d150minMentions (default 5)
trending-cas/twitter · trending-cas/telegram7d150minMentions (default 5)
top-mentions1h110ticker required, reposts
token-news7d120coinIds, reposts
keyword-mentions7d— (cursor)limit 20, max 30keywords, accountName, searchType, cursor, reposts
event-summary7d——keywords required, searchType (default or)
trending-narratives———timeFrame (day | week, default day), maxNarratives (default 7, range 1–20), maxTweetsPerNarrative (default 5, range 1–20)
smart-stats———username required

Every endpoint above except trending-narratives and smart-stats also accepts from/to unix timestamps, which take priority over timeWindow.

Per-endpoint parameter notes:

  • keyword-mentions — provide either keywords (up to 5, comma-separated) OR accountName (or both); accountName filters mentions by a specific account (e.g. accountName=elonmusk). searchType accepts and (all terms must be present) or or (any term matches). Cursor-paginated — pass metadata.cursor from the previous response as cursor. Returns account.username, which feeds directly into smart-stats.
  • event-summary — keywords is required; searchType defaults to or. A 503 means the upstream model declined to process the matched posts — retryable, and not an empty result. The 4 fixed credits are not charged; the 1 base per-request credit still is.
  • token-news — V2 always returns news mentions (no isNews parameter). Results are X posts from accounts tagged as news sources, not articles published by news outlets.
  • top-mentions — ticker is required. Account details are always included (no includeAccountDetails parameter).
  • smart-stats — the query param is username, not accountName.
  • minMentions (trending-tokens / trending-cas) — floor on mention count; raise it to cut long-tail noise.
  • reposts (keyword-mentions / top-mentions / token-news) — boolean toggle for including repost activity.

Ticker format (top-mentions): The ticker parameter behavior changes based on whether you include the $ prefix:

  • With $ prefix (e.g., ticker=$SOL or URL-encoded ticker=%24SOL): Exact cashtag matching only. Searches the cashtags field for exact matches.
  • Without $ prefix (e.g., ticker=SOL): Broader search across both cashtags (boosted 2x) and general token references.

Use $ when you want only cashtag-specific mentions. Omit $ for a more inclusive search.

Credit costs (data endpoints — both modes):

  • Most endpoints: 1 credit per call ($0.0145 via x402)
  • Event summary: 5 credits ($0.0725 via x402)
  • Trending narratives: 5 credits ($0.0725 via x402)
  • Chat: speed-based; via x402 pay exact (fast $1, expert $2) or batch-settlement (up to fast $2, expert $6)
  • API v3: free on the Pay-as-you-go and Grow plans until 31 October 2026. API key only
  • /v2/ping, /v2/key-status: free
  • Every /v2/* and /v3/* response to an authenticated request carries an x-elfa-credits header with the credits that request consumed — read it instead of diffing /v2/key-status around a call. It is present on error responses raised after the key is accepted, and absent when the request is rejected before the key resolves, because nothing was charged. Streaming responses and charges that finalize after the response is flushed carry the pre-flush subtotal, so /v2/key-status stays the authoritative balance for those.

Auto query lifecycle:

  • Validate before create (recommended): Call POST /v2/auto/queries/validate first to preview estimated credits and catch validation errors before committing. The server validates internally during create anyway, but pre-validation lets you iterate on errors without side effects.
  • Prefer webhook or SSE for real-time delivery
  • Deduplicate events by eventId
  • Use shorter expiries (24h–3d) for fresh signals
  • Include title and description in queries — they appear in notifications

Important notes

  • The Elfa API domain (api.elfa.ai) must be accessible from the network. If blocked, inform the user and provide the code snippet instead.
  • Use the v2 endpoints (paths starting with /v2/ or /x402/v2/) and the API v3 endpoints (/v3/).
  • For experimental endpoints (trending-tokens), mention that behavior may change without notice. /v2/account/smart-stats is legacy: it still works, but will be removed on 28 October 2026 — say so before building on it. /v2/data/market-events is no longer part of the public API: market events are served by API v3 Events (/v3/events).
  • /v2/data/event-summary can return 503 (ERR_SERVICE_UNAVAILABLE, 1 credit instead of 5) when the matched posts cannot be summarised. Retry, or narrow the keywords or the window. A 503 never means "no events": a quiet window is a 200 with an empty data array.
  • Measurement endpoints return no raw tweet text and no sentiment field — do not promise either. Fetching post content requires the user's own X API key.
  • Auto no longer accepts order actions (market_order / limit_order) on new queries or drafts — Auto rejects them with EQL_INVALID_ACTION. Trigger a webhook, notify or telegram_bot action and run execution from your own runner.
  • When the user asks about pricing or API key tiers, direct them to https://go.elfa.ai/claude-skills for full details on plans and pricing.
  • API-key request rate limits are per tier: Free / PAYG = 60 requests/min, Grow = 120 requests/min (Enterprise custom). These are independent of credit balances. A 429 response should be retried honoring Retry-After.
  • x402 is currently in beta. Rate limits: 1,000 requests per 60s window (per client IP).
  • Install/update this skill with npx skills add elfa-ai/skills. API keys come from the Elfa Developer Portal.
  • x402 and API key credits are independent — they do not overlap or share balances.
  • For x402 documentation and setup, refer users to https://docs.elfa.ai/get-started/x402.
  • For Auto documentation, refer users to https://docs.elfa.ai/auto/overview.
  • Auto x402 agent secret must be persistent — do not rotate per request.
  • For the full list of Auto capabilities, triggers, and query templates, see https://docs.elfa.ai/auto/capabilities and https://docs.elfa.ai/auto/query-model.

コメント