API authentication and limits
API keys, plans and credits, paying, rate limits and their errors, the WebSocket's caps, and the headers that report what is left.
Keys, plans and limits, and the service's own status.
Every data route and the WebSocket need an account: there is no access without credentials. Your account gets 10,000 free credits once, and all your keys spend them.
Get an API key
- Sign in with Google on the sign-in page.
- On your account, create a key and copy it: it is shown once. Keys start with
mb_.
You can hold 10 active keys. Revoking one takes effect everywhere within 5 s. Keys can be created once the console has registered your device, which it does right after you sign in.
Sending credentials
| Where | Carries | Notes |
|---|---|---|
Authorization: Bearer … | An API key or a JWT | Checked first. A JWT is a 15-minute token from GET /auth/token ({"token":"<jwt>"}) for a signed-in session, verifiable with /auth/jwks (EdDSA). |
x-api-key: … | An API key | Checked second. |
| Session cookie | Your console session | Sent by the browser on its own, on this site only. |
apiKey=… or jwt=… in the query | An API key or a JWT | The WebSocket only: browsers cannot set headers on one. |
A request without credentials, or with a bad, expired or revoked key, JWT or session, is refused with 401. Refusals are counted per IP: past 20 at once (5 a second), they answer 429.
Plans
| Plan | Who | Credits | Burst | WebSocket |
|---|---|---|---|---|
free | An account without paid credit | 10,000 free credits, given once | 5 a second, 20 at once | 5 sockets, 25 subscriptions each |
paid | An account with paid credit left | The free credits first, then paid credit | 30 a second, 120 at once | 5 sockets, 100 subscriptions each |
The burst limit is a bucket refilling its rate each second and holding 4 seconds' worth, so a page's first batch of requests goes through. /, /health, /plans, /logos/* and /auth/* are open and not metered; GET /plans answers these limits as JSON, the WebSocket caps included, so read them there rather than hardcode them.
Credits
A request costs credits. Most cost 1, candles and search included. Wallet analysis (GET /wallets/:chain/:address/analysis) costs 3. A token or price batch (GET /tokens?tokens=, GET /prices) costs 1 per 25 items, and GET /wallets?wallets= 1 per wallet. On the WebSocket, opening a socket and each subscription cost 1, and every data message it receives costs 0.3. The burst limit counts requests, whatever they cost.
Your account gets 10,000 free credits once, when you first sign in. They are yours to spend however you like, on any endpoint or socket, as fast or as slowly as you want. They do not renew and there is no daily allowance. Each device gets them once, and a network only a few times: an account refused them can still add credit. The free credits are always used first. Your account page shows what you have left, and what you spent per endpoint and per key.
| One deposit | Per 1,000 credits | Example |
|---|---|---|
| $5 to $279.99 | $0.28 | $50 buys 178,571 credits |
| $280 or more | $0.224 | $280 buys 1,250,000 credits |
Paying
Your account page gives you your own deposit address. Send it USDC on the Solana network only: SOL and other tokens are not credited, and the address needs no SOL. There is no subscription: you buy credits when you want, and they do not expire. Your first deposits are credited together once they total $5, and until then the page shows your last deposit, your total and how much more is needed. After that, each deposit is credited within about 5 minutes. Each deposit is priced on its own size, so send a large amount in one go. Credit is prepaid and not refundable. While you have paid credit your plan is paid; once it runs out you are back on free, and requests answer 402 until you add more.
The WebSocket
- Opening
/livecosts one credit, and so does everysubscribeon it: a subscription is metered like the REST request it replaces, and refused like one (429 rate_limitedwithretryAfter,402 insufficient_balance), as anerrormessage for that subscription. Each data message it then sends costs 0.3 credits. - Open sockets per account, subscriptions per socket and
balancessubscriptions per socket are capped by plan (liveinGET /plans). Past a socket's caps a subscribe is refused with429 too_many_subscriptionsor429 too_many_balances; unsubscribing frees the place. - A socket over the open-sockets cap, or opened with a refused credential, is closed with a code and a JSON reason:
4401unauthorized,4402out of credit,4429rate limited ortoo_many_connections. A signed-in caller's credential is checked again every 5 s; the socket closes with4401once it no longer holds (a revoked key or session, an expired JWT: reconnect with a fresh one).
What is left, on every answer
| Response header | Meaning |
|---|---|
X-Aurelia-Plan | free or paid |
X-Aurelia-Credits | The credits you have left, free and paid |
Headers of a request with a key, captured on a test instance:
curl -si -H 'Authorization: Bearer mb_YOUR_KEY' 'https://app.aurelia.build/api/chains' | grep -iE '^(x-|access-control-allow-origin)'
Access-Control-Allow-Origin: *
X-Aurelia-Plan: free
X-Aurelia-Credits: 8738
Refusals
| Status | Body | When |
|---|---|---|
| 401 | {"error":"unauthorized"} | No credentials, or a bad, expired or revoked key, JWT or session |
| 402 | {"error":"insufficient_balance"} | Your credits left cannot pay for the request. Add credit on your account page. |
| 429 | {"error":"rate_limited","retryAfter":<s>} | Over the burst limit. Wait retryAfter seconds, also sent as Retry-After. |
No key, and a bad key, captured on a test instance:
curl -s -w ' %{http_code}\n' 'https://app.aurelia.build/api/chains'
{"error":"unauthorized"} 401
curl -s -w ' %{http_code}\n' -H 'Authorization: Bearer mb_notarealkey' 'https://app.aurelia.build/api/chains'
{"error":"unauthorized"} 401
100 requests at once with a free key, captured on a test instance:
status counts of 100 parallel GET https://app.aurelia.build/api/chains
{"200":20,"429":80}
# a free bucket holds 20 and refills 5 a second, so 20 pass;
# each 429 answers {"error":"rate_limited","retryAfter":1} with Retry-After: 1
A socket with a bad key, and a 26th subscription on a free account's socket, captured on a test instance:
wss://app.aurelia.build/api/live?apiKey=mb_notarealkey
closed with code 4401, reason {"error":"unauthorized"}
{"id":"p26","type":"error","status":429,"error":"too_many_subscriptions"}
With your key
These need a real key: replace mb_YOUR_KEY.
A request with your key, curl:
curl -s -H 'Authorization: Bearer mb_YOUR_KEY' \
'https://app.aurelia.build/api/tokens/solana/So11111111111111111111111111111111111111112'
A request with your key, JavaScript:
const res = await fetch("https://app.aurelia.build/api/tokens/solana/So11111111111111111111111111111111111111112", {
headers: { Authorization: "Bearer mb_YOUR_KEY" },
});
if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
const data = await res.json();
console.log(data);
A request with your key, Python:
import requests
r = requests.get(
"https://app.aurelia.build/api/tokens/solana/So11111111111111111111111111111111111111112",
headers={"Authorization": "Bearer mb_YOUR_KEY"},
timeout=10,
)
r.raise_for_status()
print(r.json())
The socket with your key, JavaScript:
// On a server (Bun, Node 22+), where the key stays private:
const ws = new WebSocket(`wss://app.aurelia.build/api/live?apiKey=${process.env.AURELIA_KEY}`);
// In a browser a key in a URL can end up in logs and history: prefer a short-lived JWT.
const { token: jwt } = await (await fetch("/api/auth/token")).json();
const live = new WebSocket(`wss://app.aurelia.build/api/live?jwt=${jwt}`);
live.onopen = () => live.send(JSON.stringify({ op: "subscribe", id: "sol", channel: "token", chain: "solana", token: "So11111111111111111111111111111111111111112" }));
The socket with your key, Python:
import asyncio, json, os
import websockets
async def main():
async with websockets.connect(f"wss://app.aurelia.build/api/live?apiKey={os.environ['AURELIA_KEY']}") as ws:
await ws.send(json.dumps({"op": "subscribe", "id": "sol", "channel": "token", "chain": "solana", "token": "So11111111111111111111111111111111111111112"}))
print(await ws.recv()) # {"type":"subscribed",…}
asyncio.run(main())
Your account
These routes serve the console's account page. They take the session cookie only, not a key.
| Route | Does |
|---|---|
GET /account | Your user, plan and limits, your credits left (credits: free, freeLeft, left, refusedReason), whether this device is registered, and the last 30 days' usage per endpoint and per API key, in requests and credits |
POST /account/device | Registers this device, which decides your free credits and unlocks key creation (the console does it after sign-in) |
GET /account/billing | Your deposit address, balance and deposits |
GET /account/keys | Your keys: id, name, prefix, createdAt, lastUsedAt (never the key) |
POST /account/keys | Creates a key from { name }; the answer holds the full key, once. 403 device_required or key_limit |
DELETE /account/keys/:id | Revokes a key |
GET /account/billing | Your deposit address, deposited, credited and spent totals (spent past the free credits), balance, paid credits left, and deposits (newest first, up to 100). 503 address_unavailable while the address is being prepared |