API v3 New
API v3 is the data stack that powers agent awareness. Elfa monitors news and market chatter worldwide, across crypto and equities, and turns it into events, positions and rankings, so your agents get the latest real-time context and react faster when the stakes are high.
| Question | Endpoint (GET) | Returns |
|---|---|---|
| Which id is this? | /v3/entities | The id every other endpoint takes, from a ticker, name, handle, address, CoinGecko id, FIGI or CIK |
| What is happening? | /v3/events | Events: what happened, what it names, how prices reacted. Searchable by topic |
/v3/events/storylines | One development followed over days | |
/v3/events/arcs | Storylines grouped into a larger movement | |
| Where is attention? | /v3/market/crypto/mindshare, …/equity/mindshare | Assets ranked by share of attention, with hot |
/v3/market/crypto/trending-contracts | Contract addresses ranked by how many posts pasted them | |
| Which way does the crowd lean? | /v3/market/crypto/call-book, …/equity/call-book | The whole market's long/short lean, hour by hour |
/v3/calls/positioning | Open longs and shorts on one asset, now | |
| What are accounts doing? | /v3/calls | Every post that takes or changes a position |
/v3/calls/episodes | Each position from open to close, with prices and the author's track record | |
| Who is worth reading? | /v3/people/accounts | Accounts ranked by standing |
| What can my key do? | /v3/key-status | Plan, credits, limits and how far back it reads |
No endpoint returns post text. Events carry a written title and summary, and link to each post behind them.
New to the terms? Concepts shows each one with a real example.
Quickstart
Resolve Bitcoin, read its last day of events, then see how accounts are positioned on it.
BASE=https://api.elfa.ai
# 1. Resolve. Check the name: the first match may not be the asset you meant.
ID=$(curl -s "$BASE/v3/entities?q=btc&entityType=token&limit=1" \
-H "x-elfa-api-key: $ELFA_API_KEY" | jq -r '.entities[0].id')
# 2. Its events of the last 24 hours. from is required, in seconds.
# An empty list is a quiet range, not an error.
FROM=$(( $(date +%s) - 86400 ))
curl -s "$BASE/v3/events?entityIds=$ID&from=$FROM&limit=10" \
-H "x-elfa-api-key: $ELFA_API_KEY"
# 3. Open longs and shorts on it right now.
curl -s "$BASE/v3/calls/positioning?entityId=$ID" \
-H "x-elfa-api-key: $ELFA_API_KEY"
See Choosing a Match before you rely on step 1, and Workflows for longer recipes.
Rules Every Endpoint Follows
| Rule | In short |
|---|---|
| Authentication | Send x-elfa-api-key on every request |
| Access | PAYG, Grow and Enterprise keys |
| Times | Unix seconds, UTC, in requests and responses. Milliseconds are a 400: divide by 1000 |
| Time ranges | from is required, to defaults to now, both inclusive |
| History | 30 days, 60 days or 2 years back, by plan. An earlier from is cut, and servedFrom says where |
| Paging | Pass nextCursor back as cursor. No offset, no total |
| Lists | Parameters marked a list take several values, repeated or comma-separated. At most 30 ids |
| By id | eventIds, storylineIds, arcIds, callIds and episodeIds fetch up to 30, with no from |
| Errors | A bad parameter, or an unknown id in a filter, is a 400. Nothing matching is a 200 with an empty list |
Authentication
Send x-elfa-api-key: <your key> on every request. A 401 says why it failed:
message | Fix |
|---|---|
API key is required | Send the header |
Invalid or expired API key | Check the key. A mistyped or revoked key gets this |
Error validating API key | Our side. Retry |
A 503 ERR_SERVICE_UNAVAILABLE means the key could not be checked just then,
or, with q, that search is down.
It says nothing about the key. Retry after a few seconds.
Access
PAYG, Grow and Enterprise keys can call every v3 endpoint. A Free key
can call only /v3/key-status. Get a key, or upgrade, in
the Developer Portal. Each 403 names its next step:
403 reads | When | Do |
|---|---|---|
The v3 API is free on the Pay-as-you-go and Grow plans. Upgrade at … | A Free key, on any endpoint but /v3/key-status | Upgrade in the Developer Portal |
API key does not have the required scope: … | A paid key without this endpoint | Email [email protected] with the requestId |
Rate Limits
A per-minute limit per key, set by the plan and shared with your /v2
requests. /v3/key-status returns it as
credits.requestsPerMinute, and responses carry it in headers:
RateLimit-Limit: 60
RateLimit-Remaining: 59
RateLimit-Reset: 42
Over it, you get 429 ERR_RATE_LIMIT_EXCEEDED: wait RateLimit-Reset
seconds. See Rate Limits for the limits per plan.
Time Ranges
| Parameter | Value |
|---|---|
from | Unix seconds. Required, except when fetching by id or searching |
to | Unix seconds. Defaults to now |
There is no default range: leaving out from is a 400, from is required.
Some endpoints read time their own way:
- Mindshare takes a
periodand an optionalto. - Call episodes match a position open at any point in the range.
- Events with
sort=relevantrank the last 24 hours. - A search with
qstarts at your history by default. - Trending contracts read 30 days at most.
- Entities, arcs, positioning and people take no range:
fromis a400.
History
| Plan | Reads back |
|---|---|
| PAYG | 30 days |
| Grow | 60 days |
| Enterprise | 2 years |
Counted from midnight UTC. /v3/key-status returns the
start as historyFrom.
from before historyFrom -> cut to historyFrom, and servedFrom says so
to before historyFrom -> 400 "This plan answers from 1788307200 onwards."
an id older than it -> skipped when you fetch by id
servedFromis on every list that takesfrom. It isnullwhen you fetch by id withoutfrom, and on events withsort=relevant.- Storylines, arcs, call episodes and mindshare apply the limit as their pages say.
- The limit moves at midnight UTC. A cursor from a range it cut then stops working: start again from the first page.
Paging
{
"events": [ … ],
"nextCursor": "TUHFseJr.MjAy…",
"hasMore": true,
"servedFrom": 1790672305
}
- Pass
nextCursorback ascursor.hasMore: falseandnextCursor: nullmark the last page. There is no offset and no total. - A cursor belongs to its query. Change anything but
limitand it is a400,Invalid cursor…. Start again without it. Treat a cursor as opaque. - The list is named for what it holds:
events,storylines,arcs,entities,bars,assets,contracts,calls,episodes,accounts. orderisdesc(default) orascwhere an endpoint takes it. Entities, mindshare and trending contracts take none.
| Endpoints | Default limit | Most |
|---|---|---|
| Events, storylines, arcs, calls, call episodes | 10 | 30 |
| Call books, mindshare, trending contracts, top accounts | 50 | 200 |
| Entities | 10 | 200 |
Lists of Values
?entityIds=<id1>,<id2>
?entityIds=<id1>&entityIds=<id2>
Only parameters marked a list take several values: eventClass=market,asset
is a 400. A list of ids takes at most 30, and entityIds matches any of
them.
Fetching by Id
eventIds, storylineIds, arcIds, callIds and episodeIds take up to 30
ids you already hold:
- No
from, and no next page: everything found comes back at once. - An id we do not hold, or one older than your history, is skipped, not an error.
- Other filters still apply, except with
eventIds, which combines only withalertAction,orderandsort=latest.
Errors
Every error has the body { code, message, details?, errorId, requestId }.
Branch on code or the status, not on message. The common ones:
| You sent | Answer | Do |
|---|---|---|
No from | 400, from is required | Send from |
| A range wholly before your history | 400, This plan answers from … onwards. | Move to later |
| An unknown parameter | 400, Unrecognized: … in details | Fix or drop it. Unknown parameters are never ignored |
A bad value: an unknown enum, milliseconds, from after to, a malformed id | 400 naming the field in details | Fix that field |
| A cursor from another query | 400, Invalid cursor… | Start again without it |
limit over the most | 400, limit must be 30 or less | Lower it |
| An entity id that is not a UUID | 400, must be an id from /v3/entities | Resolve it with /v3/entities |
| A UUID we do not hold | 400, Unknown id: … | Resolve it again |
| A person, project or organisation where an asset is needed | 400, … is not an asset family. | Resolve with entityType=token or equity |
| A handle we do not hold, on calls or episodes | 400, Unknown handle: … | Check the spelling |
| A filter that matches nothing | 200, an empty list | Widen the range |
q while search is down | 503, Search is unavailable right now… | Retry, or drop q |
Field Reference lists every status, and which are worth retrying.
Key Status
GET /v3/key-status: what your key can do and what it has left. No scope, no parameters, and it
answers even when credits run out.
{
"plan": "payg",
"billingMode": "deposit",
"owner": "user",
"scopes": ["key-status", "chat", "events", "calls", "entities"],
"credits": {
"limit": null,
"used": 407,
"remaining": 2223,
"resetsAt": null,
"requestsPerMinute": 60
},
"historyFrom": 1788307200
}
A PAYG key, scopes trimmed to 5 of its 25.
| Field | Meaning |
|---|---|
plan | payg, grow or enterprise. A Free key reads payg, with no v3 scope in scopes |
billingMode | deposit: a prepaid balance. arrears: a subscription, billed after use |
owner | user or org: whose account these numbers are. Its keys share them |
scopes | Every scope the key holds, /v2 included. They follow the plan |
credits.limit, credits.resetsAt | The monthly limit and its reset (1st of the month, UTC). null on prepaid |
credits.used | Credits used this month, UTC |
credits.remaining | Prepaid: the balance. Subscription: limit minus used, never below 0 |
credits.requestsPerMinute | The per-minute limit on this key |
historyFrom | The earliest time this plan reads. See History |
Sending any parameter is a 400. An Enterprise key not set up yet gets 403,
Enterprise API keys require explicit configuration…: email
[email protected] with the requestId.