# Aurelia Aurelia is an onchain token data API at https://aurelia.build. It is not the Aurelia JavaScript framework. AI agents can use it directly through the MCP server at https://app.aurelia.build/mcp: https://app.aurelia.build/guides/mcp-server.md > Aurelia indexes every swap on BSC, Solana, Base, Robinhood Chain and Ethereum itself and serves token lists (batch, search, screener, dev history), prices (now or at a time), candles, one trade feed, token details with security checks and holders, token traders, wallet portfolios (with liquidity positions, labels and first funding), positions, value history, activity and PnL, wallet discovery by label, and one WebSocket with channels for trades, candles, token and pool pages, holders, balances, positions and Pulse lists. Every endpoint is a GET under https://app.aurelia.build/api. Every request needs an account (a free one is enough): sign in at https://app.aurelia.build/#/login, create a key at https://app.aurelia.build/#/account and send `Authorization: Bearer `. Without one the API answers 401. A server can give its users a 15-minute JWT instead of the key: `POST /auth/token`, https://app.aurelia.build/docs/auth-token.md. An account gets 10,000 free credits once, to spend on anything; they do not renew and there is no daily allowance. Most requests cost 1 credit and a WebSocket message 0.01; then 1,000 credits cost $0.28, or $0.224 on a single deposit of $280 or more, paid in USDC with no subscription. Every answer reports the credits left in `X-Aurelia-Credits`. Out of credits the API answers 402 (a socket closes with 4402). Over the burst limit it answers 429 with `retryAfter` seconds; wait that long. GET /plans states every limit, the WebSocket caps included. - Base URL: https://app.aurelia.build/api. The console at https://app.aurelia.build forwards /api to the API, which listens on http://localhost:7070 locally and on its own domain in production. - Chains: `evm:56` (BSC: 0x addresses, lowercase in answers), `solana` (base58 addresses, case-sensitive), `evm:8453` (Base: 0x addresses, lowercase in answers), `evm:4663` (Robinhood Chain: 0x addresses, lowercase in answers) and `evm:1` (Ethereum: 0x addresses, lowercase in answers). `chains=` defaults to these. Native BNB, ETH or SOL is the token address `native`. - Amounts are decimal strings with decimals applied; USD values are numbers; times are unix ms, but candles use unix seconds. - A price is the base token's. A trade row is told from one token's side (the token asked for, else the swap's base): `buy` means the trader received it. - Lists take `limit` and `cursor`: pass the answer's `next` back as `cursor`; `null` is the end. A filtered page can be short before the end: follow `next`. - Every row that names a wallet carries its `labels` (creator, insider, sniper, smart_money, bot, fresh), each derived from indexed data by a stated rule. - A token older than the index: its candles and unfiltered trade feed may also hold rows marked `imported`, from a third-party provider, from before Aurelia's first trade of it. `token.history` says what is held for a token and why. - Wallet figures cover only swaps indexed since the answer's `indexedSince`; PnL is by weighted average cost, and a null PnL is unknown, never 0. The portfolio reads balances from the chain. - Answers state what they cover (`coverage`, `complete`, `exactSince`, `unknown`): Solana wallet activity and history are read from the chain, bounded per request; BSC value history leaves out native BNB; holders are complete only where they can be; a security check that could not run is null with its reason in `unknown`. - Batch lists (`tokens=` on /tokens and /prices, `wallets=` on /wallets) are `:
` items, split on the last colon; an unknown token is null in its place. - WebSocket: one socket, `/live`; send `{"op":"subscribe","id","channel","chain",…}`; messages carry the id; an `error` ends that subscription only. Each subscribe is metered as one request; subscriptions per socket are capped by plan. - Errors are JSON `{"error": "…"}`: 400 for a bad parameter, 404 for an unknown token or pool, 422 when an answer could not be exact, 502 when the chain cannot be read, 503 (with Retry-After) while a chain's read budget is spent. An unknown route answers plain `NOT_FOUND`. This file is generated from the same source as the docs at https://app.aurelia.build/#/docs. Every example response was captured from the running API, at the time its heading gives (those marked "on a test instance" from a local run over test data, before the endpoint was deployed); lists are trimmed to 1–3 items. ## Start ### Basics #### Base URL | Where | Base URL | | |---|---|---| | Through the console | `https://app.aurelia.build/api` | Same origin as these docs: the console forwards `/api/*` to the API, WebSocket included. The examples use it. | | The API, locally | `http://localhost:7070` | The API's own port, published on loopback by docker compose. | | The API, in production | `https://` | The domain your deployment gives the API service. | All endpoints are `GET`, take no body and answer JSON, apart from [`POST /auth/token`](https://app.aurelia.build/docs/auth-token), which makes a short-lived token with your key; live data is one WebSocket, [`/live`](https://app.aurelia.build/docs/live). Data routes, `/plans` and `/health` allow any origin (CORS), so a web page on another site can call them with a [short-lived token](https://app.aurelia.build/docs/auth-token); the console's own session cookie only works on its own origin. #### Chains and addresses | Chain | Id | Addresses | |---|---|---| | BSC | `evm:56` | `0x` and 40 hex digits. Answers use lowercase; a checksummed address is accepted. | | Solana | `solana` | Base58, case-sensitive: send it exactly. | | Base | `evm:8453` | `0x` and 40 hex digits. Answers use lowercase; a checksummed address is accepted. | | Robinhood Chain | `evm:4663` | `0x` and 40 hex digits. Answers use lowercase; a checksummed address is accepted. | | Ethereum | `evm:1` | `0x` and 40 hex digits. Answers use lowercase; a checksummed address is accepted. | A chain is named in the path (`/tokens/:chain/:address`), in a query (`/trades?chain=`, `chains=`) or in a batch item. Native BNB, ETH or SOL has the address `native` wherever a token object names it. Addresses are not format-checked: a malformed one reads as unknown. Batch lists (`tokens=` on [`/tokens`](https://app.aurelia.build/docs/tokens) and [`/prices`](https://app.aurelia.build/docs/prices), `wallets=` on [`/wallets`](https://app.aurelia.build/docs/wallet-list)) are comma-separated `:
` items, split on the last colon: `evm:56:0xbb4c…`, `solana:So111…`. An unknown token comes back `null` in its place. #### Units - Token amounts are decimal strings with decimals applied, such as `"0.109729208291499538"`. They are exact: parse them with a decimal library if you do arithmetic. - USD values and prices are JSON numbers. Price changes and shares are fractions: `0.05` is 5%. - Times (`time`, `…At`, `at`) are unix milliseconds. Candles use unix seconds: `from`, `to` and each bar's `t`. - Blocks are block numbers on BSC and slots on Solana. #### Base, quote and side Every swap has a base, the token being priced, and a quote. The quote is picked by fixed priority: a stablecoin, then the wrapped native token (WBNB, WSOL), then the token that was priced first, then `token0`. A pool keeps the same base, so its price and candles are one series. USD prices come from direct trades: $1 for a stablecoin; for WBNB, WSOL and other tokens, a price set by recent trades against a stablecoin or the native token. A swap with neither is priced through its quote's recent price, else its base's; without one it stays unpriced (`priced: false`) rather than guessed. A trade is told from one token's side: the token asked for (`token=`), else the swap's base. `buy` means the trader received that token, `sell` that they paid it. Token stats count the swaps where the token was the base; traders count both sides. #### Pagination Lists take `limit` and `cursor`, and end with `next`: pass it back as `cursor` for the next page. `next` is an opaque string, `null` at the end. A filtered page can come back short, or empty, before the end: it examined a bounded part of the list, and `next` continues after it. Follow `next` until it is `null`. Two pages: ```bash curl -s 'https://app.aurelia.build/api/trades?chain=evm:56&token=0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777&limit=2' # … "next": "1790618554904.1000749.0.4" curl -s 'https://app.aurelia.build/api/trades?chain=evm:56&token=0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777&limit=2&cursor=1790618554904.1000749.0.4' ``` #### Wallets in lists Every row that names a wallet carries its [labels](https://app.aurelia.build/docs/labels) beside it: trades, traders, holders, portfolios, a token's creator, a wallet's funder, a transfer's counterparty. A wallet without a label has `[]`. #### Errors | Status | When | Body | |---|---|---| | 400 | A parameter is invalid, unknown, repeated, or of another mode | `{"error":"limit must be 1-200"}` | | 404 | An unknown token or pool | `{"error":"unknown token"}` | | 422 | An answer that could not be exact: a BSC wallet that touched more than 2,000 tokens; an address that is not a mint; candles of a token that is the base of no pool; native BNB history; history before indexing reached the balances | `{"error":"…"}` | | 502 | The chain could not be read (a portfolio, holders, a Solana wallet's activity or history) | `{"error":"the chain could not be read: …"}` | | 503 | A chain's read budget is spent (holders, Solana activity and history), with `Retry-After`; `/health` when a service is down | `{"error":"…"}` | | 401 | No credentials, or a bad, expired or revoked key or JWT | `{"error":"unauthorized"}` | | 402 | Your credits left cannot pay for the request | `{"error":"insufficient_balance"}` | | 429 | Over the burst limit | `{"error":"rate_limited","retryAfter":1}` | Error bodies are JSON with an `error` message meant for people: branch on the status. An unknown route answers 404 with the plain text `NOT_FOUND`. On the socket, an error ends one subscription, not the socket (see [the socket](https://app.aurelia.build/docs/live)). Every request needs an account: [sign in](https://app.aurelia.build/#/login) and create a key on [your account](https://app.aurelia.build/#/account). Your account gets 10,000 free credits once, to spend however you like. [Authentication and limits](https://app.aurelia.build/docs/auth) covers keys, credits, paying and the headers that report what is left. ### Data and coverage Aurelia follows every listed chain at the tip with its own pipeline: it reads each block, decodes the swaps, prices them and stores them. On BSC that covers PancakeSwap V2, V3 and Infinity CL, Uniswap V2, V3 and V4, and the bonding curves of four.meme, flap.sh and Genius; on Solana pump.fun, PumpSwap, Raydium (AMM v4, CPMM, CLMM, LaunchLab), Orca Whirlpool, and Meteora DLMM, DAMM v2 and DBC. How far behind the tip indexing is, is always there to check, with any account: [`GET /chains`](https://app.aurelia.build/docs/chains). | Kind | What | Speed and freshness | |---|---|---| | Hot data (in memory, Dragonfly) | Pool state, each token's newest 100 trades, the live feed, wallet labels, cached answers | A few ms. Current to the last indexed block. | | History and analytics (ClickHouse) | Every swap, candles, token and pool stats, traders, wallet positions and PnL, activity on BSC | Tens of ms when not cached; updated as each block is written. Popular answers are cached for 2–30 s. | | Metadata (Postgres) | Token names, decimals, supplies and creators; pools; launches | Filled in shortly after a token is first seen. | | The chain, on request (RPC) | A wallet's balances and liquidity positions, its first funding, a Solana wallet's transactions, a token's holders where the index cannot tell them, security checks | Read when asked, cached, and under a per-chain read budget: past it, `503` with `Retry-After`. | #### Coverage Where a chain cannot give an exact answer, Aurelia says so rather than guess: each answer states what it covers (`coverage`, `complete`, `exactSince`, `note`, `unknown`). Aurelia's own data starts at `indexedSince`. For a token older than that, reading past its first trade asks for its earlier candles and trades once, from a third-party provider: up to 2,000 bars per resolution and 1,000 trades, each marked `imported`, and used only on [its candles](https://app.aurelia.build/docs/token-candles) and [its trade feed](https://app.aurelia.build/docs/trades). PnL, stats and all-time highs never count them. [`token.history`](https://app.aurelia.build/docs/token) says what is held for a token and why. | Capability | BSC | Solana | |---|---|---| | Trades, candles, token and pool stats, positions and PnL | Every swap of the indexed DEXes since `indexedSince` | The same | | [Wallet activity](https://app.aurelia.build/docs/wallet-activity) | From the index: swaps, liquidity, every ERC-20 transfer and BNB sent as a transaction's value. Not internal BNB transfers or gas. Transfers kept 30 days | From the chain: every transaction that names the wallet, with exact balance changes; at most 40 transactions read a request | | [Value history](https://app.aurelia.build/docs/wallet-history) | Tokens, back through the indexed transfers; no native BNB (the node keeps no past state) | Exact, SOL included, back through the wallet's transactions as far as they have been read | | [Holders](https://app.aurelia.build/docs/token-holders) | Complete, with a count, for a token whose whole history the index holds; else the balances of the addresses that moved it, no count | Complete (every token account) unless the mint has too many accounts: then its 20 largest | | [Liquidity positions](https://app.aurelia.build/docs/wallet-portfolio) | LP tokens of V2-style pools, the NFTs of enumerable position managers, and Uniswap V4 and PancakeSwap Infinity positions Aurelia saw change hands since `positionsIndexedSince` | LP mints (PumpSwap, Raydium AMM v4 and CPMM), position NFTs (Orca, Raydium CLMM, Meteora DAMM v2), and Meteora DLMM positions opened since `positionsIndexedSince` | | [First funding](https://app.aurelia.build/docs/wallet-portfolio) | The first BNB transfer the index saw, confirmed by the wallet's state the block before: unknown once the node no longer keeps that state | The wallet's oldest transactions | | [Security](https://app.aurelia.build/docs/security) | Owner, proxy, dangerous functions, measured taxes, honeypot signal, LP burned | Authorities, Token-2022 extensions, metadata mutability, honeypot signal; LP not covered | | [Labels](https://app.aurelia.build/docs/labels) | Every rule; insiders also by funding from the creator | Every rule; funding by the creator is not seen (only DEX transactions are indexed) | Base (`evm:8453`), Ethereum (`evm:1`) and Robinhood Chain (`evm:4663`) are EVM chains and answer as BSC does, over their own DEXes and launchpads. `chains=` defaults to every listed chain. ## Prices [`GET /prices`](https://app.aurelia.build/docs/prices) gives up to 100 tokens' USD prices on any chains in one request, now or at a past moment. [Token lists](https://app.aurelia.build/docs/tokens) and [the token](https://app.aurelia.build/docs/token) carry the same price with its changes; every trade carries `priceUsd`; [the token channel](https://app.aurelia.build/docs/live-token) pushes it as trades land. A price is always the base token's: see [Base, quote and side](https://app.aurelia.build/docs/basics). A token that is never the base, such as USDC, is priced as the quote by `/prices` and in token lists, and has no price on its page. ### Prices: `GET /prices` The USD price of up to 100 tokens on any chains, now or at a past moment, and the swap each comes from. The cheapest read the API has. **Parameters** | Name | In | Type | Default | Allowed values | |---|---|---|---|---| | `tokens` | query | string | required | Up to 100 `:
` items, comma-separated, on any chains: `solana:So111…,evm:56:0xbb4c…` | | `at` | query | integer | now | Unix ms: the price at that moment | **Response** | Field | Type | Meaning | |---|---|---| | `prices[]` | array | One entry per item, in request order, or `null` for a token Aurelia has never stored. | | `prices[].chain`, `address` | string | The token. | | `prices[].priceUsd` | number \| null | USD price in the swap below. `null` when it was not priced, or when the token had no priced swap in the last 24 h (with `at`: none before then). | | `prices[].time`, `pool` | number \| null, string \| null | When and where that swap happened: unix ms, and the token's chart pool. | | `prices[].at`, `tx` | number, string \| null | With `at`: the time asked for, and the swap's transaction. | **Example** (captured 2026-09-28 18:03 UTC on a test instance) ```bash curl -s -H 'Authorization: Bearer ' 'https://app.aurelia.build/api/prices?tokens=evm:56:0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777,solana:So11111111111111111111111111111111111111112,solana:Cs6pmAnogKf3eaouzrM4oCefKP34nVxoEQhtjT7Cpump' ``` ```json { "prices": [ { "chain": "evm:56", "address": "0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777", "priceUsd": 0.006, "time": 1790618578665, "pool": "0x702e6533e0182381606adbe3ccbb77f682ccc58b" }, { "chain": "solana", "address": "So11111111111111111111111111111111111111112", "priceUsd": 150, "time": 1790618141920, "pool": "58oQChx4yWmvKdwLLZzBi4ChoCc2fqCUWBkwMihLYQo2" }, { "chain": "solana", "address": "Cs6pmAnogKf3eaouzrM4oCefKP34nVxoEQhtjT7Cpump", "priceUsd": 0.00000675, "time": 1790618141920, "pool": "8sN9549P3Zn6xpQRqpApN5jE6mHYrqJKeTsKr8idYDqi" } ] } ``` **Notes** - Now: the token page's price, the latest swap where the token was the base in its chart pool, the pool where it was the base with the most USD volume over the last 24 hourly buckets. A priced swap worth under $0.01 sets no price. A token that is never the base, such as USDC or USDT, is priced as the quote in its busiest priced pool, where [the token](https://app.aurelia.build/docs/token) answers `price: null`. - Now is current to the last indexed block: the price comes from the token's newest swaps, kept in memory. Which pool is the chart pool is decided again every 30 s. A token whose chart pool has no swap among its 100 newest is read from history, and kept for a tenth of that swap's age: 2 s to a minute. - With `at`: of the latest hour at or before `at` in which the token was the base anywhere, the pool where it was the base with the most USD volume; in it, the last priced swap worth at least $0.01 at or before `at`. Only what Aurelia indexed: before indexing began, the fields are null. Kept an hour once `at` is 2 minutes old, 5 s before. - `400` when `tokens` is missing, holds more than 100 items or a malformed one (the error names it), or `at` is not a whole number. A token listed twice is answered twice. A warm batch is answered from memory; its misses are read together, in one database query. ## Charts Candles belong to a pool, not a token. [Token candles](https://app.aurelia.build/docs/token-candles) chart a token in its chart pool and name it; [pool candles](https://app.aurelia.build/docs/pool-candles) chart any pool. A price history is the bars' closes. Keep the newest bar moving with [the candles channel](https://app.aurelia.build/docs/live-candles). ### Token candles: `GET /tokens/:chain/:address/candles` OHLC bars of a token, in USD or in its quote token, from the pool that charts it. The answer names that pool. A price history is the bars' closes. **Parameters** | Name | In | Type | Default | Allowed values | |---|---|---|---|---| | `chain` | path | string | required | `evm:56` (BSC), `solana`, `evm:8453` (Base), `evm:4663` (Robinhood Chain) or `evm:1` (Ethereum) | | `address` | path | string | required | Token address (BSC) or mint (Solana) | | `res` | query | string | `1m` | `1m`, `5m`, `15m`, `1h`, `4h`, `1d` | | `from` | query | integer | `to` − 500 bars | Unix seconds | | `to` | query | integer | now | Unix seconds, ≥ `from`; at most 5,000 bars from `from` | | `currency` | query | string | `usd` | `usd`, `quote` | | `fill` | query | string | off | `1` or `true`: repeat the previous close through empty intervals | **Response** | Field | Type | Meaning | |---|---|---| | `chain`, `pool`, `res`, `currency` | string | `pool` is the pool the bars come from; the rest is the request, echoed. | | `token` | string | The token asked for. | | `bars[]` | array | Ascending, `from` ≤ `t` ≤ `to`. | | `t` | number | Bar start, unix seconds, aligned to UTC multiples of `res`. | | `o`, `h`, `l`, `c` | number | Open, high, low, close. Each bar opens at the previous bar's close. | | `v` | number | USD volume, in both currencies. | | `n` | number | Trades: priced ones in `usd`, all of them in `quote`. | | `imported` | true, only on such bars | A USD bar from before Aurelia's first trade of the token, imported from a third-party provider. Its `n` is `null`. | **Example** (captured 2026-09-28 18:03 UTC on a test instance, trimmed to 3 bars) ```bash curl -s -H 'Authorization: Bearer ' 'https://app.aurelia.build/api/tokens/evm:56/0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777/candles?res=1h&from=1790611200&to=1790618400' ``` ```json { "chain": "evm:56", "pool": "0x702e6533e0182381606adbe3ccbb77f682ccc58b", "res": "1h", "currency": "usd", "bars": [ { "t": 1790611200, "o": 0.006, "h": 0.006122448979591836, "l": 0.006, "c": 0.006122448979591836, "v": 36, "n": 5 }, { "t": 1790614800, "o": 0.006122448979591836, "h": 0.006122448979591836, "l": 0.006, "c": 0.006, "v": 18, "n": 3 }, { "t": 1790618400, "o": 0.006, "h": 0.006, "l": 0.006, "c": 0.006, "v": 600, "n": 2 } ], "token": "0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777" } ``` **Notes** - A token older than the index: reading past its first bar asks for its earlier history once, in the background, and the answer does not wait for it. A later read has up to 2,000 earlier bars per `res`, in `usd` only, marked `imported`, from the provider's largest pool rather than the answer's `pool`. A token Aurelia saw created never needs them. See [Data and coverage](https://app.aurelia.build/docs/data). - The pool is the token's chart pool, where it was the base with the most USD volume in the last 24 h, as [its price](https://app.aurelia.build/docs/prices) picks it. Without a trade in 24 h, the pool where it was the base with the most volume ever. - Bars, caching and parameters are those of [pool candles](https://app.aurelia.build/docs/pool-candles): unix seconds, at most 5,000 bars a request. - A pool's candles price its base, so a token that is the base of no pool, such as USDC, answers `422`. `404 {"error":"unknown token"}` for a token Aurelia has never stored. - Keep the newest bar moving with [the candles channel](https://app.aurelia.build/docs/live-candles). ### Pool candles: `GET /pools/:chain/:address/candles` OHLC bars of a pool's base token, in USD or in the quote token. **Parameters** | Name | In | Type | Default | Allowed values | |---|---|---|---|---| | `chain` | path | string | required | `evm:56` (BSC), `solana`, `evm:8453` (Base), `evm:4663` (Robinhood Chain) or `evm:1` (Ethereum) | | `address` | path | string | required | Pool address (BSC; a 32-byte pool id for Uniswap V4, PancakeSwap Infinity CL, and four.meme TokenManager2 and flap.sh curves) or pool account (Solana) | | `res` | query | string | `1m` | `1m`, `5m`, `15m`, `1h`, `4h`, `1d` | | `from` | query | integer | `to` − 500 bars | Unix seconds | | `to` | query | integer | now | Unix seconds, ≥ `from`; at most 5,000 bars from `from` | | `currency` | query | string | `usd` | `usd`, `quote` | | `fill` | query | string | off | `1` or `true`: repeat the previous close through empty intervals | **Response** | Field | Type | Meaning | |---|---|---| | `chain`, `pool`, `res`, `currency` | string | The request, echoed. | | `bars[]` | array | Ascending, `from` ≤ `t` ≤ `to`. | | `t` | number | Bar start, unix seconds, aligned to UTC multiples of `res`. | | `o`, `h`, `l`, `c` | number | Open, high, low, close. Each bar opens at the previous bar's close. | | `v` | number | USD volume, in both currencies. | | `n` | number | Trades: priced ones in `usd`, all of them in `quote`. | **Example** (captured 2026-09-28 18:03 UTC on a test instance, trimmed to 3 bars) ```bash curl -s -H 'Authorization: Bearer ' 'https://app.aurelia.build/api/pools/solana/8sN9549P3Zn6xpQRqpApN5jE6mHYrqJKeTsKr8idYDqi/candles?res=1h&from=1790611200&to=1790618400' ``` ```json { "chain": "solana", "pool": "8sN9549P3Zn6xpQRqpApN5jE6mHYrqJKeTsKr8idYDqi", "res": "1h", "currency": "usd", "bars": [ { "t": 1790611200, "o": 0.000006, "h": 0.000006, "l": 0.000005857142857142857, "c": 0.000006, "v": 9.15, "n": 2 }, { "t": 1790614800, "o": 0.000006, "h": 0.00000675, "l": 0.000006, "c": 0.00000675, "v": 13.5, "n": 1 } ] } ``` **Notes** - Bars are cached in pages of 500. A page the indexer has fully passed is cached for 24 h; the page holding now for up to a minute: tick it with [the candles channel](https://app.aurelia.build/docs/live-candles). - In `usd`, an interval without a priced swap has no bar. In `quote`, every interval with a trade has one. With `fill`, empty intervals repeat the previous close with `v` and `n` at 0. Swaps worth under $0.01 do not set a price. - `from` and `to` are seconds; milliseconds are not detected. An unknown pool answers `200` with no bars. `400` for a bad `res`, `currency` or range, or more than 5,000 bars. ## Trades One trade feed, [`GET /trades`](https://app.aurelia.build/docs/trades), for a token, a pool, a wallet or a transaction, with one row shape and the trader's labels; a token's [traders](https://app.aurelia.build/docs/token-traders) ranked with their positions. The same rows stream over [the trades channel](https://app.aurelia.build/docs/live-trades). ### Trades: `GET /trades` One trade feed, newest first: a token's trades in all its pools, a pool's, a wallet's, or a transaction's, filtered by side, USD size, trader or label. One row shape everywhere, with the trader's labels. **Parameters** | Name | In | Type | Default | Allowed values | |---|---|---|---|---| | `chain` | query | string | required | `evm:56` (BSC), `solana`, `evm:8453` (Base), `evm:4663` (Robinhood Chain) or `evm:1` (Ethereum) | | `token` | query | string | one scope | Exactly one of `token`, `pool`, `wallet`, `tx`: a token's trades, rows from its side | | `pool` | query | string | one scope | A pool's trades, rows from its base's side | | `wallet` | query | string | one scope | A wallet's trades, rows from each swap's base | | `tx` | query | string | one scope | Every swap of a transaction (the last 7 days): each leg of a route | | `side` | query | string | both | `buy` or `sell`, from the row's side | | `minUsd` | query | number | any | A lower bound on `amountUsd`, included; an unpriced trade never matches | | `maxUsd` | query | number | any | An upper bound on `amountUsd`, included | | `trader` | query | string | any | One wallet's trades within the scope (not with `wallet`) | | `label` | query | string | any | Trades by wallets with this [label](https://app.aurelia.build/docs/labels): `sniper`, `insider`… | | `limit` | query | integer | `50` | 1–200 | | `cursor` | query | string | first page | The previous page's `next`, as it came | **Response** | Field | Type | Meaning | |---|---|---| | `chain`, `token \| pool \| wallet \| tx` | string | The request's chain and scope, echoed. | | `indexedSince` | number \| null | Unix ms from which trades are indexed: the chain's first indexed block, or for `tx` 7 days back (transactions are kept that long). | | `trades[].time` | number | Block time, unix ms. | | `trades[].block`, `txIndex`, `eventIndex` | number | Position in the chain: block (Solana: slot), transaction, event. | | `trades[].tx`, `pool`, `protocol` | string | Transaction hash or signature, pool, and DEX (`pancakeswap-v2`, `pumpswap`…). | | `trades[].side` | "buy" \| "sell" | `buy` when the trader received `token`, `sell` when they paid it. | | `trades[].token`, `amount` | { address, symbol, name, decimals }, string | The token the row is told from (see the notes), and its amount, positive, decimals applied. Native BNB, ETH or SOL has address `"native"`. | | `trades[].counter`, `counterAmount` | { address, symbol, name, decimals }, string | The other token and its amount. | | `trades[].priceUsd` | number \| null | `token`'s USD price in this swap, whether it was the base or the quote. `null` when the swap could not be priced. | | `trades[].amountUsd` | number \| null | USD value of the swap. `null` when unpriced. | | `trades[].priced` | boolean | Whether the swap has a USD price. | | `trades[].imported` | true, only on such rows | On a token's own feed, a trade from before Aurelia's first trade of the token, imported from a third-party provider: `block` and `txIndex` are 0, `eventIndex` only orders it, and `protocol` and `labels` are empty. | | `trades[].trader`, `labels` | string, string[] | The wallet that traded (see [Wallets and PnL](https://app.aurelia.build/docs/wallets)), `""` when the swap belongs to no wallet, and its [labels](https://app.aurelia.build/docs/labels). | | `next` | string \| null | Cursor for the next older page, or for the part of the scope after the one a filtered page examined. `null` at the end. | **Example** (captured 2026-09-28 18:03 UTC on a test instance, trimmed to 2 trades) ```bash curl -s -H 'Authorization: Bearer ' 'https://app.aurelia.build/api/trades?chain=evm:56&token=0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777&limit=2' ``` ```json { "chain": "evm:56", "token": "0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777", "indexedSince": 1790546261920, "trades": [ { "time": 1790618578665, "block": 1000750, "txIndex": 0, "eventIndex": 4, "tx": "0x2b170741f16da7ea4287bbd163e3365f89c73c4face2b6ed032487c0dd9b0cb4", "pool": "0x702e6533e0182381606adbe3ccbb77f682ccc58b", "protocol": "pancakeswap-v2", "side": "buy", "token": { "address": "0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777", "symbol": "TAX", "name": "Tax Token", "decimals": 18 }, "amount": "50000", "counter": { "address": "0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c", "symbol": "WBNB", "name": "Wrapped BNB", "decimals": 18 }, "counterAmount": "0.5", "priceUsd": 0.006, "amountUsd": 300, "priced": true, "trader": "0x3304e22ddaa22bcdc5fca2269b418046ae7b566a", "labels": [ "sniper", "fresh" ] }, { "time": 1790618554904, "block": 1000749, "txIndex": 0, "eventIndex": 4, "tx": "0xcd32103a902cba28b871741adf595a6b209ce0b66d6849cc8a603842adb07729", "pool": "0x702e6533e0182381606adbe3ccbb77f682ccc58b", "protocol": "pancakeswap-v2", "side": "buy", "token": { "address": "0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777", "symbol": "TAX", "name": "Tax Token", "decimals": 18 }, "amount": "50000", "counter": { "address": "0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c", "symbol": "WBNB", "name": "Wrapped BNB", "decimals": 18 }, "counterAmount": "0.5", "priceUsd": 0.006, "amountUsd": 300, "priced": true, "trader": "0x3304e22ddaa22bcdc5fca2269b418046ae7b566a", "labels": [ "sniper", "fresh" ] } ], "next": "1790618554904.1000749.0.4" } ``` **Notes** - A token older than the index: reading its unfiltered feed to the end asks for its earlier trades once, in the background. A later read continues with up to 1,000 of them, marked `imported`. A filter, or a pool, wallet or transaction scope, reads Aurelia's own trades only. - One swap in one pool is one row. It is told from the side of the token in scope (`token=`), else from the swap's base token (see [Basics](https://app.aurelia.build/docs/basics)): buying a memecoin with SOL is a buy of the memecoin. A multi-hop route is one row per pool it crossed, sharing `tx`, `block` and `txIndex`: `tx=` returns every leg, a token's feed the legs that moved it. - A filter never widens a read: a filtered page examines a bounded part of its scope (a pool's or a wallet's newest 50,000 swaps before the cursor; a token's last hour, day, then 7 days), and when that part ends before the page fills, `next` continues right after it. Follow `next` until it is `null`. `label=` reads 500 rows a page and keeps those whose trader has the label. - A token's first page comes from its 100 newest swaps, kept in memory: current to the last indexed block. A first page is cached 2 s, a later one 30 s (older trades change only in a reorg). - `400` without exactly one scope, for a bad `side`, `limit`, `cursor` or bound, `minUsd` above `maxUsd`, `trader` with `wallet`, or a malformed `label`. `404` for an unknown token or pool. An unknown wallet or transaction answers `200` with no trades. - [The trades channel](https://app.aurelia.build/docs/live-trades) pushes the same rows as each block is indexed. ### Token traders: `GET /tokens/:chain/:address/traders` A token's traders ranked by PnL, realized PnL or volume, over a window or all time, or its first buyers; each with its position, labels and first buy. **Parameters** | Name | In | Type | Default | Allowed values | |---|---|---|---|---| | `chain` | path | string | required | `evm:56` (BSC), `solana`, `evm:8453` (Base), `evm:4663` (Robinhood Chain) or `evm:1` (Ethereum) | | `address` | path | string | required | Token address (BSC) or mint (Solana) | | `sort` | query | string | `pnl` | `pnl` (total), `realized`, `volume` (bought + sold USD), `first` (first buyers, earliest first) | | `window` | query | string | `all` | `1h`, `6h`, `24h`, `7d`, `30d`, `all` | | `label` | query | string | any | Only traders with this [label](https://app.aurelia.build/docs/labels) | | `limit` | query | integer | `50` | 1–100 | | `cursor` | query | string | first page | The previous page's `next`, as it came | **Response** | Field | Type | Meaning | |---|---|---| | `chain`, `token`, `indexedSince`, `sort`, `window` | string, object, number, string, string | `token` is `{ address, symbol, name, decimals }`; `indexedSince` as in [Wallets and PnL](https://app.aurelia.build/docs/wallets). | | `traders[]` | array | In `sort` order: highest first (a null PnL last), or for `first` earliest first. | | `traders[].wallet`, `labels` | string, string[] | The wallet and its [labels](https://app.aurelia.build/docs/labels). | | `traders[].firstBuy` | { time, block, tx } \| null | The wallet's first buy of the token. `null` for a wallet that only sold it. | | `traders[].quote` | boolean | A stablecoin, or the native or wrapped native token: listed, but left out of every wallet total. | | `traders[].trades`, `buys`, `sells`, `unpricedTrades` | number | The swaps of the token. A buy is one where the wallet received it. `unpricedTrades` had no USD price and are left out of the USD figures. | | `traders[].bought`, `sold` | string | Token amounts, decimals applied. | | `traders[].open` | string | `bought − sold`, at least 0: what the trades leave held. Transfers are not counted, so it can differ from the balance. | | `traders[].boughtUsd`, `soldUsd` | number | USD value of the priced buys and sells. | | `traders[].avgBuyPriceUsd`, `avgSellPriceUsd` | number \| null | USD per token over the priced buys, and sells. Null without one. | | `traders[].priceUsd`, `priceAt` | number \| null | The token's price now, and the start of the hour of the swap it came from, unix ms (see [Wallets and PnL](https://app.aurelia.build/docs/wallets)). | | `traders[].realizedPnlUsd`, `unrealizedPnlUsd`, `totalPnlUsd` | number \| null | See [Wallets and PnL](https://app.aurelia.build/docs/wallets). Null is unknown, never 0. | | `traders[].firstTradeAt`, `lastTradeAt` | number | Unix ms. | | `next` | string \| null | Cursor for the next page. `null` on the last. | **Example** (captured 2026-09-28 18:03 UTC on a test instance, trimmed to 2 traders) ```bash curl -s -H 'Authorization: Bearer ' 'https://app.aurelia.build/api/tokens/evm:56/0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777/traders?sort=first&limit=2' ``` ```json { "chain": "evm:56", "token": { "address": "0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777", "symbol": "TAX", "name": "Tax Token", "decimals": 18 }, "indexedSince": 1790546261920, "sort": "first", "window": "all", "traders": [ { "wallet": "0x0883bc322b58788f1cce720a8a285db72a4facc4", "labels": [ "insider" ], "firstBuy": { "time": 1790546861920, "block": 1000002, "tx": "0x4517764dd3b9782c02c5a640714b20335ea2585b394946c4b0f5f44bc1a7bf12" }, "quote": false, "trades": 20, "buys": 20, "sells": 0, "unpricedTrades": 0, "bought": "5019000", "sold": "0", "open": "5019000", "boughtUsd": 174, "soldUsd": 0, "avgBuyPriceUsd": 0.0000346682606096832, "avgSellPriceUsd": null, "priceUsd": 0.006, "priceAt": 1790618400000, "realizedPnlUsd": 0, "unrealizedPnlUsd": null, "totalPnlUsd": 0, "firstTradeAt": 1790546861920, "lastTradeAt": 1790615861920 }, { "wallet": "0x000000000000000000000000000000000000a00c", "labels": [ "sniper" ], "firstBuy": { "time": 1790546864920, "block": 1000003, "tx": "0x5221ff9327bd2f842642692502d948d77a28f3b6fcda5992a0f8b3174155f12c" }, "quote": false, "trades": 12, "buys": 8, "sells": 4, "unpricedTrades": 0, "bought": "2011000", "sold": "3920", "open": "2007080", "boughtUsd": 96, "soldUsd": 24, "avgBuyPriceUsd": 0.00004773744405768274, "avgSellPriceUsd": 0.006122448979591836, "priceUsd": 0.006, "priceAt": 1790618400000, "realizedPnlUsd": 23.81286921929388, "unrealizedPnlUsd": 11946.667130780706, "totalPnlUsd": 11970.48, "firstTradeAt": 1790546864920, "lastTradeAt": 1790615861920 } ], "next": "2" } ``` **Notes** - With a `window`, a trader's trade counts, amounts and realized PnL are the window's (its sells against the all-time average buy); the average buy, `open` and unrealized PnL are all-time. Windows are whole clock hours, the current one included: `24h` is the last 24 hours rounded down to the hour. - `first` ranks by the hour of each wallet's first buy, exactly, then by its first trade in that hour; with a `window`, only wallets whose first buy falls in it. - Wallets that traded the token, not its holders: one that only received it is not here ([token holders](https://app.aurelia.build/docs/token-holders)). Every swap of the token counts, whether it was the base or the quote. - Cached 15 s. With `label`, a page examines 500 ranked traders. `404 {"error":"unknown token"}` for a token Aurelia has never stored. ## Tokens [Token lists](https://app.aurelia.build/docs/tokens) find tokens: by address, a search, a screener, a launchpad's or a creator's. A token's page gathers its metadata, price, stats, market cap, holders summary, creator and [security](https://app.aurelia.build/docs/security) checks; its pools and each pool's details are one call away. ### Token lists: `GET /tokens` One list of tokens for every purpose, one row shape in every mode: up to 100 tokens by address (`tokens=`), a search by address, symbol or name (`q=`), or a screener over chains with range filters and sorts, a launchpad's tokens, or a creator's (`creator=`, dev history). **Parameters** | Name | In | Type | Default | Allowed values | |---|---|---|---|---| | `tokens` | query | string | none | Batch: Up to 100 `:
` items, comma-separated, on any chains: `solana:So111…,evm:56:0xbb4c…` | | `q` | query | string | none | Search: a token or pool address, a symbol or a name; at most 100 characters. Case and a leading `$` are ignored | | `chains` | query | string | every listed chain | Comma-separated: `evm:56` (BSC), `solana`, `evm:8453` (Base), `evm:4663` (Robinhood Chain) or `evm:1` (Ethereum) | | `sort` | query | string | `volume24hUsd` (`createdAt` with `creator`) | Screener: `volume24hUsd`, `trades24h`, `feesPaid5mUsd`, `feesPaid15mUsd`, `feesPaid30mUsd`, `feesPaid45mUsd`, `feesPaid1hUsd`, `feesPaid6hUsd`, `feesPaid24hUsd`, `liquidityUsd`, `marketCapUsd`, `fdvUsd`, `change24h`, `priceUsd`, `createdAt` | | `order` | query | string | `desc` | Screener: `desc`, `asc`. A null value sorts last either way | | `minLiquidityUsd` | query | number | any | Screener: a USD bound, included | | `maxLiquidityUsd` | query | number | any | Screener: a USD bound, included | | `minVolume24hUsd` | query | number | any | Screener: a USD bound, included | | `minFeesPaid5mUsd` | query | number | any | Screener: a USD bound, included | | `minFeesPaid15mUsd` | query | number | any | Screener: a USD bound, included | | `minFeesPaid30mUsd` | query | number | any | Screener: a USD bound, included | | `minFeesPaid45mUsd` | query | number | any | Screener: a USD bound, included | | `minFeesPaid1hUsd` | query | number | any | Screener: a USD bound, included | | `minFeesPaid6hUsd` | query | number | any | Screener: a USD bound, included | | `minFeesPaid24hUsd` | query | number | any | Screener: a USD bound, included | | `minMarketCapUsd` | query | number | any | Screener: a USD bound, included | | `maxMarketCapUsd` | query | number | any | Screener: a USD bound, included | | `minChange24h` | query | number | any | Screener: a bound on the 24 h price change, a fraction (`0.5` is +50%) | | `maxChange24h` | query | number | any | Screener: a bound on the 24 h price change, a fraction (`0.5` is +50%) | | `createdAfter` | query | integer | any | Screener: a bound on `createdAt`, unix ms, included | | `createdBefore` | query | integer | any | Screener: a bound on `createdAt`, unix ms, included | | `excludeQuotes` | query | boolean | `false` | Screener: `true` leaves out what pools quote in: the stablecoins Aurelia prices at $1 and the chain's wrapped native token | | `creator` | query | string | none | Screener: a wallet; its tokens, on the listed chains its address fits | | `launchpad` | query | string | all | Screener: comma-separated launchpad ids, any of them: `pumpfun`, `fourmeme`… | | `limit` | query | integer | `20` (search), `50` (screener) | Search 1–50, screener 1–100 | | `cursor` | query | string | first page | The previous page's `next`, as it came | **Response** | Field | Type | Meaning | |---|---|---| | `tokens[].chain` | string | The token's chain. | | `tokens[].token` | { address, symbol, name, decimals, logo } | `logo` is an absolute URL of the re-hosted WebP, or `null`. | | `tokens[].priceUsd` | number \| null | As [`GET /prices`](https://app.aurelia.build/docs/prices) gives it. | | `tokens[].change` | { 5m, 1h, 6h, 24h } | Price change as a fraction, as the token page's `priceChange`. Each is `null` without a priced trade that long ago; all are `null` for a token priced as the quote. | | `tokens[].volume24hUsd`, `trades24h`, `buys24h`, `sells24h`, `traders24h` | number | The token's swaps as the base over 24 h, as the token page's `windows["24h"]`. `traders24h` is approximate. | | `tokens[].feesPaidUsd` | { 5m, 15m, 30m, 45m, 1h, 6h, 24h } | The transaction fees traders paid on the token's swaps as the base over each window, as the token page's `windows[w].feesPaidUsd`. Each is `null` while Aurelia's fee records start inside that window. | | `tokens[].marketCapUsd`, `fdvUsd` | number \| null | `priceUsd` × circulating supply, and × total supply, with the token page's limits. | | `tokens[].liquidityUsd` | number \| null | The real balances, in USD, of the pool the price comes from. `null` where that pool's reserves are virtual (concentrated liquidity and bins on BSC) or a price is missing. | | `tokens[].chartPool` | string \| null | The pool the price comes from, when the token is its base. | | `tokens[].createdAt` | number \| null | When the token was created, unix ms, only when Aurelia saw the creation itself. | | `tokens[].creator` | { address, labels } \| null | Who created it, when Aurelia saw the creation, with the creator's [labels](https://app.aurelia.build/docs/labels). | | `tokens[].launchpad` | { id, pool, progress, complete, graduatedAt } \| null | For a launchpad token (`pumpfun`, `fourmeme`…): its launch pool, its bonding curve's progress (0 to 1) and whether it completed, and when. | | `tokens[].flags` | string[] \| null | The [security](https://app.aurelia.build/docs/security) flags of the token's last check. `null` until it was checked: asking queues a check, and a later request has them. | | `next` | string \| null | Cursor for the next page (search and screener). `null` on the last. | | `query`, `tokens[].match` | string | Search: `q` as asked, and how each token matched: `address` (the token's own), `pool` (a pool's), `exact` (its symbol or name is the query), `prefix` (its symbol, or a word of its name, starts with it), `contains` (its symbol or name contains a query of 3+ characters). | | `indexedAt`, `rankedAt` | number | Search: when the names searched, and the volumes that rank them, were last read, unix ms. | | `total`, `updatedAt` | number | Screener: how many tokens match the filters, and when the oldest snapshot used was built, unix ms. | | `creator` | { address, labels, tokensCreated, graduated } | With `creator`: the wallet, its labels, and how many tokens Aurelia saw it create and how many graduated. | | `tokens[].peakMarketCapUsd` | number \| null | With `creator`: the highest daily candle high where the token was the base, × its circulating supply now. Null above $2T, or above $100M while the token's price pool holds under $1k (as for `marketCapUsd`). | **Example** (captured 2026-09-28 18:03 UTC on a test instance, trimmed to 2 tokens) ```bash curl -s -H 'Authorization: Bearer ' 'https://app.aurelia.build/api/tokens?chains=evm:56&minLiquidityUsd=1000&sort=volume24hUsd&limit=2' ``` ```json { "tokens": [ { "chain": "evm:56", "token": { "address": "0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777", "symbol": "TAX", "name": "Tax Token", "decimals": 18, "logo": null }, "priceUsd": 0.006, "change": { "5m": 0, "1h": -0.019999999999999907, "6h": 0, "24h": null }, "volume24hUsd": 1758, "trades24h": 105, "buys24h": 72, "sells24h": 33, "traders24h": 8, "marketCapUsd": 6000000, "fdvUsd": 6000000, "liquidityUsd": 4204200, "chartPool": "0x702e6533e0182381606adbe3ccbb77f682ccc58b", "createdAt": 1790546861920, "creator": { "address": "0x0f325bd174c70440fa62c31dce9f69d54baf717e", "labels": [ "creator" ] }, "launchpad": { "id": "fourmeme", "pool": "0xf39f85a07237050d069187679c621b9df1df8585b01d625ae22bc7cb99430013", "progress": 1, "complete": true, "graduatedAt": 1790549261920 }, "flags": [ "buy_tax", "sell_tax" ] } ], "next": null, "total": 1, "updatedAt": 1790618550079 } ``` **Notes** - The mode is set by the parameters: `tokens=` is the batch, `q=` a search, anything else the screener. A parameter of another mode, an unknown one or a repeated one is `400`, and the error names it. `minHolders` is `400` too: holder counts are not known for many tokens at once ([token holders](https://app.aurelia.build/docs/token-holders) answers them one token at a time). - Batch: items in request order, an unknown token `null` in its place (`:
`, split on the last colon: `evm:56:0xbb4c…`). No paging. - Search: an address is looked up first, on the chains it can belong to: a token with that address, then a pool, which stands for the token it trades against a stablecoin or the wrapped native token. Symbol and name matches follow, by kind and by activity: an `exact` match outranks a `prefix` match unless that one trades about 100 times as much, so the token that trades comes before the dozens that copy its symbol. Names and volumes are held in memory and read again every minute (in full every 5 minutes). A search pages through its 200 best matches. - Screener: the candidates are every token with a USD price from a trade in the last 24 hours, and the launches of the last 24 hours without one yet; with `creator`, every token the wallet created. Filters and order apply to a snapshot rebuilt every 30 s, the rows are current: a value may have moved past a bound since, except `feesPaidUsd`, the snapshot's, so a list sorted or filtered by fees reads in order. A `null` value never matches a range. A page is kept 10 s. - Every row is the same whatever the mode: a token has the same price, stats and flags here as in a batch or on [its page](https://app.aurelia.build/docs/token), with two differences from the page: a token that is never the base (USDC) is priced here as the quote, where the page gives no price; and `liquidityUsd` is the price pool's alone, where the page sums its listed pools. ### Token: `GET /tokens/:chain/:address` Everything about one token: metadata, logo and socials, creation and creator, supply, price, stats and fees paid per window, ATH and ATL, bonding curve, market cap and FDV, holders summary, security checks, and its pools with the chart pool first. **Parameters** | Name | In | Type | Default | Allowed values | |---|---|---|---|---| | `chain` | path | string | required | `evm:56` (BSC), `solana`, `evm:8453` (Base), `evm:4663` (Robinhood Chain) or `evm:1` (Ethereum) | | `address` | path | string | required | Token address (BSC) or mint (Solana) | **Response** | Field | Type | Meaning | |---|---|---| | `token` | object | `address`, `decimals`, `symbol`, `name`, `firstSeenBlock`; `totalSupply` and `circulatingSupply` (decimal strings or null); `supplyAt` (unix ms the supply was read, or null). | | `token.history` | { complete, basis, from } | How much of the token's past Aurelia holds. `complete`: all of it. `basis`: why. `creation`, a parser saw it created; `minted`, the index saw all of its supply minted (EVM); `no-older-history`, a third-party provider that knows the token has no trade or candle older than Aurelia's first; `imported`, it is older than the index and its earlier candles and trades were imported; `null`, not known (nothing has read past its first trade yet, or the provider does not know it). `from`: when the data held starts, unix ms, or `null`. | | `token.firstSeenAt` | number \| null | Time of `firstSeenBlock`, unix ms: when Aurelia first saw the token, not when it was created. | | `token.createdAt`, `createdTx`, `creator` | number \| null, string \| null, string \| null | The token's creation: unix ms, transaction and creator. Only when Aurelia saw the creation itself, which means a launchpad's create; all `null` otherwise, never guessed from a first sighting, so `null` does not say the token is old. See `token.history`. | | `token.logo` | string \| null | Absolute URL of the logo, re-hosted as a WebP at most 256×256 and served by `GET /logos/:chain/:address`. `null` when none is stored. | | `token.description`, `socials`, `uri` | string \| null, object, string \| null | From the token's metadata document: plain text (at most 1,000 characters); `website`, `twitter`, `telegram`, `discord` (`https://` URLs or null) and `others`; and where the document lives as the chain states it. | | `token.security` | object \| null | The token's risk checks, each saying how it was established: see [Security](https://app.aurelia.build/docs/security). `null` for a chain Aurelia does not check. | | `holders` | { count, top10Share } \| null | As the last [holders](https://app.aurelia.build/docs/token-holders) read found them (kept an hour); each is null when that list was not complete. `null` before any read: this page never reads the chain for them. | | `creator` | { address, labels, tokensCreated, graduated } \| null | The creator's [labels](https://app.aurelia.build/docs/labels), and how many tokens Aurelia saw it create and how many graduated from their bonding curve. `null` when the creation was not seen. | | `price` | object \| null | From the token's latest swap as the base in its chart pool: `usd` (null when that swap could not be priced), `quote` and `quoteToken` (the same price in the pool's quote token; the chain's own coin is address `native`, with its symbol), `pool`, `time`. `null` when it was never the base there. | | `windows` | { 5m, 15m, 1h, 4h, 6h, 12h, 24h } | Each: `priceChange` (fraction or null), `volumeUsd`, `buyVolumeUsd`, `sellVolumeUsd`, `trades`, `buys`, `sells`, `traders`, `buyers`, `sellers` (distinct, approximate), `feesPaidUsd` (null when Aurelia's fee records start inside the window). | | `stats24h` | object | `windows["24h"]` without `priceChange`. | | `chartPool`, `poolCount` | string \| null, number | The pool with the most 24 h volume where the token was the base (failing that, the pool holding most of it), and every pool that holds the token. | | `liquidityUsd`, `marketCapUsd`, `fdvUsd` | number \| null | The listed pools whose liquidity is known, summed; `price.usd` × circulating supply, and × total supply. | | `pools[]` | array | Up to 25: the chart pool, then by 24 h volume. Each: `address`, `protocol`, `token0`, `token1`, `based` (the token was the base here in 24 h), `counter`, `priceUsd`, `reserve` and `counterReserve` (decimals applied; a curve's are virtual), `liquidityUsd` (real balances; null for concentrated liquidity and bins on BSC), `volume24hUsd`, `trades24h`. [Token pools](https://app.aurelia.build/docs/token-pools) pages through all of them. | | `feesPaidUsd` | number \| null | All-time fees paid on the token's swaps as the base. `null` unless `token.history.complete` and the token's history starts after Aurelia's fee records began. | | `ath`, `atl` | { usd, at } \| null | All-time high and low USD price and the minute it happened. `null` unless `token.history.complete`: imported history is not counted. | | `bonding` | { pool, progress, complete } \| null | For a token launched on a bonding curve: the curve, the share of its sellable supply sold (0 to 1, or null when the program does not state it), and whether it completed. | | `latestTradeAt` | number \| null | The token's newest swap in any pool, unix ms. | **Example** (captured 2026-09-28 18:03 UTC on a test instance, trimmed to 1 pool and the 5m, 1h and 24h windows) ```bash curl -s -H 'Authorization: Bearer ' 'https://app.aurelia.build/api/tokens/evm:56/0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777' ``` ```json { "token": { "address": "0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777", "decimals": 18, "symbol": "TAX", "name": "Tax Token", "firstSeenBlock": 1000002, "totalSupply": "1000000000", "circulatingSupply": "1000000000", "supplyAt": 1790618262558, "firstSeenAt": 1790546861920, "createdAt": 1790546861920, "createdTx": "0x4517764dd3b9782c02c5a640714b20335ea2585b394946c4b0f5f44bc1a7bf12", "creator": "0x0f325bd174c70440fa62c31dce9f69d54baf717e", "history": { "complete": true, "basis": "creation", "from": 1790546861920 }, "logo": null, "description": null, "socials": { "website": null, "twitter": null, "telegram": null, "discord": null, "others": {} }, "uri": null, "security": { "checkedAt": 1790618614169, "flags": [ "buy_tax", "sell_tax" ], "honeypot": { "likely": false, "buyers": 8, "sellers": 7, "window": "24h", "how": "Distinct wallets that bought, and that sold, in the last 24h (the transaction's sender), leaving out the token's creator and its pools' creators where Aurelia saw them. Likely a honeypot when 10+ wallets bought and none of them sold; not one once any sold" }, "lp": { "pool": "0x702e6533e0182381606adbe3ccbb77f682ccc58b", "lockedShare": null, "burnedShare": 1, "how": "The pool is its own LP token: totalSupply() and balanceOf() of the zero and dead addresses at block 124557783. Locked LP is not counted: Aurelia has no trustworthy list of lockers" }, "top10Share": 0.30010822, "logoReuses": null, "unknown": { "logoReuses": "the token has no logo" }, "contract": { "block": 124557783, "owner": "0x0000000000000000000000000000000000000000", "renounced": true, "proxy": { "kind": "minimal", "implementation": "0x024f18294970b5c76c0691b87f138a0317156422", "admin": null, "upgradeable": false }, "functions": { "mint": { "present": false, "signatures": [], "nonOwnerCall": null }, "pause": { "present": false, "signatures": [], "nonOwnerCall": null }, "blacklist": { "present": false, "signatures": [], "nonOwnerCall": null }, "tradingToggle": { "present": false, "signatures": [], "nonOwnerCall": null } }, "how": "At block 124557783: owner() or getOwner(); the EIP-1967, OpenZeppelin and EIP-1822 proxy slots and EIP-1167 code; the known mint, pause, blacklist and trading-toggle signatures the implementation's bytecode dispatches, each present one called once through Multicall3 (eth_call from a caller that is not the owner)" }, "taxes": { "buy": 0.02, "sell": 0.02, "buys": 69, "sells": 33, "window": "24h", "how": "Median over the swaps of the last 24h in transactions with one swap of the token: what the pool's Transfer logs delivered to the recipient against what the pool sent (buys), what the pool received against what its sender sent in all (sells)" } } }, "holders": { "count": 13, "top10Share": 0.30010822 }, "creator": { "address": "0x0f325bd174c70440fa62c31dce9f69d54baf717e", "labels": [ "creator" ], "tokensCreated": 3, "graduated": 1 }, "stats24h": { "volumeUsd": 1758, "buyVolumeUsd": 1560, "sellVolumeUsd": 198, "trades": 105, "buys": 72, "sells": 33, "traders": 8, "buyers": 8, "sellers": 7, "feesPaidUsd": null }, "chartPool": "0x702e6533e0182381606adbe3ccbb77f682ccc58b", "price": { "usd": 0.006, "quote": 0.00001, "quoteToken": { "address": "0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c", "symbol": "WBNB" }, "pool": "0x702e6533e0182381606adbe3ccbb77f682ccc58b", "time": 1790618578665 }, "windows": { "5m": { "priceChange": 0, "volumeUsd": 600, "buyVolumeUsd": 600, "sellVolumeUsd": 0, "trades": 2, "buys": 2, "sells": 0, "traders": 1, "buyers": 1, "sellers": 0, "feesPaidUsd": null }, "1h": { "priceChange": -0.019999999999999907, "volumeUsd": 618, "buyVolumeUsd": 612, "sellVolumeUsd": 6, "trades": 5, "buys": 4, "sells": 1, "traders": 4, "buyers": 3, "sellers": 1, "feesPaidUsd": null }, "24h": { "priceChange": null, "volumeUsd": 1758, "buyVolumeUsd": 1560, "sellVolumeUsd": 198, "trades": 105, "buys": 72, "sells": 33, "traders": 8, "buyers": 8, "sellers": 7, "feesPaidUsd": null } }, "poolCount": 2, "liquidityUsd": 4204200, "marketCapUsd": 6000000, "fdvUsd": 6000000, "pools": [ { "address": "0x702e6533e0182381606adbe3ccbb77f682ccc58b", "protocol": "pancakeswap-v2", "token0": "0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777", "token1": "0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c", "volume24hUsd": 1368, "reserve": "700000000", "based": true, "counter": { "address": "0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c", "symbol": "WBNB" }, "trades24h": 102, "priceUsd": 0.006, "counterReserve": "7", "liquidityUsd": 4204200 } ], "feesPaidUsd": null, "ath": { "usd": 0.006122448979591836, "at": 1790549820000 }, "atl": { "usd": 0.000012, "at": 1790546820000 }, "bonding": { "pool": "0xf39f85a07237050d069187679c621b9df1df8585b01d625ae22bc7cb99430013", "progress": 1, "complete": true }, "latestTradeAt": 1790618578665 } ``` **Notes** - Cached 5 s. `404 {"error":"unknown token"}` for a token Aurelia has never stored. [The token channel](https://app.aurelia.build/docs/live-token) sends this answer again as it changes, and the price after each trade. - `price` and the window stats count swaps where the token was the base. A stablecoin is always the quote, so USDC answers `price: null` and zero stats: it is priced at $1 when it quotes other tokens. - `marketCapUsd` and `fdvUsd` are null when the price or the supply is unknown, when the supply is 0, above $2T, or above $100M while the token's known liquidity is under $1k (a price printed in a pool nobody can exit). WBNB has no supply here, since its wrapped supply is not BNB's. - `security` waits at most 1.5 s for a chain read on a miss; a slower one finishes in the background and the block says so in `unknown` ("the chain is being read"): ask again in a few seconds. - A token seen for the first time can have null `symbol`, `name` and supplies for a short while; `logo`, `description` and `socials` follow once its metadata document is fetched, which can take minutes or never happen. - Window stats are built from 5-minute buckets, the newest one still filling: `5m` is the current bucket only. `priceChange` is null without a priced trade that long ago (the look-back is 48 h). - Fees paid are the transaction fees traders paid to get their swaps included: on Solana the base and priority fee plus any Jito tip, on BSC the gas, in USD at the native price of the moment. Trading-bot fees and the pool's own swap fee are not included. - `ath` and `atl` come from the pools where the token was the base with at least 5% of the chart pool's 24 h volume, plus its bonding curve. When set, they hold the current price: `ath.usd` ≥ `price.usd` ≥ `atl.usd`. ### Security [The token](https://app.aurelia.build/docs/token) carries `security`: a block of checks, each saying how it was established (`how`). A check that could not be established is `null`, with the reason under its name in `unknown`: an absent answer is never a pass. [Token lists](https://app.aurelia.build/docs/tokens) carry the same checks' `flags` for each row. | Check | Chain | How it is established | |---|---|---| | `contract.owner`, `renounced` | BSC | `owner()` or `getOwner()` at a block; renounced when it is the zero or dead address. | | `contract.proxy` | BSC | The EIP-1967 implementation, admin and beacon slots, OpenZeppelin's legacy slot, EIP-1822, and EIP-1167 minimal-proxy code. `upgradeable` when an admin or an upgrade function can change the code. | | `contract.functions` | BSC | The known mint, pause, blacklist and trading-toggle signatures the bytecode (the implementation's, behind a proxy) dispatches; each one present is called once from an address that is not the owner, and `nonOwnerCall` says whether it succeeded. | | `taxes` | BSC | Measured from indexed swaps and their transfers over 24 h: what the recipient got against what the pool sent (buys), what the pool received against what the seller sent (sells). The median per side, with the number of swaps. Not measured for a token in more than 50 pools (a quote token). | | `lp` | BSC | The token's most traded constant-product pool, which is its own LP token, at one block. `burnedShare`: held by the zero and dead addresses. `lockedShare`: held by verified lockers (PinkLock V2 and V1, UNCX's PancakeSwap V2 and Uniswap V2 lockers, Team Finance, GemPad), less locks PinkLock or UNCX list as already past their end. `lockers` lists each locker holding LP with its share; `counted: false` marks DxSale's legacy locker, whose flaw lets an owner withdraw a lock again. `unlock`: the soonest end of a listed lock (unix seconds) and the share it frees. A custom timelock is not seen. | | `lp` | Solana | The token's most traded pool in 24 h with a fungible LP (PumpSwap, Raydium AMM v4, Raydium CPMM) or permanently locked liquidity (Meteora DAMM v2). `burnedShare`: the LP the pool issued (its own count, which misses burns outside a withdrawal) less the mint's supply, plus what the incinerator holds; null on DAMM v2. `lockedShare`: what Raydium's LP lock (Burn & Earn) holds of a CPMM pool's LP, or a DAMM v2 pool's permanently locked liquidity; null where no lock program is verified (PumpSwap, AMM v4). | | `mintAuthority`, `freezeAuthority`, `mint` | Solana | The mint account: its authorities (null is none: revoked), its program, and its Token-2022 extensions (transfer fee, permanent delegate, transfer hook, non-transferable, default frozen, pausable). | | `metadata` | Solana | The Metaplex metadata account's `is_mutable`, or the Token-2022 metadata's update authority. | | `honeypot` | Both | Distinct wallets that bought and that sold in 24 h, leaving out the creator and its pools' creators. `likely` when 10 or more bought and none sold; not once any sold; null below 10 buyers. | | `top10Share` | Both | From the last complete [holders](https://app.aurelia.build/docs/token-holders) list; null without one. | | `logoReuses` | Both | Other tokens whose logo is the same image; null for a token without a logo. | #### Flags `flags` names each finding that is a risk: `upgradeable`, `mintable`, `pausable`, `blacklist`, `trading_toggle` (a function present and either callable by anyone or the owner not renounced), `buy_tax`, `sell_tax` (a median of 0.5% or more over 3+ swaps), `mint_authority`, `freeze_authority`, `transfer_fee`, `permanent_delegate`, `transfer_hook`, `non_transferable`, `default_frozen`, `mutable_metadata`, `honeypot`, `logo_reused`, `lp_unlocked` (both LP shares known, and less than half the LP burned, locked, or held by a known locker). A check that could not run raises none: read `unknown` before calling a token clean. #### Unknown is not revoked On Solana, `mintAuthority` and `freezeAuthority` are `null` both when revoked and when unknown. They are revoked only when `unknown` does not name them. When the chain could not be read, the authorities the indexer stored last are shown, and `unknown.mint` says so. #### Freshness A block is kept 5 minutes (30 s while it lacks its chain facts); chain facts 10 minutes; a contract's bytecode facts 30 days (code does not change). A request waits at most 1.5 s for a chain read; a slower one finishes in the background and the block says so in `unknown`. A list row's `flags` is `null` until the token was checked: listing it queues a check, and flags older than an hour are checked again. BSC, a four.meme token (security block of the token example), captured on a test instance: ```json { "checkedAt": 1790618614169, "flags": [ "buy_tax", "sell_tax" ], "honeypot": { "likely": false, "buyers": 8, "sellers": 7, "window": "24h", "how": "Distinct wallets that bought, and that sold, in the last 24h (the transaction's sender), leaving out the token's creator and its pools' creators where Aurelia saw them. Likely a honeypot when 10+ wallets bought and none of them sold; not one once any sold" }, "lp": { "pool": "0x702e6533e0182381606adbe3ccbb77f682ccc58b", "lockedShare": null, "burnedShare": 1, "how": "The pool is its own LP token: totalSupply() and balanceOf() of the zero and dead addresses at block 124557783. Locked LP is not counted: Aurelia has no trustworthy list of lockers" }, "top10Share": 0.30010822, "logoReuses": null, "unknown": { "logoReuses": "the token has no logo" }, "contract": { "block": 124557783, "owner": "0x0000000000000000000000000000000000000000", "renounced": true, "proxy": { "kind": "minimal", "implementation": "0x024f18294970b5c76c0691b87f138a0317156422", "admin": null, "upgradeable": false }, "functions": { "mint": { "present": false, "signatures": [], "nonOwnerCall": null }, "pause": { "present": false, "signatures": [], "nonOwnerCall": null }, "blacklist": { "present": false, "signatures": [], "nonOwnerCall": null }, "tradingToggle": { "present": false, "signatures": [], "nonOwnerCall": null } }, "how": "At block 124557783: owner() or getOwner(); the EIP-1967, OpenZeppelin and EIP-1822 proxy slots and EIP-1167 code; the known mint, pause, blacklist and trading-toggle signatures the implementation's bytecode dispatches, each present one called once through Multicall3 (eth_call from a caller that is not the owner)" }, "taxes": { "buy": 0.02, "sell": 0.02, "buys": 69, "sells": 33, "window": "24h", "how": "Median over the swaps of the last 24h in transactions with one swap of the token: what the pool's Transfer logs delivered to the recipient against what the pool sent (buys), what the pool received against what its sender sent in all (sells)" } } ``` Solana, a pump.fun coin, captured on a test instance (chain facts from recorded replies): ```json { "checkedAt": 1790618353852, "flags": [], "honeypot": { "likely": false, "buyers": 2, "sellers": 2, "window": "24h", "how": "Distinct wallets that bought, and that sold, in the last 24h (the transaction's sender), leaving out the token's creator and its pools' creators where Aurelia saw them. Likely a honeypot when 10+ wallets bought and none of them sold; not one once any sold" }, "lp": null, "top10Share": null, "logoReuses": null, "unknown": { "top10Share": "no complete holders list read yet (GET /tokens/:chain/:address/holders)", "logoReuses": "the token has no logo", "lp": "not covered on Solana: each protocol keeps its LP mint differently, and Aurelia does not read them yet" }, "mintAuthority": null, "freezeAuthority": null, "mint": { "slot": 451374423, "program": "token", "mintAuthority": null, "freezeAuthority": null, "extensions": [], "transferFee": null, "permanentDelegate": null, "transferHook": null, "nonTransferable": false, "defaultFrozen": false, "pausable": null, "closeAuthority": null, "metadataAuthority": null, "how": "The mint account at slot 451374423: its authorities and Token-2022 extensions" }, "metadata": { "source": "metaplex", "updateAuthority": "TSLvdd1pWpHVjahSpsvCXUbgwsL3JAcvokwaKt1eokM", "mutable": false, "how": "The Metaplex metadata account's is_mutable at slot 451374423" } } ``` ### Token pools: `GET /tokens/:chain/:address/pools` Every pool of a token, busiest or deepest first, each with its state, price, liquidity and stats over 5m to 24h. **Parameters** | Name | In | Type | Default | Allowed values | |---|---|---|---|---| | `chain` | path | string | required | `evm:56` (BSC), `solana`, `evm:8453` (Base), `evm:4663` (Robinhood Chain) or `evm:1` (Ethereum) | | `address` | path | string | required | Token address (BSC) or mint (Solana) | | `sort` | query | string | `volume` | `volume` (24 h), `liquidity` | | `limit` | query | integer | `10` | 1–25 | | `cursor` | query | string | first page | The previous page's `next`, as it came | **Response** | Field | Type | Meaning | |---|---|---| | `chain`, `token`, `sort` | string | The request, echoed. | | `poolCount` | number | Every pool that holds the token, as on [its page](https://app.aurelia.build/docs/token). | | `listed` | number | The pools this listing pages through (see the notes). | | `pools[]` | array | Each a [pool](https://app.aurelia.build/docs/pool) answer: the pool, its tokens with logos, its state, `price`, `liquidityUsd` and `windows`. | | `next` | string \| null | Cursor for the next page. `null` on the last. | **Example** (captured 2026-09-28 18:03 UTC on a test instance, trimmed to 1 pool) ```bash curl -s -H 'Authorization: Bearer ' 'https://app.aurelia.build/api/tokens/evm:56/0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777/pools?limit=1' ``` ```json { "chain": "evm:56", "token": "0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777", "sort": "volume", "poolCount": 2, "listed": 2, "pools": [ { "pool": { "chain": "evm:56", "address": "0x702e6533e0182381606adbe3ccbb77f682ccc58b", "protocol": "pancakeswap-v2", "token0": "0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777", "token1": "0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c", "factory": null, "createdBlock": 1000008, "createdTx": null, "creator": "0x0f325bd174c70440fa62c31dce9f69d54baf717e", "extra": {}, "createdAt": 1790549261920 }, "token0": { "address": "0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777", "decimals": 18, "symbol": "TAX", "name": "Tax Token", "firstSeenBlock": 1000002, "logo": null }, "token1": { "address": "0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c", "decimals": 18, "symbol": "WBNB", "name": "Wrapped BNB", "firstSeenBlock": 1000001, "logo": null }, "state": { "atBlock": 1000008, "atIndex": 0, "reserve0": "700000000", "reserve1": "7" }, "price": { "baseToken": "0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777", "quoteToken": "0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c", "usd": 0.006, "quote": 0.00001 }, "liquidityUsd": 4204200, "windows": { "5m": { "volumeUsd": 600, "buyVolumeUsd": 600, "sellVolumeUsd": 0, "trades": 2, "buys": 2, "sells": 0, "traders": 1, "priceChange": 0 }, "1h": { "volumeUsd": 618, "buyVolumeUsd": 612, "sellVolumeUsd": 6, "trades": 5, "buys": 4, "sells": 1, "traders": 4, "priceChange": -0.019999999999999907 }, "6h": { "volumeUsd": 822, "buyVolumeUsd": 762, "sellVolumeUsd": 60, "trades": 31, "buys": 21, "sells": 10, "traders": 8, "priceChange": 0 }, "24h": { "volumeUsd": 1368, "buyVolumeUsd": 1170, "sellVolumeUsd": 198, "trades": 102, "buys": 69, "sells": 33, "traders": 8, "priceChange": null } } } ], "next": "1" } ``` **Notes** - `volume` lists the pools with a swap of the token in its last 24 hourly buckets, busiest first (at most 1,000), then, for a token in at most 500 pools, its other pools by address. `liquidity` ranks the first 50 of that order by `liquidityUsd`, deepest first; a pool whose liquidity is unknown comes last. - The ranking is kept 30 s, so a walk through the pages sees one order; each pool's answer is kept 5 s. - `400` for a bad `sort`, `limit` or `cursor`. `404 {"error":"unknown token"}` for a token Aurelia has never stored. ### Pool: `GET /pools/:chain/:address` A pool's metadata, both tokens, its live state, and its market: price, liquidity, and stats over 5m to 24h. **Parameters** | Name | In | Type | Default | Allowed values | |---|---|---|---|---| | `chain` | path | string | required | `evm:56` (BSC), `solana`, `evm:8453` (Base), `evm:4663` (Robinhood Chain) or `evm:1` (Ethereum) | | `address` | path | string | required | Pool address (BSC; a 32-byte pool id for Uniswap V4, PancakeSwap Infinity CL, and four.meme TokenManager2 and flap.sh curves) or pool account (Solana) | **Response** | Field | Type | Meaning | |---|---|---| | `pool` | object | `chain`, `address`, `protocol`, `token0`, `token1`, `factory` (or null), `extra` (protocol details: fee, tick spacing, vaults, hooks…). | | `pool.createdBlock`, `createdAt`, `createdTx`, `creator` | number \| null, number \| null, string \| null, string \| null | The pool's creation. Null when Aurelia first saw the pool through a swap, not its creation. | | `token0`, `token1` | object \| null | `address`, `decimals`, `symbol`, `name`, `firstSeenBlock`, and `logo`: an absolute URL, or `null`. | | `state` | object \| null | After the last indexed event that touched the pool: `atBlock`, `atIndex`; `reserve0`, `reserve1` (decimals applied); `sqrtPrice` and `liquidity` (raw, concentrated liquidity only), `tick`, `activeBin`, `curveComplete` where the pool type has them. `null` when unknown. | | `price` | object \| null | The closes of the pool's charts: `baseToken` and `quoteToken`; `usd`, the base's USD price at the newest priced swap (null without one); `quote`, its price in quote-token units at the newest swap. `null` when the base of the pool's newest hour of swaps cannot be told. | | `liquidityUsd` | number \| null | Value of the pool's real balances at both tokens' USD prices in its newest priced swap. `null` for concentrated liquidity and bins on BSC, whose reserves are virtual, and without both prices. | | `windows` | { 5m, 1h, 6h, 24h } | Each: `volumeUsd`, `buyVolumeUsd`, `sellVolumeUsd`, `trades`, `buys`, `sells` (a buy is the trader receiving the base), `traders` (approximate), `priceChange` (the base's, from 1-minute closes). | **Example** (captured 2026-09-28 18:03 UTC on a test instance) ```bash curl -s -H 'Authorization: Bearer ' 'https://app.aurelia.build/api/pools/evm:56/0x702e6533e0182381606adbe3ccbb77f682ccc58b' ``` ```json { "pool": { "chain": "evm:56", "address": "0x702e6533e0182381606adbe3ccbb77f682ccc58b", "protocol": "pancakeswap-v2", "token0": "0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777", "token1": "0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c", "factory": null, "createdBlock": 1000008, "createdTx": null, "creator": "0x0f325bd174c70440fa62c31dce9f69d54baf717e", "extra": {}, "createdAt": 1790549261920 }, "token0": { "address": "0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777", "decimals": 18, "symbol": "TAX", "name": "Tax Token", "firstSeenBlock": 1000002, "logo": null }, "token1": { "address": "0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c", "decimals": 18, "symbol": "WBNB", "name": "Wrapped BNB", "firstSeenBlock": 1000001, "logo": null }, "state": { "atBlock": 1000008, "atIndex": 0, "reserve0": "700000000", "reserve1": "7" }, "price": { "baseToken": "0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777", "quoteToken": "0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c", "usd": 0.006, "quote": 0.00001 }, "liquidityUsd": 4204200, "windows": { "5m": { "volumeUsd": 600, "buyVolumeUsd": 600, "sellVolumeUsd": 0, "trades": 2, "buys": 2, "sells": 0, "traders": 1, "priceChange": 0 }, "1h": { "volumeUsd": 618, "buyVolumeUsd": 612, "sellVolumeUsd": 6, "trades": 5, "buys": 4, "sells": 1, "traders": 4, "priceChange": -0.019999999999999907 }, "6h": { "volumeUsd": 822, "buyVolumeUsd": 762, "sellVolumeUsd": 60, "trades": 31, "buys": 21, "sells": 10, "traders": 8, "priceChange": 0 }, "24h": { "volumeUsd": 1368, "buyVolumeUsd": 1170, "sellVolumeUsd": 198, "trades": 102, "buys": 69, "sells": 33, "traders": 8, "priceChange": null } } } ``` **Notes** - Cached 5 s. [The pool channel](https://app.aurelia.build/docs/live-token) sends this answer again as it changes, and the price after each swap. - Constant-product pools report real balances. Concentrated liquidity reports the active range's virtual reserves: take the price from `sqrtPrice`, `price` or trades, not from reserves. - Window stats count every swap of the pool, unpriced ones (0 USD) and those under $0.01 included; `priceChange` follows the candles, which leave those out. - `404 {"error":"unknown pool"}` for a pool Aurelia has never stored. ### Token logo: `GET /logos/:chain/:address` A token's logo as a small WebP, re-hosted by Aurelia so pages never load a deployer's server or an IPFS gateway. Use the URL the token endpoints give in `logo`. **Parameters** | Name | In | Type | Default | Allowed values | |---|---|---|---|---| | `chain` | path | string | required | `evm:56` (BSC), `solana`, `evm:8453` (Base), `evm:4663` (Robinhood Chain) or `evm:1` (Ethereum) | | `address` | path | string | required | Token address (BSC) or mint (Solana) | | `v` | query | string | none | The logo's version, as in the `logo` URL | **Response** | Field | Type | Meaning | |---|---|---| | `(body)` | image/webp | The token's image, fitted inside 256×256 and never enlarged; the first frame of an animated one. | **Example** (captured 2026-09-28 18:03 UTC on a test instance) ```bash curl -s -H 'Authorization: Bearer ' 'https://app.aurelia.build/api/logos/solana/So11111111111111111111111111111111111111112' ``` ```json { "error": "no logo" } ``` **Notes** - Open and not metered, so an `` can load it directly. - `Cache-Control: public, max-age=31536000, immutable` when `v` names the stored image; without `v`, or with an older one, `max-age=3600`. A new image gets a new `v`. - `404 {"error":"no logo"}` when none is stored: the token's metadata has no image, it could not be fetched, or it was not fetched yet. ## Holders Who holds a token now, with labels and PnL, and how complete the list is. [The holders channel](https://app.aurelia.build/docs/live-holders) sends it again each time it changes. ### Token holders: `GET /tokens/:chain/:address/holders` Who holds a token now: its largest holders with their balance, share, USD value, labels and position (PnL), the holder count where it can be known, and how complete the list is. **Parameters** | Name | In | Type | Default | Allowed values | |---|---|---|---|---| | `chain` | path | string | required | `evm:56` (BSC), `solana`, `evm:8453` (Base), `evm:4663` (Robinhood Chain) or `evm:1` (Ethereum) | | `address` | path | string | required | Token address (BSC) or mint (Solana) | | `limit` | query | integer | `20` | 1–100 | | `cursor` | query | string | first page | The previous page's `next`: pages reach the 1,000 largest | **Response** | Field | Type | Meaning | |---|---|---| | `chain`, `token`, `indexedSince` | string, object, number | `token` is `{ address, symbol, name, decimals }`; `indexedSince` bounds the positions, as in [Wallets and PnL](https://app.aurelia.build/docs/wallets). | | `source` | string | How the list was established: `indexed-transfers`, `indexed-addresses` (BSC), `token-accounts`, `largest-accounts` (Solana). See the notes. | | `complete`, `coverage` | boolean, string | Whether the list holds every holder, and what it covers, in words. | | `block` | number | The block (Solana: slot) the balances are at. | | `supply`, `priceUsd` | string \| null, number \| null | Total supply (decimals applied) and the token's price now. | | `count`, `top10Share` | number \| null | How many addresses hold any, and the share of the supply the 10 largest hold, pools and the burn address left out. Only from a complete list; otherwise null. | | `holders[]` | array | Largest first; ties by address. On Solana a holder is the owner of the token accounts: one per owner. | | `holders[].address`, `balance`, `share`, `valueUsd` | string, string, number \| null, number \| null | The holder, its balance (decimals applied), its share of `supply`, and `balance × priceUsd`. | | `holders[].tags` | string[] | `pool`: a pool of the token keeps it there (the pool, the contract holding a V4 pool's funds, or a Solana pool's vault). `burn`: a burn address. `creator`: the token's creator, when Aurelia saw the creation. | | `holders[].labels` | string[] | The holder's [labels](https://app.aurelia.build/docs/labels). | | `holders[].position` | object \| null | The holder's position in the token, as in [wallet positions](https://app.aurelia.build/docs/wallet-positions) without `token`. Null for a pool, and for an address that never traded it. | | `next` | string \| null | Cursor for the next page. `null` on the last. | **Example** (captured 2026-09-28 18:03 UTC on a test instance, trimmed to 3 holders) ```bash curl -s -H 'Authorization: Bearer ' 'https://app.aurelia.build/api/tokens/evm:56/0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777/holders?limit=3' ``` ```json { "chain": "evm:56", "token": { "address": "0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777", "symbol": "TAX", "name": "Tax Token", "decimals": 18 }, "source": "indexed-transfers", "complete": true, "coverage": "Every holder: the index holds the token's whole history, and its largest balances match the chain.", "indexedSince": 1790546261920, "block": 1000750, "supply": "1000000000", "priceUsd": 0.006, "count": 13, "top10Share": 0.30015722, "holders": [ { "address": "0x702e6533e0182381606adbe3ccbb77f682ccc58b", "balance": "699837340", "share": 0.69983734, "valueUsd": 4199024.04, "tags": [ "pool" ], "labels": [], "position": null }, { "address": "0x0f325bd174c70440fa62c31dce9f69d54baf717e", "balance": "200000000", "share": 0.2, "valueUsd": 1200000, "tags": [ "creator" ], "labels": [ "creator" ], "position": null }, { "address": "0x5c952063c7fc8610ffdb798152d69f0b9550762b", "balance": "73000000", "share": 0.073, "valueUsd": 438000, "tags": [], "labels": [], "position": null } ], "next": "3" } ``` **Notes** - BSC, `indexed-transfers` (complete): the sums of every Transfer the index holds, served only when the index has the token's whole history: all of its supply minted since indexing began, no recorded gap after its first transfer, and at the newest indexed block its supply and its 20 largest balances equal to the chain's (a rebasing or reflection token fails). The verdict is kept 5 minutes. One database query, not kept. - BSC, `indexed-addresses` (incomplete): any other token, most of all one minted before indexing began. The 300 addresses whose indexed balance moved most, its pools, its creator and the burn address, each read with `balanceOf` at the chain's head: every balance is exact, but holders that have not moved the token since indexing began are missing, so there is no count. The node keeps no older state or logs: the holder count of an older token cannot be established. - Solana (only DEX transactions are indexed, so balances come from the chain): `token-accounts` (complete) lists every token account of the mint, one holder per owner; `largest-accounts` (incomplete) is a mint with too many accounts to list, its 20 largest accounts. - A list read from the chain is kept 30 s (BSC) or 60 s (Solana), the answer 5 s. Reads spend the chain's read budget: past it, `503` with `Retry-After: 10`. `422` for an address that is not a token mint, `502` when the chain cannot be read, `404 {"error":"unknown token"}` for a token Aurelia has never stored. - [The holders channel](https://app.aurelia.build/docs/live-holders) sends the first 100 again each time they change. ## Wallets Any address's portfolio (liquidity positions included), who funded it and what it is known to be, its positions, value over time, every transaction and its PnL; several wallets at once, or the top wallets by label. [Wallets and PnL](https://app.aurelia.build/docs/wallets) defines every figure, [Labels](https://app.aurelia.build/docs/labels) every label. ### Wallets and PnL A wallet is the address that traded a swap, its `trader`. On BSC that is the transaction sender, or, inside an ERC-4337 bundle, the smart account whose operation made the swap. On Solana it is the fee payer, or the owner whose token accounts traded when someone else paid the fee. Swaps that a solver or filler makes to fill someone else's order belong to no wallet (`trader: ""`), and no wallet endpoint counts them. Every wallet answer carries `indexedSince`, the unix ms of the chain's first indexed block, and the wallet's [labels](https://app.aurelia.build/docs/labels). Trades, positions and PnL cover only what happened after `indexedSince`: a token bought before then has no known cost. Balances are the exception: [the portfolio](https://app.aurelia.build/docs/wallet-portfolio) reads them from the chain. #### PnL Per wallet and token, over its indexed swaps. B and S are the tokens bought and sold, Bp and Sp the same over priced swaps only, BU and SU the USD paid and received in those. | Figure | Definition | |---|---| | Average buy, sell | `BU / Bp` and `SU / Sp`, null when the divisor is 0. A weighted average over the whole indexed history, not FIFO. | | Realized | `min(Sp, Bp) × (avg sell − avg buy)`: only the amount sold that priced buys cover has a known cost. 0 without sells; null with sells but no priced buy. | | Open | `max(0, B − S)`: what the trades leave held. Not the balance, which transfers change. | | Unrealized | `min(open, max(0, Bp − Sp)) × (price now − avg buy)`. Null without a price now or a priced buy, and null when no market could take `open` at that price: it is worth more than 10× the token's 24 h USD volume, or the price values the token over $100M while the pool it comes from holds under $1k (the rule that leaves such a token without a market cap). | | Total | Realized plus unrealized; null only when both are. | | In a window | Realized counts the window's sells against the all-time average buy, over `min(Sp in the window, Bp)`. Unrealized is always as of now. | #### Price now A token's price now is its price in its chart pool: the pool where it was the base with the most USD volume over its last 24 hourly buckets (for a token that is never the base, the busiest pool that prices it), from that pool's latest priced swap worth at least $0.01. BNB and SOL are priced as WBNB and WSOL. Prices are recomputed every 30 s. #### Quote tokens Stablecoins, and the native and wrapped native token, are listed as positions with `quote: true` but left out of every wallet total, win rate and distribution: their PnL is the price move of the currency a trader pays with, and each of their trades already counts on the other side. ### Labels Every row that names a wallet carries its `labels`, read the same way everywhere. Each label comes from indexed data by a stated rule. No outside list is used: none that is trustworthy covers BSC and Solana wallets, and a stale or guessed list would be wrong data. | Label | Rule | |---|---| | `creator` | Created a token Aurelia saw created: a launchpad's creation event named it. All indexed time. | | `insider` | In the last 30 days, bought a launched token (not its creator) in its creation transaction, or on Solana in its creation slot: in the same bundle as the launch. On BSC also: bought it within 600 blocks (about 4.5 minutes) of its creation after receiving BNB from its creator. One such buy is enough. | | `sniper` | In the last 30 days, first bought at least 3 different launched tokens within about 4 seconds of their creation, and was not an insider there: Solana slots 1–10 after the creation slot, BSC blocks 0–9 from the creation block, outside the creation transaction. A person clicking through an interface does not buy that fast; three times is a habit. | | `smart_money` | Over the last 30 days: realized PnL of at least $10,000 over at least 10 tokens with a realized result, at least half of them winners, and not a bot. The figures are those of [the analysis](https://app.aurelia.build/docs/wallet-analysis) over `30d`, quote tokens left out. | | `bot` | At least 3,500 swaps in the last 7 days: 500 a day, one every three minutes around the clock. | | `fresh` | Its first funding was less than 7 days ago. Known only once its funding is: [the portfolio](https://app.aurelia.build/docs/wallet-portfolio) reads it on request, so a wallet never looked up is not `fresh`. Never inferred from when Aurelia first saw a wallet. | #### When they appear A worker derives each launch's creator, insiders and snipers every minute, once the blocks of its window are indexed, and rebuilds every label and the wallet rankings every 15 minutes. A label appears at the first rebuild after the data it rests on is indexed. A rollback takes back what it derived from the dropped blocks. #### Where - In every list of wallets: [trades](https://app.aurelia.build/docs/trades) (the trader), [traders](https://app.aurelia.build/docs/token-traders), [holders](https://app.aurelia.build/docs/token-holders), [portfolios](https://app.aurelia.build/docs/wallet-portfolio) (the wallet and its funder), a token's `creator`, [activity](https://app.aurelia.build/docs/wallet-activity) (the counterparty of a transfer), and on the socket. - As a filter: `label=` on [`/trades`](https://app.aurelia.build/docs/trades) and [`/tokens/:chain/:address/traders`](https://app.aurelia.build/docs/token-traders). - As discovery: [`GET /wallets?label=`](https://app.aurelia.build/docs/wallet-list) ranks the labeled wallets by PnL, volume or win rate. `fresh` is not ranked: it rests on funding read on request. Top snipers by 30-day volume, captured on a test instance: ```bash curl -s 'https://app.aurelia.build/api/wallets?chains=evm:56&label=sniper&sort=volume30d&limit=2' { "chains": [ "evm:56" ], "label": "sniper", "sort": "volume30d", "rankedAt": 1790618277000, "wallets": [ { "chain": "evm:56", "wallet": "0x3304e22ddaa22bcdc5fca2269b418046ae7b566a", "labels": [ "sniper", "fresh" ], "pnl7dUsd": 59.80025976621041, "pnl30dUsd": 59.80025976621041, "volume30dUsd": 1068, "winRate30d": 1, "wins30d": 1, "losses30d": 0, "swaps7d": 22 }, { "chain": "evm:56", "wallet": "0x000000000000000000000000000000000000a00c", "labels": [ "sniper" ], "pnl7dUsd": 23.812869219293884, "pnl30dUsd": 23.812869219293884, "volume30dUsd": 180, "winRate30d": 1, "wins30d": 1, "losses30d": 0, "swaps7d": 14 } ], "next": null } ``` ### Wallet portfolio: `GET /wallets/:chain/:address` A wallet's holdings now, read from the chain and valued in USD, its liquidity positions in the pools Aurelia indexes, each token with the wallet's position in it; and who the wallet is: its labels and its first funding. **Parameters** | Name | In | Type | Default | Allowed values | |---|---|---|---|---| | `chain` | path | string | required | `evm:56` (BSC), `solana`, `evm:8453` (Base), `evm:4663` (Robinhood Chain) or `evm:1` (Ethereum) | | `address` | path | string | required | Wallet address (BSC) or account (Solana) | | `limit` | query | integer | `100` | 1–500 holdings | **Response** | Field | Type | Meaning | |---|---|---| | `chain`, `wallet`, `indexedSince` | string, string, number | As in every wallet answer (see [Wallets and PnL](https://app.aurelia.build/docs/wallets)). | | `labels` | string[] | The wallet's [labels](https://app.aurelia.build/docs/labels). | | `fundedBy` | { address, amount, time, tx, labels } \| null | Its first funding: who sent it its first BNB, ETH or SOL, how much (decimals applied), when and in which transaction, and the sender's labels. `null` when it cannot be established, with the reason in `fundedByReason`. | | `fundedByReason` | string \| null | Why `fundedBy` is null. | | `block`, `balancesAt` | number | The block (Solana: slot) the balances reflect, and when they were read, unix ms. | | `totalValueUsd` | number | Sum of the counted holdings: priced, and not thin. | | `pnl` | { realizedUsd, unrealizedUsd, totalUsd } | All-time PnL of every non-quote position, held or not. Unknown unrealized PnL counts as 0. | | `holdings[]` | array | Token balances above 0 and liquidity positions: the counted ones by `valueUsd`, then the thin ones, then the unpriced ones. | | `holdings[].kind` | "token" \| "lp" | A token balance, or a liquidity position in a pool Aurelia indexes. | | `holdings[] (token)` | object | `token` (`address` `"native"` for BNB, ETH or SOL), `balance` (decimals applied), `priceUsd` and `valueUsd` (null without a price), `allocation` (share of `totalValueUsd`, null for thin and unpriced holdings), `thin` (the token traded under $1,000 in 24 h, or no market backs its price, as for a null market cap: shown, not counted), `position` (as in [wallet positions](https://app.aurelia.build/docs/wallet-positions) without `token`; null when it never traded it). | | `holdings[] (lp)` | object | `protocol`, `pool`, `position` (the position's id: an NFT or an account; null for a fungible LP token), `lpToken` (`{ address, balance, supply, share }` for a fungible one), `tokens[]` (each `{ token, amount, fees, locked, priceUsd, valueUsd }`: what withdrawing all of it pays now, the uncollected fees (null where the protocol states none), and the part of the amount locked or vesting (Meteora DAMM v2; counted in the value)), `range` (concentrated liquidity and bins: `lower`, `upper` (exclusive), `current` ticks or bins, `inRange`, and the prices at them), `valueUsd`, `feesUsd`, `allocation`, `thin`. | | `unpriced`, `count` | number | Holdings without a price, and all holdings: `limit` cuts the list, never the totals. | | `liquidity` | { protocols, incomplete, notCovered, positionsIndexedSince, error } | What the liquidity read covered: the protocols read, those not read in full, those not covered and why, since when positions found through Aurelia's record of their transfers are known (unix ms; null while nothing is recorded), and the error when the chain could not be read (the portfolio then lists no position). | **Example** (captured 2026-09-28 18:03 UTC on a test instance, trimmed to 3 holdings) ```bash curl -s -H 'Authorization: Bearer ' 'https://app.aurelia.build/api/wallets/evm:56/0x3304e22ddaa22bcdc5fca2269b418046ae7b566a?limit=3' ``` ```json { "chain": "evm:56", "wallet": "0x3304e22ddaa22bcdc5fca2269b418046ae7b566a", "indexedSince": 1790546261920, "labels": [ "sniper", "fresh" ], "fundedBy": { "address": "0x8894e0a0c962cb723c1976a4421c95949be2d4e3", "amount": "0.25", "time": 1790616461920, "tx": "0x124aa51a8b73a7c373249b80ec4beb9fa4e80b4aa78a8bc73a64c781b3f2925d", "labels": [] }, "fundedByReason": null, "block": 1000748, "balancesAt": 1790618266812, "totalValueUsd": 214134, "pnl": { "realizedUsd": 59.5089770354906, "unrealizedUsd": 6.776263578034403e-14, "totalUsd": 59.50897703549067 }, "holdings": [ { "kind": "token", "token": { "address": "0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777", "symbol": "TAX", "name": "Tax Token", "decimals": 18 }, "balance": "21500000", "priceUsd": 0.006, "valueUsd": 129000, "allocation": 0.6024265179747261, "thin": false, "position": { "quote": false, "trades": 22, "buys": 12, "sells": 10, "unpricedTrades": 0, "bought": "20118000", "sold": "9800", "open": "20108200", "boughtUsd": 1008, "soldUsd": 60, "avgBuyPriceUsd": 0.00005010438413361169, "avgSellPriceUsd": 0.006122448979591836, "priceUsd": 0.006, "priceAt": 1790618400000, "realizedPnlUsd": 59.5089770354906, "unrealizedPnlUsd": null, "totalPnlUsd": 59.5089770354906, "firstTradeAt": 1790546864920, "lastTradeAt": 1790618578665 } }, { "kind": "lp", "protocol": "pancakeswap-v2", "pool": "0x702e6533e0182381606adbe3ccbb77f682ccc58b", "position": null, "lpToken": { "address": "0x702e6533e0182381606adbe3ccbb77f682ccc58b", "balance": "2000000000000000000000", "supply": "100000000000000000000000", "share": 0.02 }, "tokens": [ { "token": { "address": "0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777", "symbol": "TAX", "name": "Tax Token", "decimals": 18 }, "amount": "14000000", "fees": null, "raw": { "amount": "14000000000000000000000000", "fees": null }, "priceUsd": 0.006, "valueUsd": 84000 }, { "token": { "address": "0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c", "symbol": "WBNB", "name": "Wrapped BNB", "decimals": 18 }, "amount": "0.14", "fees": null, "raw": { "amount": "140000000000000000", "fees": null }, "priceUsd": 600, "valueUsd": 84.00000000000001 } ], "range": null, "valueUsd": 84084, "feesUsd": null, "allocation": 0.39267001036734006, "thin": false }, { "kind": "token", "token": { "address": "native", "symbol": "BNB", "name": "BNB", "decimals": 18 }, "balance": "1.25", "priceUsd": 600, "valueUsd": 750, "allocation": 0.0035024797556670122, "thin": false, "position": null } ], "unpriced": 0, "count": 6, "liquidity": { "protocols": [ "pancakeswap-v2", "pancakeswap-v3", "uniswap-v2", "uniswap-v3" ], "incomplete": [], "notCovered": [ { "protocol": "pancakeswap-infinity-cl", "reason": "its positions are not enumerable per owner" } ], "error": null } } ``` **Notes** - Balances are read from the chain: on BSC, BNB and every token the wallet sent, received or traded since indexing began; on Solana, SOL and every token account it owns. Cached 15 s per wallet, the answer 5 s. `502` when the chain cannot be read, `422` for a BSC wallet that touched more than 2,000 tokens: a partial portfolio would be wrong, so neither sends one. - Liquidity positions: on BSC, the LP tokens among the balances (a V2-style pool is its own LP token) and the position NFTs of each enumerable position manager; on Solana, the token accounts whose mint is a position NFT or an LP mint. What withdrawing all of it pays now, read from one block or slot, rounded down as the program rounds (Raydium AMM v4 less the PnL it owes the protocol, CPMM less the fees held in its vaults); uncollected fees where the chain states them. An LP token a position stands for leaves the token holdings. Farm rewards, lending and staking are not covered. - Positions nothing on chain lists by owner (Uniswap V4 and PancakeSwap Infinity NFTs on BSC, Meteora DLMM accounts on Solana) come from Aurelia's record of every transfer, creation and close since `positionsIndexedSince`; each one is checked on chain (owner, liquidity) before it is shown. One whose last transfer came before that is not known, so these protocols are always in `incomplete`. BSC positions in pools whose hook can change what a withdrawal pays are left out. - The first funding is read on request and kept: for good once found. Solana: the wallet's oldest transactions; BSC: the first BNB transfer into it the index saw, confirmed by its balance and nonce being 0 the block before (the node must still keep that state). A request waits 3 s for it; a slower read goes on, and `fundedByReason` says to ask again. - The thin rule keeps airdropped tokens out of the total: a price from a pool nobody trades is no value. BNB and SOL are priced as WBNB and WSOL. - [The balances channel](https://app.aurelia.build/docs/live-wallets) sends this answer again whenever it changes. ### Wallets: `GET /wallets` Several wallets' portfolios with totals, on every listed chain each address fits (`wallets=`); or wallet discovery: the top wallets by PnL, volume or win rate, by label. **Parameters** | Name | In | Type | Default | Allowed values | |---|---|---|---|---| | `wallets` | query | string | none | 1–10 items, comma-separated: `:
`, or a bare address (every listed chain its format fits) | | `chains` | query | string | every listed chain | Comma-separated: `evm:56` (BSC), `solana`, `evm:8453` (Base), `evm:4663` (Robinhood Chain) or `evm:1` (Ethereum) | | `limit` | query | integer | `100` (wallets), `50` (discovery) | Holdings per portfolio 1–500; wallets per page 1–100 | | `label` | query | string | any wallet | Discovery: `creator`, `insider`, `sniper`, `smart_money`, `bot` (not `fresh`: see [labels](https://app.aurelia.build/docs/labels)) | | `sort` | query | string | `pnl30d` | Discovery: `pnl7d`, `pnl30d`, `volume30d`, `winRate30d` | | `cursor` | query | string | first page | Discovery: the previous page's `next` | **Response** | Field | Type | Meaning | |---|---|---| | `wallets`, `chains` | string[] | `wallets=`: the wallets and chains answered. | | `totalValueUsd`, `pnl` | number, object | `wallets=`: sums of the portfolios' `totalValueUsd` and `pnl`. | | `complete` | boolean | `wallets=`: `false` when a portfolio could not be read: the totals leave it out. | | `portfolios[]` | array | `wallets=`: one per wallet and chain, in request order: the [wallet portfolio](https://app.aurelia.build/docs/wallet-portfolio) answer, or `{ chain, wallet, status, error }` for one that could not be read (the status its own route would answer). | | `chains`, `label`, `sort`, `rankedAt` | string[], string \| null, string, number \| null | Discovery: the request, and when the ranking was built, unix ms. | | `wallets[]` | array | Discovery: highest first. Each: `chain`, `wallet`, `labels`, `pnl7dUsd`, `pnl30dUsd`, `volume30dUsd`, `winRate30d` (null without results), `wins30d`, `losses30d`, `swaps7d`. | | `next` | string \| null | Discovery: cursor for the next page. | **Example** (captured 2026-09-28 18:03 UTC on a test instance, trimmed to 1 holding each) ```bash curl -s -H 'Authorization: Bearer ' 'https://app.aurelia.build/api/wallets?wallets=evm:56:0x3304e22ddaa22bcdc5fca2269b418046ae7b566a,solana:Fm1Nzv3EcgGMW2MFHHGKjdVDbvHsMMtoRdoBDmUmsFFE&limit=1' ``` ```json { "wallets": [ "0x3304e22ddaa22bcdc5fca2269b418046ae7b566a", "Fm1Nzv3EcgGMW2MFHHGKjdVDbvHsMMtoRdoBDmUmsFFE" ], "chains": [ "evm:56", "solana" ], "totalValueUsd": 214764, "pnl": { "realizedUsd": 66.2589770354906, "unrealizedUsd": 57.375000000000064, "totalUsd": 123.63397703549066 }, "complete": true, "portfolios": [ { "chain": "evm:56", "wallet": "0x3304e22ddaa22bcdc5fca2269b418046ae7b566a", "indexedSince": 1790546261920, "labels": [ "sniper", "fresh" ], "fundedBy": { "address": "0x8894e0a0c962cb723c1976a4421c95949be2d4e3", "amount": "0.25", "time": 1790616461920, "tx": "0x124aa51a8b73a7c373249b80ec4beb9fa4e80b4aa78a8bc73a64c781b3f2925d", "labels": [] }, "fundedByReason": null, "block": 1000748, "balancesAt": 1790618266812, "totalValueUsd": 214134, "pnl": { "realizedUsd": 59.5089770354906, "unrealizedUsd": 6.776263578034403e-14, "totalUsd": 59.50897703549067 }, "holdings": [ { "kind": "token", "token": { "address": "0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777", "symbol": "TAX", "name": "Tax Token", "decimals": 18 }, "balance": "21500000", "priceUsd": 0.006, "valueUsd": 129000, "allocation": 0.6024265179747261, "thin": false, "position": { "quote": false, "trades": 22, "buys": 12, "sells": 10, "unpricedTrades": 0, "bought": "20118000", "sold": "9800", "open": "20108200", "boughtUsd": 1008, "soldUsd": 60, "avgBuyPriceUsd": 0.00005010438413361169, "avgSellPriceUsd": 0.006122448979591836, "priceUsd": 0.006, "priceAt": 1790618400000, "realizedPnlUsd": 59.5089770354906, "unrealizedPnlUsd": null, "totalPnlUsd": 59.5089770354906, "firstTradeAt": 1790546864920, "lastTradeAt": 1790618578665 } } ], "unpriced": 0, "count": 6, "liquidity": { "protocols": [ "pancakeswap-v2", "pancakeswap-v3", "uniswap-v2", "uniswap-v3" ], "incomplete": [], "notCovered": [ { "protocol": "pancakeswap-infinity-cl", "reason": "its positions are not enumerable per owner" } ], "error": null } }, { "chain": "solana", "wallet": "Fm1Nzv3EcgGMW2MFHHGKjdVDbvHsMMtoRdoBDmUmsFFE", "indexedSince": 1790546261920, "labels": [ "insider" ], "fundedBy": null, "fundedByReason": "the chain could not be read (Unable to connect. Is the computer able to access the url?)", "block": 449600022, "balancesAt": 1790618266815, "totalValueUsd": 630, "pnl": { "realizedUsd": 6.75, "unrealizedUsd": 57.37499999999999, "totalUsd": 64.125 }, "holdings": [ { "kind": "token", "token": { "address": "native", "symbol": "SOL", "name": "Solana", "decimals": 9 }, "balance": "3.4", "priceUsd": 150, "valueUsd": 510, "allocation": 0.8095238095238095, "thin": false, "position": null } ], "unpriced": 0, "count": 3, "liquidity": { "protocols": [ "raydium-cpmm", "orca-whirlpool" ], "incomplete": [], "notCovered": [], "error": null } } ] } ``` **Notes** - `wallets=`: each portfolio reads its wallet's balances from the chain, as [the wallet portfolio](https://app.aurelia.build/docs/wallet-portfolio) does, and shares that route's caches. A bare `0x` address is read on BSC, a base58 one on Solana. `400` for more than 10, one that fits no chain of `chains`, or `label`, `sort` or `cursor` alongside. - Discovery reads a ranking rebuilt every 15 minutes with the labels (the top 1,000 per chain, label and sort), never a scan per request: the wallets that traded at least $1,000 in 30 days, or carry a label. Figures are the [analysis](https://app.aurelia.build/docs/wallet-analysis)'s over 7 and 30 days, quote tokens left out; `winRate30d` ranks only wallets with 10+ results. Cached a minute. ### Wallet analysis: `GET /wallets/:chain/:address/analysis` A wallet's PnL, win rate, volume, return distribution and PnL timeline over a window, with its labels. **Parameters** | Name | In | Type | Default | Allowed values | |---|---|---|---|---| | `chain` | path | string | required | `evm:56` (BSC), `solana`, `evm:8453` (Base), `evm:4663` (Robinhood Chain) or `evm:1` (Ethereum) | | `address` | path | string | required | Wallet address (BSC) or account (Solana) | | `window` | query | string | `7d` | `1d`, `7d`, `30d`, `90d` | **Response** | Field | Type | Meaning | |---|---|---| | `chain`, `wallet`, `labels`, `indexedSince`, `window` | string, string, string[], number, string | As in every wallet answer, its [labels](https://app.aurelia.build/docs/labels), and the window. | | `realizedPnlUsd` | number | PnL of the window's sells, against each position's all-time average buy price. | | `unrealizedPnlUsd`, `totalPnlUsd` | number | Unrealized PnL now, of every position, traded in the window or not (unknown ones count as 0); and the sum. | | `boughtUsd`, `soldUsd`, `trades`, `buys`, `sells`, `tokens` | number | The window's swaps, and the tokens they traded. | | `wins`, `losses`, `winRate` | number, number, number \| null | Tokens with realized PnL above and below 0 in the window; `winRate` is wins / (wins + losses), null without either. | | `distribution` | object | Tokens sold in the window by realized return (average sell / average buy − 1): `gt500`, `200to500`, `50to200`, `0to50`, `minus50to0`, `ltMinus50`. | | `timeline[]` | array | `t` (bucket start, unix ms), `realizedPnlUsd`, `boughtUsd`, `soldUsd`, `trades`. Hourly for `1d`, per UTC day otherwise; ascending, empty buckets left out. | | `best[]`, `worst[]` | array | Up to 3 positions with the largest realized gain and loss in the window. | **Example** (captured 2026-09-28 18:03 UTC on a test instance, trimmed to the last 2 buckets) ```bash curl -s -H 'Authorization: Bearer ' 'https://app.aurelia.build/api/wallets/evm:56/0x3304e22ddaa22bcdc5fca2269b418046ae7b566a/analysis?window=1d' ``` ```json { "chain": "evm:56", "wallet": "0x3304e22ddaa22bcdc5fca2269b418046ae7b566a", "labels": [ "sniper", "fresh" ], "indexedSince": 1790546261920, "window": "1d", "realizedPnlUsd": 59.5089770354906, "unrealizedPnlUsd": 6.776263578034403e-14, "totalPnlUsd": 59.50897703549067, "boughtUsd": 1608, "soldUsd": 60, "trades": 24, "buys": 14, "sells": 10, "tokens": 3, "wins": 1, "losses": 0, "winRate": 1, "distribution": { "gt500": 1, "200to500": 0, "50to200": 0, "0to50": 0, "minus50to0": 0, "ltMinus50": 0 }, "timeline": [ { "t": 1790611200000, "realizedPnlUsd": 5.95089770354906, "boughtUsd": 0, "soldUsd": 6, "trades": 1 }, { "t": 1790618400000, "realizedPnlUsd": 0, "boughtUsd": 600, "soldUsd": 0, "trades": 2 } ], "best": [ { "token": { "address": "0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777", "symbol": "TAX", "name": "Tax Token", "decimals": 18 }, "quote": false, "trades": 22, "buys": 12, "sells": 10, "unpricedTrades": 0, "bought": "20118000", "sold": "9800", "open": "20108200", "boughtUsd": 1008, "soldUsd": 60, "avgBuyPriceUsd": 0.00005010438413361169, "avgSellPriceUsd": 0.006122448979591836, "priceUsd": 0.006, "priceAt": 1790618400000, "realizedPnlUsd": 59.5089770354906, "unrealizedPnlUsd": null, "totalPnlUsd": 59.5089770354906, "firstTradeAt": 1790546864920, "lastTradeAt": 1790618578665 } ], "worst": [] } ``` **Notes** - Non-quote positions only. Windows are hourly buckets, the newest still filling: `1d` is the last 24, `7d` the last 168. - From the wallet's own rows, never the chain. Cached 5 s. An unknown wallet answers `200` with zeros. ### Wallet positions: `GET /wallets/:chain/:address/positions` Every token the wallet traded since indexing began, open, closed or all: amounts, average prices, realized and unrealized PnL. With `token=`, one position. **Parameters** | Name | In | Type | Default | Allowed values | |---|---|---|---|---| | `chain` | path | string | required | `evm:56` (BSC), `solana`, `evm:8453` (Base), `evm:4663` (Robinhood Chain) or `evm:1` (Ethereum) | | `address` | path | string | required | Wallet address (BSC) or account (Solana) | | `token` | query | string | none | One position: a token address or mint, or `native` for BNB, ETH or SOL | | `status` | query | string | `all` | `open`, `closed`, `all` | | `sort` | query | string | `recent` | `recent` (last trade), `pnl` (total), `realized`, `volume` | | `limit` | query | integer | `50` | 1–200 | | `cursor` | query | string | first page | The previous page's `next`, as it came | **Response** | Field | Type | Meaning | |---|---|---| | `chain`, `wallet`, `labels`, `indexedSince`, `status` | string, string, string[], number, string | As in every wallet answer, with its [labels](https://app.aurelia.build/docs/labels). | | `positions[]` | array | Highest first by `sort`; a null PnL sorts last, ties by token address. | | `positions[].token` | { address, symbol, name, decimals } | `address` is `"native"` for BNB, ETH or SOL. | | `quote` | boolean | A stablecoin, or the native or wrapped native token: listed, but left out of every wallet total. | | `trades`, `buys`, `sells`, `unpricedTrades` | number | The swaps of the token. A buy is one where the wallet received it. `unpricedTrades` had no USD price and are left out of the USD figures. | | `bought`, `sold` | string | Token amounts, decimals applied. | | `open` | string | `bought − sold`, at least 0: what the trades leave held. Transfers are not counted, so it can differ from the balance. | | `boughtUsd`, `soldUsd` | number | USD value of the priced buys and sells. | | `avgBuyPriceUsd`, `avgSellPriceUsd` | number \| null | USD per token over the priced buys, and sells. Null without one. | | `priceUsd`, `priceAt` | number \| null | The token's price now, and the start of the hour of the swap it came from, unix ms (see [Wallets and PnL](https://app.aurelia.build/docs/wallets)). | | `realizedPnlUsd`, `unrealizedPnlUsd`, `totalPnlUsd` | number \| null | See [Wallets and PnL](https://app.aurelia.build/docs/wallets). Null is unknown, never 0. | | `firstTradeAt`, `lastTradeAt` | number | Unix ms. | | `next` | string \| null | Cursor for the next page. Null on the last. | | `position` | object \| null | With `token`, in place of `positions`: that position, null when the wallet never traded the token. | **Example** (captured 2026-09-28 18:03 UTC on a test instance, trimmed to 2 positions) ```bash curl -s -H 'Authorization: Bearer ' 'https://app.aurelia.build/api/wallets/evm:56/0x3304e22ddaa22bcdc5fca2269b418046ae7b566a/positions?sort=pnl&limit=2' ``` ```json { "chain": "evm:56", "wallet": "0x3304e22ddaa22bcdc5fca2269b418046ae7b566a", "labels": [ "sniper", "fresh" ], "indexedSince": 1790546261920, "status": "all", "positions": [ { "token": { "address": "0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777", "symbol": "TAX", "name": "Tax Token", "decimals": 18 }, "quote": false, "trades": 22, "buys": 12, "sells": 10, "unpricedTrades": 0, "bought": "20118000", "sold": "9800", "open": "20108200", "boughtUsd": 1008, "soldUsd": 60, "avgBuyPriceUsd": 0.00005010438413361169, "avgSellPriceUsd": 0.006122448979591836, "priceUsd": 0.006, "priceAt": 1790618400000, "realizedPnlUsd": 59.5089770354906, "unrealizedPnlUsd": null, "totalPnlUsd": 59.5089770354906, "firstTradeAt": 1790546864920, "lastTradeAt": 1790618578665 }, { "token": { "address": "0x5eed00000000000000000000000000000000b444", "symbol": "NEWT2", "name": "Newt Two", "decimals": 18 }, "quote": false, "trades": 1, "buys": 1, "sells": 0, "unpricedTrades": 0, "bought": "20000000", "sold": "0", "open": "20000000", "boughtUsd": 300, "soldUsd": 0, "avgBuyPriceUsd": 0.000015, "avgSellPriceUsd": null, "priceUsd": 0.000015000000000000002, "priceAt": 1790546400000, "realizedPnlUsd": 0, "unrealizedPnlUsd": 3.3881317890172014e-14, "totalPnlUsd": 3.3881317890172014e-14, "firstTradeAt": 1790547464920, "lastTradeAt": 1790547464920 } ], "next": "3.3881317890172014e-14_0x5eed00000000000000000000000000000000b444" } ``` **Notes** - A position is closed when its trades left at most 0.1% of what they bought open (`open`), as a position sold down to dust is; one the wallet only sold is closed too. - Quote positions (`quote: true`) are listed with their PnL, which is only the price move of the currency paid with: every wallet total leaves them out. - A page continues after the previous page's last position in the order as it is when read: sorted by PnL, a position can change place between pages as prices move. - From the wallet's own rows, never the chain: one database query on a miss. Cached 5 s. [The positions channel](https://app.aurelia.build/docs/live-wallets) sends them again as they change. ### Value history: `GET /wallets/:chain/:address/history` What a wallet's holdings were worth over the last day, week or month; with `token=`, one position's balance, value and PnL over time. **Parameters** | Name | In | Type | Default | Allowed values | |---|---|---|---|---| | `chain` | path | string | required | `evm:56` (BSC), `solana`, `evm:8453` (Base), `evm:4663` (Robinhood Chain) or `evm:1` (Ethereum) | | `address` | path | string | required | Wallet address (BSC) or account (Solana) | | `window` | query | string | `7d` | `1d` and `7d` (hourly points), `30d` (daily) | | `token` | query | string | none | One position's series: a token, or `native` (Solana) | **Response** | Field | Type | Meaning | |---|---|---| | `chain`, `wallet`, `labels`, `indexedSince`, `window`, `resolution` | string, string, string[], number, string, string | As in every wallet answer, and the spacing of the points: `1h` or `1d`. | | `exactSince` | number \| null | Unix ms after which values are exact (see the notes). Points start after it. | | `block`, `balancesAt` | number | The block (Solana: slot) the balances were read at, and when, unix ms. | | `points[]` | [number, number] | Without `token`: `[t, valueUsd]`, ascending: each boundary of the window after `exactSince`, then one at `balancesAt`, the value now. | | `points[] (token)` | object | With `token`: `t`, `balance` (null when it cannot be established), `priceUsd`, `valueUsd`, `realizedPnlUsd`, `unrealizedPnlUsd`, `totalPnlUsd` at each point; `balanceUnknown` says why a balance is null. | | `excluded[]` | array | `{ token, reason }`: tokens left out because their balance cannot be traced back. | | `coverage` | { transactions, complete, listedAt, stopped } | Solana: the transactions read to go back, whether they reach the window's start, and where a read bound stopped. | | `note` | string | What is left out, in words. | **Example** (captured 2026-09-28 18:03 UTC on a test instance, trimmed to the last 3 points) ```bash curl -s -H 'Authorization: Bearer ' 'https://app.aurelia.build/api/wallets/evm:56/0x3304e22ddaa22bcdc5fca2269b418046ae7b566a/history?window=1d' ``` ```json { "labels": [ "sniper", "fresh" ], "chain": "evm:56", "wallet": "0x3304e22ddaa22bcdc5fca2269b418046ae7b566a", "indexedSince": 1790546261920, "window": "1d", "resolution": "1h", "exactSince": 1790546862000, "block": 1000748, "balancesAt": 1790618266812, "points": [ [ 1790611200000, 129906 ], [ 1790614800000, 132544.89795918367 ], [ 1790618266812, 129300 ] ], "excluded": [], "note": "Tokens only: native BNB / ETH is left out, since gas and internal transfers change it without a transfer the index sees, and the chain's endpoints keep no past balances to read it at each point." } ``` **Notes** - The balances now are [the portfolio](https://app.aurelia.build/docs/wallet-portfolio)'s chain read. A value at a point is each token's balance then, at its price in the hour before (the busiest priced pool that hour), counted as in the portfolio's total. - BSC: balances go back through the indexed transfers since each point, so values are exact only while the index holds every transfer since then: after the chain's first indexed block, its last skipped range and the start of per-wallet transfers (kept 30 days). Native BNB is left out: gas and internal transfers change it without a transfer the index sees, and the node keeps no past state to read it (`token=native` answers `422`). - Solana: balances go back through each of the wallet's transactions since the point, read from the chain (exact: SOL and token balances before and after), native SOL included. Points start after the oldest transaction a request could read; a later request reads further back. A token whose transactions do not link up changed in a transaction that does not name the wallet: it is left out, with the reason. - `422` while indexing has not reached the block the balances were read at, or for a wallet that moved too much to value in one request; `502` when the chain cannot be read; `503` while Solana's read budget is spent. Cached 30 s. ### Wallet activity: `GET /wallets/:chain/:address/activity` Every transaction of a wallet, newest first, with what it did: swaps, liquidity adds and removes, and transfers; on Solana complete, read from the chain. **Parameters** | Name | In | Type | Default | Allowed values | |---|---|---|---|---| | `chain` | path | string | required | `evm:56` (BSC), `solana`, `evm:8453` (Base), `evm:4663` (Robinhood Chain) or `evm:1` (Ethereum) | | `address` | path | string | required | Wallet address (BSC) or account (Solana) | | `type` | query | string | all | `swap`, `liquidity` or `transfer`: only those actions | | `limit` | query | integer | `50` | 1–100 transactions | | `cursor` | query | string | first page | The previous page's `next`: `time.block.txIndex` on BSC, a signature on Solana | **Response** | Field | Type | Meaning | |---|---|---| | `chain`, `wallet`, `labels`, `indexedSince` | string, string, string[], number | As in every wallet answer. | | `covers`, `note` | string[], string | What is listed, and what is not, in words. | | `transfersSince` | number \| null | BSC: from when transfers are listed, unix ms (kept 30 days). | | `coverage` | { listedAt, start, stopped } | Solana: when the wallet's transaction list was read, whether it reaches its first transaction, and where a read bound stopped. | | `transactions[]` | array | Newest first: `tx`, `time`, `block`, `txIndex`, `actions`. Solana adds `failed`, `fee` (SOL the wallet paid; null when another paid) and `changes` (the wallet's exact balance change per token: `{ token, amount, amountRaw }`). | | `actions[] (swap)` | object | `type: "swap"`, `eventIndex`, `protocol`, `pool`; `tokenIn` and `amountIn` (what the wallet paid), `tokenOut` and `amountOut` (what it received), `amountUsd`. | | `actions[] (liquidity)` | object | `type: "liquidity"`, `kind` (`add`, `remove`), `eventIndex`, `protocol`, `pool`, `token0`, `amount0`, `token1`, `amount1`, `valueUsd`. | | `actions[] (transfer)` | object | `type: "transfer"`, `eventIndex`, `direction` (`in`, `out`, `self`), `token`, `amount` (null for a token Aurelia never stored), `amountRaw`, `from`, `to` (null for a mint or a burn), `labels` (the counterparty's). | | `next` | string \| null | Cursor for the next older page. `null` at the end. | **Example** (captured 2026-09-28 18:03 UTC on a test instance, trimmed to 2 transactions) ```bash curl -s -H 'Authorization: Bearer ' 'https://app.aurelia.build/api/wallets/evm:56/0x3304e22ddaa22bcdc5fca2269b418046ae7b566a/activity?limit=2' ``` ```json { "chain": "evm:56", "wallet": "0x3304e22ddaa22bcdc5fca2269b418046ae7b566a", "labels": [ "sniper", "fresh" ], "indexedSince": 1790546261920, "transfersSince": 1790546861000, "covers": [ "swaps", "liquidity", "transfers" ], "note": "Swaps and liquidity events the index decoded, ERC-20 transfers and native transfers sent as a transaction's value (transfers kept 30 days: see transfersSince). Internal native transfers and gas are not indexed. A transfer of a token swapped or pooled in the same transaction is part of that action, and is not listed again.", "transactions": [ { "tx": "0x2b170741f16da7ea4287bbd163e3365f89c73c4face2b6ed032487c0dd9b0cb4", "time": 1790618578665, "block": 1000750, "txIndex": 0, "actions": [ { "type": "swap", "eventIndex": 4, "protocol": "pancakeswap-v2", "pool": "0x702e6533e0182381606adbe3ccbb77f682ccc58b", "tokenIn": { "address": "0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c", "symbol": "WBNB", "name": "Wrapped BNB", "decimals": 18 }, "amountIn": "0.5", "tokenOut": { "address": "0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777", "symbol": "TAX", "name": "Tax Token", "decimals": 18 }, "amountOut": "50000", "amountUsd": 300 } ] }, { "tx": "0xcd32103a902cba28b871741adf595a6b209ce0b66d6849cc8a603842adb07729", "time": 1790618554904, "block": 1000749, "txIndex": 0, "actions": [ { "type": "swap", "eventIndex": 4, "protocol": "pancakeswap-v2", "pool": "0x702e6533e0182381606adbe3ccbb77f682ccc58b", "tokenIn": { "address": "0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c", "symbol": "WBNB", "name": "Wrapped BNB", "decimals": 18 }, "amountIn": "0.5", "tokenOut": { "address": "0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777", "symbol": "TAX", "name": "Tax Token", "decimals": 18 }, "amountOut": "50000", "amountUsd": 300 } ] } ], "next": "1790618554904.1000749.0" } ``` **Notes** - BSC, from the index: swaps, liquidity events, every ERC-20 transfer and BNB sent as a transaction's value. Internal BNB transfers and gas are not indexed. A transfer of a token swapped or pooled in the same transaction is a leg of that action, and is not listed again. One database query a page. - Solana, from the chain: every transaction that names the wallet, with its exact balance changes; swaps and liquidity events from the index (with USD values) and the System Program and SPL Token transfers the node parsed. A token received by a transaction that does not name the wallet is not listed: Solana lists a transaction under the addresses it names. A request reads at most 40 transactions; `503` while the chain's read budget is spent, `502` when it cannot be read. - A page holds at most `limit` transactions, fewer when `type` leaves some without actions or a read bound stops it: follow `next` until it is `null`. Cached 5 s. ## Pulse Launches as they happen: fresh launches, bonding curves close to graduating, and fresh graduations, with filters and sorting. Read a snapshot over REST, or follow a list with [the pulse channel](https://app.aurelia.build/docs/live-pulse). ### Pulse Pulse is a chain's board of launches: tokens a launchpad just created, bonding curves close to graduating, and curves that just graduated. Read a snapshot with [`GET /chains/:chain/pulse`](https://app.aurelia.build/docs/chain-pulse), or follow a list with [the pulse channel](https://app.aurelia.build/docs/live-pulse). #### Views | View | Holds | Board | |---|---|---| | `new` | Launches that have not graduated | The newest 1,000 of the last 24 h | | `graduating` | Open bonding curves that traded in the last hour | The 500 with the highest `progress` | | `graduated` | Curves that completed in the last 24 h | The newest 500 | A launch is recorded only from a launchpad's own creation event, such as pump.fun's on Solana and four.meme's on BSC. #### Filters Filters are query parameters (on the socket, parameters of the subscribe message), and a token must match all of them. Filtering, sorting and paging run over the view's board. | Parameter | Keeps | Example | |---|---|---| | `=..` | Values in the range, bounds included. Either side may be empty. A `null` value never matches. | `marketCapUsd=5000..`, `progress=..0.9`, `age=60..600` | | `launchpad=a,b` | Any of them | `launchpad=pumpfun` | | `has=a,b` | Every one of them: `logo`, `website`, `twitter`, `telegram`, `discord` | `has=logo,twitter` | | `keywords=a,b` | The symbol or name contains any of them, case-insensitive | `keywords=cat,dog` | | `excludeKeywords=a,b` | The symbol or name contains none of them | `excludeKeywords=test` | | `sort=` | Any range key, or `createdAt`, `graduatedAt`, `latestTradeAt`. By default `createdAt` (`new`), `progress` (`graduating`), `graduatedAt` (`graduated`) | `sort=windows.1h.volumeUsd` | | `order=desc` or `asc` | `desc` by default. Nulls sort last either way; ties break by `address`, ascending | `order=asc` | | `limit`, `offset` | `limit` 1–100 (50 by default), `offset` 0 or more. `total` counts every match | `limit=20&offset=20` | Range keys: `priceUsd`, `marketCapUsd`, `liquidityUsd`, `progress`, `age`, `creatorNetShare`, `sniperCount`, `sniperNetShare`, and `windows..`, where `` is `5m`, `1h`, `6h` or `24h` and `` is one of `priceChange`, `volumeUsd`, `buyVolumeUsd`, `sellVolumeUsd`, `trades`, `buys`, `sells`, `traders`, `buyers`, `sellers`, `feesPaidUsd`. Examples: ``` # pump.fun launches under 10 minutes old, with a logo and a Twitter link https://app.aurelia.build/api/chains/solana/pulse?launchpad=pumpfun&age=..600&has=logo,twitter # curves at least 80% sold that traded over $5k in the last hour, by that volume https://app.aurelia.build/api/chains/solana/pulse?view=graduating&progress=0.8..&windows.1h.volumeUsd=5000..&sort=windows.1h.volumeUsd # four.meme launches on BSC without "test" in the name, oldest first https://app.aurelia.build/api/chains/evm:56/pulse?launchpad=fourmeme&excludeKeywords=test&order=asc ``` Launchpads: `fourmeme`, `flap` and `genius` on BSC; `pumpfun`, `letsbonk`, `raydium-launchlab` and `meteora-dbc` on Solana; `doppler`, `zora`, `clanker`, `virtuals`, `o1`, `basestonk`, `flaunch` and `rwagmi` on Base; `flap`, `doppler`, `pons`, `pools-trade`, `torch` and `frontier` on Robinhood Chain; `clanker` and `stockereum` on Ethereum. An unknown parameter, a malformed range, an unknown view or a malformed launchpad id answers `400`, and the error names the parameter. #### Not provided on the board - Holder counts and top holdings: [token holders](https://app.aurelia.build/docs/token-holders) answers them one token at a time. - Snipers here are by swaps only: wallets that bought in the launch block or the 4 after it. Wallet [labels](https://app.aurelia.build/docs/labels) (insider, sniper) follow their own rules across launches. ### Pulse board: `GET /chains/:chain/pulse` The chain's board of launches: fresh launches, bonding curves close to graduating, and fresh graduations, filtered and sorted. A snapshot at most a few seconds old; follow it with [the pulse channel](https://app.aurelia.build/docs/live-pulse). **Parameters** | Name | In | Type | Default | Allowed values | |---|---|---|---|---| | `chain` | path | string | required | `evm:56` (BSC), `solana`, `evm:8453` (Base), `evm:4663` (Robinhood Chain) or `evm:1` (Ethereum) | | `view` | query | string | `new` | `new`, `graduating`, `graduated` | | `marketCapUsd` | query | string | any | A range `min..max`, bounds included, either side empty: `5000..`. Every range key works so: see [Pulse](https://app.aurelia.build/docs/pulse) | | `age` | query | string | any | A range of seconds since `createdAt`: `..600` is the last 10 minutes | | `launchpad` | query | string | all | Comma-separated, any of them. BSC: `fourmeme`, `flap`, `genius`; Solana: `pumpfun`, `letsbonk`, `raydium-launchlab`, `meteora-dbc`; Base: `doppler`, `zora`, `clanker`, `virtuals`, `o1`, `basestonk`, `flaunch`, `rwagmi`; Robinhood Chain: `flap`, `doppler`, `pons`, `pools-trade`, `torch`, `frontier`; Ethereum: `clanker`, `stockereum` | | `has` | query | string | none | Comma-separated, every one of them: `logo`, `website`, `twitter`, `telegram`, `discord` | | `keywords` | query | string | none | Comma-separated: the symbol or name contains any of them, case-insensitive | | `excludeKeywords` | query | string | none | Comma-separated: the symbol or name contains none of them | | `sort` | query | string | `createdAt`, `progress` or `graduatedAt`, by view | Any range key, or `createdAt`, `graduatedAt`, `latestTradeAt` | | `order` | query | string | `desc` | `desc`, `asc`. Nulls sort last either way; ties break by `address` | | `limit` | query | integer | `50` | 1–100 | | `offset` | query | integer | `0` | 0 or more | **Response** | Field | Type | Meaning | |---|---|---| | `chain`, `view`, `total`, `updatedAt` | string, string, number, number | The request; board tokens that match the filters before `limit` and `offset`; when the board was built, unix ms. | | `tokens[]` | array | The page, sorted. | | `address`, `symbol`, `name`, `logo` | string, string \| null, string \| null, string \| null | The token, and its logo's absolute URL. | | `launchpad`, `pool`, `createdAt`, `creator` | string, string \| null, number, string | The launch protocol, the pool it launched into, the launch (unix ms) and the wallet that made it. | | `socials` | object | From the token's metadata document: `https://` URLs or `null`. | | `priceUsd`, `marketCapUsd`, `liquidityUsd` | number \| null | As on the token page: the chart pool's latest priced close, × circulating supply, and its real balances. | | `progress`, `graduatedAt`, `latestTradeAt` | number \| null | A bonding curve's share of its sellable supply sold (0 to 1), when the curve completed, and the token's newest swap. | | `creatorNetShare` | number \| null | What the creator bought minus sold through swaps of the token, as a share of total supply. Transfers do not count. | | `sniperCount`, `sniperNetShare` | number \| null | Wallets (the creator excluded) that bought in the launch block or the 4 after it (slots on Solana), and their bought minus sold as a share of supply. | | `windows` | { 5m, 1h, 6h, 24h } | As on the token page. | **Example** (captured 2026-09-28 18:03 UTC on a test instance, trimmed to 1 token) ```bash curl -s -H 'Authorization: Bearer ' 'https://app.aurelia.build/api/chains/evm:56/pulse?view=new&limit=1' ``` ```json { "chain": "evm:56", "view": "new", "total": 2, "updatedAt": 1790618579441, "tokens": [ { "address": "0x5eed00000000000000000000000000000000c444", "symbol": "NEWT3", "name": "Newt Three", "logo": "http://localhost:7170/logos/evm:56/0x5eed00000000000000000000000000000000c444?v=cccccccccccc", "launchpad": "fourmeme", "pool": "0xed80c90b543397140863599d535a4427ed378a30e094a01a025bea52acb9fcc7", "createdAt": 1790548061920, "creator": "0x0f325bd174c70440fa62c31dce9f69d54baf717e", "socials": { "website": null, "twitter": null, "telegram": null, "discord": null, "others": {} }, "priceUsd": 0.000015000000000000002, "marketCapUsd": 15000.000000000002, "liquidityUsd": null, "progress": 0.18, "graduatedAt": null, "latestTradeAt": 1790548064920, "creatorNetShare": 0, "sniperCount": 2, "sniperNetShare": 0.022, "windows": { "5m": { "priceChange": 0, "volumeUsd": 0, "buyVolumeUsd": 0, "sellVolumeUsd": 0, "trades": 0, "buys": 0, "sells": 0, "traders": 0, "buyers": 0, "sellers": 0, "feesPaidUsd": null }, "1h": { "priceChange": 0, "volumeUsd": 0, "buyVolumeUsd": 0, "sellVolumeUsd": 0, "trades": 0, "buys": 0, "sells": 0, "traders": 0, "buyers": 0, "sellers": 0, "feesPaidUsd": null }, "6h": { "priceChange": 0, "volumeUsd": 0, "buyVolumeUsd": 0, "sellVolumeUsd": 0, "trades": 0, "buys": 0, "sells": 0, "traders": 0, "buyers": 0, "sellers": 0, "feesPaidUsd": null }, "24h": { "priceChange": null, "volumeUsd": 330, "buyVolumeUsd": 330, "sellVolumeUsd": 0, "trades": 2, "buys": 2, "sells": 0, "traders": 2, "buyers": 2, "sellers": 0, "feesPaidUsd": null } } } ] } ``` **Notes** - A snapshot: each view's board is rebuilt at most every second, and a request answers at once from the latest one. The first request for a view after a quiet spell of over 5 minutes waits while its board is built. - `new` holds the newest 1,000 launches of the last 24 h that have not graduated; `graduating` the 500 open curves with the highest `progress` among those that traded in the last hour; `graduated` the newest 500 graduations of the last 24 h. - A `null` value never matches a range. `400` for an unknown parameter, a malformed range, an unknown view or a malformed launchpad id; the error names the parameter. ## Live data: Connecting Trades, candles, token and pool pages, holders, wallets and Pulse lists, pushed over one WebSocket as blocks are indexed. [The socket](https://app.aurelia.build/docs/live) covers the protocol, metering and caps; each chapter after it is one channel. ### The socket Live data comes over one WebSocket, `/live`. Open it once, then subscribe to as many things as you follow: each subscription has an id you choose, and every message for it carries that id. What a subscription follows is fixed by its subscribe message; to follow something else, unsubscribe, or subscribe again with the same id (it replaces the old one). URL: ``` wss://app.aurelia.build/api/live[?apiKey= | ?jwt=] ws://localhost:7070/live the API directly, locally wss:///live in production ``` #### Messages | Direction | Message | Meaning | |---|---|---| | You send | `{"op":"subscribe","id","channel","chain",…parameters}` | Start a subscription. `id` is 1–64 characters; parameters are strings or numbers. | | You send | `{"op":"unsubscribe","id"}` | End it; answered with `{"type":"unsubscribed","id"}`. | | You receive | `{"type":"subscribed","id","channel","chain",…}` | The subscription holds; its parameters, normalized. A snapshot channel's first message follows at once. | | You receive | `{"id","type":"","data"}` | The subscription's messages: `trades`, `candle`, `token`, `pool`, `tick`, `holders`, `balances`, `positions`, `position`, `pulse`. | | You receive | `{"id","type":"error","status","error"}` | Ends that subscription only; the socket stays open. `id` is null for a message that named none. | | You receive | `{"type":"resubscribed","chain"}` | The server's feed came back after an outage: what was indexed meanwhile was not sent. Snapshot channels send their snapshot again; for trades and candles, read the recent ones over REST. | #### Channels | Channel | Parameters besides chain | Messages | Read | |---|---|---|---| | `trades` | Exactly one of `token`, `pool`, `wallet` | `trades`: the scope's rows of each indexed batch, newest first, as [`GET /trades`](https://app.aurelia.build/docs/trades) | [Trades](https://app.aurelia.build/docs/live-trades) | | `candles` | `pool` or `token`; `res` (`1m`), `currency` (`usd`) | `candle`: the bar in progress, then each bar's final values | [Candles](https://app.aurelia.build/docs/live-candles) | | `token` | `token` | `token`: the [token](https://app.aurelia.build/docs/token) body; `tick`: the price between bodies | [Token and pool pages](https://app.aurelia.build/docs/live-token) | | `pool` | `pool` | `pool`: the [pool](https://app.aurelia.build/docs/pool) body; `tick` | [Token and pool pages](https://app.aurelia.build/docs/live-token) | | `holders` | `token` | `holders`: the [holders](https://app.aurelia.build/docs/token-holders) body, its 100 largest | [Holders](https://app.aurelia.build/docs/live-holders) | | `balances` | `wallet` | `balances`: the [portfolio](https://app.aurelia.build/docs/wallet-portfolio) body | [Balances and positions](https://app.aurelia.build/docs/live-wallets) | | `positions` | `wallet`, optionally `token` | `positions` (200 most recent), or `position` (one token) | [Balances and positions](https://app.aurelia.build/docs/live-wallets) | | `pulse` | `view` and the filters of [Pulse](https://app.aurelia.build/docs/chain-pulse) | `pulse`: the list's order, and the rows that changed | [Pulse lists](https://app.aurelia.build/docs/live-pulse) | #### Metering and caps - Credentials go in the URL (`apiKey=` or `jwt=`), or the console's session cookie: browsers cannot set headers on a WebSocket. Opening the socket costs one credit. - Each subscribe to a known channel with a chain and known parameters costs one credit, and is refused like a request: `429 rate_limited` (with `retryAfter`: subscribe again after it), `402 insufficient_balance`, `401 unauthorized` (then the socket closes with `4401`). Unsubscribing is free. - Each data message a subscription receives costs 0.01 credits, so 100 messages cost 1 credit, charged every second; `subscribed`, `unsubscribed`, `resubscribed`, `retract` and errors are free. When your credits run out your sockets close with `4402`: add credit, then connect again. Your account page shows messages per channel and per API key, with the data sent and the time your sockets were open. - A socket holds at most its plan's subscriptions, and of them its plan's `balances` subscriptions: past them a subscribe is refused with `429 too_many_subscriptions` or `429 too_many_balances`. Open sockets per account are capped too. The numbers are in [`GET /plans`](https://app.aurelia.build/docs/plans), `live`: a free account has 5 sockets with 25 subscriptions (5 `balances`) each. - What a channel computes is computed once per subject for every socket that follows it, so subscribing to a busy token costs the server no more than a quiet one. #### Errors and closing - An error message ends one subscription. Its `status` is the one its REST route would answer: `400` for a bad channel or parameter, `404` for an unknown token or pool, `422` or `502` as the route has them, `503` when it cannot be served now (retry with backoff). A 4xx means fix the subscription before asking again. - The socket itself closes only for access: `4401` (a bad or revoked key or JWT: stop), `4402` (out of credit: add credit, then reconnect), `4429` (burst, or too many open sockets: back off; `retryAfter` says how long when known). The reason is JSON, such as `{"error":"unauthorized"}`. A client too slow to take its messages is closed rather than skipped: reconnect and subscribe again. - Nothing is replayed after a reconnect: subscribe again, and read over REST what the page shows. Captured on a test instance: an unknown channel, and an unsubscribe: ```json {"op":"subscribe","id":"z","channel":"prices","chain":"evm:56"} {"id":"z","type":"error","status":400,"error":"channel must be one of trades, candles, token, pool, holders, balances, positions, pulse"} {"op":"unsubscribe","id":"t1"} {"type":"unsubscribed","id":"t1"} ``` #### A client One socket per page or process: subscribe on open, route each message by its `id`, take a REST snapshot on `subscribed` and `resubscribed`, and on a drop reconnect with backoff and subscribe everything again. Live clients, Browser: ```javascript // Live trades of one token in a web page: one socket, a trades subscription, a REST snapshot // on every (re)subscription, and reconnection. Same origin as the Aurelia console, whose /api // forwards to the API: your signed-in session is the credential. const API = "/api"; const CHAIN = "solana"; const TOKEN = "So11111111111111111111111111111111111111112"; // WSOL const list = document.body.appendChild(document.createElement("ol")); let trades = []; // newest first const key = (t) => `${t.tx}:${t.pool}:${t.eventIndex}`; const newest = (a, b) => b.time - a.time || b.block - a.block || b.txIndex - a.txIndex || b.eventIndex - a.eventIndex; function merge(batch) { const byKey = new Map(trades.map((t) => [key(t), t])); for (const t of batch) byKey.set(key(t), t); trades = [...byKey.values()].sort(newest).slice(0, 200); list.replaceChildren(...trades.slice(0, 50).map((t) => { const li = document.createElement("li"); const usd = t.priceUsd === null ? "unpriced" : `$${t.priceUsd.toPrecision(4)}`; li.textContent = `${new Date(t.time).toLocaleTimeString()} ${t.side} ${t.amount} at ${usd} ${t.labels.join(" ")}`; return li; })); } // Swaps indexed while this subscription was not live were not sent: fetch them. async function snapshot() { const res = await fetch(`${API}/trades?chain=${CHAIN}&token=${TOKEN}&limit=50`); if (res.ok) merge((await res.json()).trades); } let delay = 1000; function connect() { const url = new URL(`${API}/live`, location.href); url.protocol = url.protocol === "https:" ? "wss:" : "ws:"; const ws = new WebSocket(url); ws.onopen = () => ws.send(JSON.stringify({ op: "subscribe", id: "sol", channel: "trades", chain: CHAIN, token: TOKEN })); ws.onmessage = (e) => { const msg = JSON.parse(e.data); if (msg.type === "subscribed") delay = 1000; if (msg.type === "subscribed" || msg.type === "resubscribed") snapshot(); else if (msg.type === "trades") merge(msg.data); else if (msg.type === "error") console.error(msg.status, msg.error); // that subscription ended }; ws.onclose = (e) => { if (e.code === 4401 || e.code === 4402) return console.error("refused:", e.reason); // fix the key or add credit: do not retry let wait = delay; if (e.code === 4429) { try { wait = Math.max(wait, JSON.parse(e.reason).retryAfter * 1000 || 60_000); } catch {} } setTimeout(connect, wait); delay = Math.min(delay * 2, 30_000); }; } connect(); ``` Live clients, Bun / Node: ```javascript // live.mjs: `bun live.mjs` or `node live.mjs` (Node 22+ has fetch and WebSocket built in). // Prints a pool's swaps as they are indexed, and reconnects when the connection drops. const API = process.env.AURELIA_API ?? "https://app.aurelia.build/api"; const KEY = process.env.AURELIA_KEY; // your API key, from your account page const CHAIN = "solana"; const POOL = "Czfq3xZZDmsdGdUyrNLtRhGc47cXcZtLG4crryfu44zE"; // SOL / USDC, Orca const seen = new Set(); function print(batch) { for (const t of [...batch].reverse()) { // batches are newest first; print oldest first const key = `${t.tx}:${t.pool}:${t.eventIndex}`; if (seen.has(key)) continue; if (seen.size > 10_000) seen.clear(); seen.add(key); const usd = t.priced ? `$${t.priceUsd.toFixed(4)}` : "unpriced"; console.log(new Date(t.time).toISOString(), t.side, t.amount, t.token.symbol, usd, t.labels.join(" ")); } } // Swaps indexed while this subscription was not live were not sent: fetch them. async function snapshot() { const res = await fetch(`${API}/trades?chain=${CHAIN}&pool=${POOL}&limit=20`, { headers: { authorization: `Bearer ${KEY}` }, }); if (res.ok) print((await res.json()).trades); else console.error("snapshot failed:", res.status, await res.text()); } let delay = 1000; function connect() { const ws = new WebSocket(`${API.replace(/^http/, "ws")}/live?apiKey=${KEY}`); ws.onopen = () => ws.send(JSON.stringify({ op: "subscribe", id: "pool", channel: "trades", chain: CHAIN, pool: POOL })); ws.onmessage = (e) => { const msg = JSON.parse(e.data); if (msg.type === "subscribed") delay = 1000; if (msg.type === "subscribed" || msg.type === "resubscribed") snapshot(); else if (msg.type === "trades") print(msg.data); else if (msg.type === "error") console.error("subscription ended:", msg.status, msg.error); }; ws.onclose = (e) => { if (e.code === 4401 || e.code === 4402) { console.error("refused:", e.reason); // bad credentials or no credit: do not retry process.exit(1); } let wait = delay; if (e.code === 4429) { try { wait = Math.max(wait, JSON.parse(e.reason).retryAfter * 1000 || 60_000); } catch {} } console.error(`closed (${e.code}), reconnecting in ${wait} ms`); setTimeout(connect, wait); delay = Math.min(delay * 2, 30_000); }; } connect(); ``` Live clients, Python: ```python # live.py: pip install requests websockets, then python live.py # Prints a pool's swaps as they are indexed, and reconnects when the connection drops. import asyncio, json, os, sys from datetime import datetime, timezone import requests import websockets API = os.environ.get("AURELIA_API", "https://app.aurelia.build/api") KEY = os.environ["AURELIA_KEY"] # your API key, from your account page CHAIN = "solana" POOL = "Czfq3xZZDmsdGdUyrNLtRhGc47cXcZtLG4crryfu44zE" # SOL / USDC, Orca seen = set() def show(batch): for t in reversed(batch): # batches are newest first; print oldest first key = (t["tx"], t["pool"], t["eventIndex"]) if key in seen: continue seen.add(key) when = datetime.fromtimestamp(t["time"] / 1000, timezone.utc).isoformat() usd = f"${t['priceUsd']:.4f}" if t["priced"] else "unpriced" print(when, t["side"], t["amount"], t["token"]["symbol"], usd, " ".join(t["labels"]), flush=True) def snapshot(): # Swaps indexed while this subscription was not live were not sent: fetch them. headers = {"Authorization": f"Bearer {KEY}"} r = requests.get(f"{API}/trades", params={"chain": CHAIN, "pool": POOL, "limit": 20}, headers=headers, timeout=10) r.raise_for_status() show(r.json()["trades"]) async def main(): url = API.replace("http", "ws", 1) + f"/live?apiKey={KEY}" subscribe = {"op": "subscribe", "id": "pool", "channel": "trades", "chain": CHAIN, "pool": POOL} delay = 1 while True: wait = delay try: async with websockets.connect(url) as ws: await ws.send(json.dumps(subscribe)) async for raw in ws: msg = json.loads(raw) if msg["type"] == "subscribed": delay = 1 if msg["type"] in ("subscribed", "resubscribed"): await asyncio.to_thread(snapshot) elif msg["type"] == "trades": show(msg["data"]) elif msg["type"] == "error": print("subscription ended:", msg["status"], msg["error"], file=sys.stderr) except (OSError, websockets.WebSocketException) as e: close = getattr(e, "rcvd", None) if close and close.code in (4401, 4402): sys.exit(f"refused: {close.reason}") # bad credentials or no credit: do not retry if close and close.code == 4429: try: wait = max(wait, json.loads(close.reason).get("retryAfter") or 60) except ValueError: wait = max(wait, 60) print(f"disconnected ({e}), reconnecting in {wait} s", file=sys.stderr) await asyncio.sleep(wait) delay = min(delay * 2, 30) asyncio.run(main()) ``` ## Live data: Tokens and prices ### Token and pool pages Subscribe to `token` with `token`, or to `pool` with `pool`. The first message after `subscribed` is the REST body ([the token](https://app.aurelia.build/docs/token), [the pool](https://app.aurelia.build/docs/pool)), and again whenever it changes. Between two bodies, `tick` messages carry what a trade changes at once. Subscribe: ```json {"op":"subscribe","id":"t","channel":"token","chain":"solana","token":"So11111111111111111111111111111111111111112"} {"op":"subscribe","id":"p","channel":"pool","chain":"solana","pool":"Czfq3xZZDmsdGdUyrNLtRhGc47cXcZtLG4crryfu44zE"} ``` | type | When | data | |---|---|---| | `token` / `pool` | Right after `subscribed`; then when it changed, rebuilt at most every 5 s after a trade of the subject, and every 30 s regardless (windows roll without trades) | The REST body | | `tick` (token) | After a trade that moves the price or the latest trade time, at most 4 a second | `price`, `latestTradeAt`, `marketCapUsd`, `fdvUsd` | | `tick` (pool) | After a swap that moves the price, at most 4 a second | `price`: `baseToken`, `quoteToken`, `usd`, `quote` | A `tick` replaces those fields of the latest body; the next body replaces it whole. The token's price follows the page's rule: the latest swap where the token was the base in its chart pool, worth at least $0.01 when priced. After `resubscribed`, a fresh body follows. Logo URLs are absolute, as over REST. This is the way to follow one token's price. For many tokens, poll [`GET /prices`](https://app.aurelia.build/docs/prices) (up to 100 a request) rather than subscribe to each. Captured on a test instance: a token, trimmed to 1 pool and the 5m and 24h windows, then a trade: ```json {"type":"subscribed","id":"k1","channel":"token","chain":"evm:56","token":"0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777"} { "id": "k1", "type": "token", "data": { "token": { "address": "0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777", "decimals": 18, "symbol": "TAX", "name": "Tax Token", "firstSeenBlock": 1000002, "totalSupply": "1000000000", "circulatingSupply": "1000000000", "supplyAt": 1790618262558, "firstSeenAt": 1790546861920, "createdAt": 1790546861920, "createdTx": "0x4517764dd3b9782c02c5a640714b20335ea2585b394946c4b0f5f44bc1a7bf12", "creator": "0x0f325bd174c70440fa62c31dce9f69d54baf717e", "logo": null, "description": null, "socials": { "website": null, "twitter": null, "telegram": null, "discord": null, "others": {} }, "uri": null, "security": { "checkedAt": 1790618614169, "flags": [ "buy_tax", "sell_tax" ], "honeypot": { "likely": false, "buyers": 8, "sellers": 7, "window": "24h", "how": "Distinct wallets that bought, and that sold, in the last 24h (the transaction's sender), leaving out the token's creator and its pools' creators where Aurelia saw them. Likely a honeypot when 10+ wallets bought and none of them sold; not one once any sold" }, "lp": { "pool": "0x702e6533e0182381606adbe3ccbb77f682ccc58b", "lockedShare": null, "burnedShare": 1, "how": "The pool is its own LP token: totalSupply() and balanceOf() of the zero and dead addresses at block 124557783. Locked LP is not counted: Aurelia has no trustworthy list of lockers" }, "top10Share": 0.30010822, "logoReuses": null, "unknown": { "logoReuses": "the token has no logo" }, "contract": { "block": 124557783, "owner": "0x0000000000000000000000000000000000000000", "renounced": true, "proxy": { "kind": "minimal", "implementation": "0x024f18294970b5c76c0691b87f138a0317156422", "admin": null, "upgradeable": false }, "functions": { "mint": { "present": false, "signatures": [], "nonOwnerCall": null }, "pause": { "present": false, "signatures": [], "nonOwnerCall": null }, "blacklist": { "present": false, "signatures": [], "nonOwnerCall": null }, "tradingToggle": { "present": false, "signatures": [], "nonOwnerCall": null } }, "how": "At block 124557783: owner() or getOwner(); the EIP-1967, OpenZeppelin and EIP-1822 proxy slots and EIP-1167 code; the known mint, pause, blacklist and trading-toggle signatures the implementation's bytecode dispatches, each present one called once through Multicall3 (eth_call from a caller that is not the owner)" }, "taxes": { "buy": 0.02, "sell": 0.02, "buys": 69, "sells": 33, "window": "24h", "how": "Median over the swaps of the last 24h in transactions with one swap of the token: what the pool's Transfer logs delivered to the recipient against what the pool sent (buys), what the pool received against what its sender sent in all (sells)" } } }, "holders": { "count": 13, "top10Share": 0.30015722 }, "creator": { "address": "0x0f325bd174c70440fa62c31dce9f69d54baf717e", "labels": [ "creator" ], "tokensCreated": 3, "graduated": 1 }, "stats24h": { "volumeUsd": 1758, "buyVolumeUsd": 1560, "sellVolumeUsd": 198, "trades": 105, "buys": 72, "sells": 33, "traders": 8, "buyers": 8, "sellers": 7, "feesPaidUsd": null }, "chartPool": "0x702e6533e0182381606adbe3ccbb77f682ccc58b", "price": { "usd": 0.006, "quote": 0.00001, "quoteToken": { "address": "0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c", "symbol": "WBNB" }, "pool": "0x702e6533e0182381606adbe3ccbb77f682ccc58b", "time": 1790618578665 }, "windows": { "5m": { "priceChange": 0, "volumeUsd": 600, "buyVolumeUsd": 600, "sellVolumeUsd": 0, "trades": 2, "buys": 2, "sells": 0, "traders": 1, "buyers": 1, "sellers": 0, "feesPaidUsd": null }, "24h": { "priceChange": null, "volumeUsd": 1758, "buyVolumeUsd": 1560, "sellVolumeUsd": 198, "trades": 105, "buys": 72, "sells": 33, "traders": 8, "buyers": 8, "sellers": 7, "feesPaidUsd": null } }, "poolCount": 2, "liquidityUsd": 4204200, "marketCapUsd": 6000000, "fdvUsd": 6000000, "pools": [ { "address": "0x702e6533e0182381606adbe3ccbb77f682ccc58b", "protocol": "pancakeswap-v2", "token0": "0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777", "token1": "0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c", "volume24hUsd": 1368, "reserve": "700000000", "based": true, "counter": { "address": "0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c", "symbol": "WBNB" }, "trades24h": 102, "priceUsd": 0.006, "counterReserve": "7", "liquidityUsd": 4204200 } ], "feesPaidUsd": null, "ath": { "usd": 0.006122448979591836, "at": 1790549820000 }, "atl": { "usd": 0.000012, "at": 1790546820000 }, "bonding": { "pool": "0xf39f85a07237050d069187679c621b9df1df8585b01d625ae22bc7cb99430013", "progress": 1, "complete": true }, "latestTradeAt": 1790618578665 } } {"id":"k1","type":"tick","data":{"price":{"usd":0.006,"quote":0.00001,"quoteToken":{"address":"0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c","symbol":"WBNB"},"pool":"0x702e6533e0182381606adbe3ccbb77f682ccc58b","time":1790618628209},"latestTradeAt":1790618628209,"marketCapUsd":6000000,"fdvUsd":6000000}} ``` Captured on a test instance: the pool, then the swap's tick: ```json {"type":"subscribed","id":"p1","channel":"pool","chain":"evm:56","pool":"0x702e6533e0182381606adbe3ccbb77f682ccc58b"} { "id": "p1", "type": "pool", "data": { "pool": { "chain": "evm:56", "address": "0x702e6533e0182381606adbe3ccbb77f682ccc58b", "protocol": "pancakeswap-v2", "token0": "0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777", "token1": "0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c", "factory": null, "createdBlock": 1000008, "createdTx": null, "creator": "0x0f325bd174c70440fa62c31dce9f69d54baf717e", "extra": {}, "createdAt": 1790549261920 }, "token0": { "address": "0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777", "decimals": 18, "symbol": "TAX", "name": "Tax Token", "firstSeenBlock": 1000002, "logo": null }, "token1": { "address": "0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c", "decimals": 18, "symbol": "WBNB", "name": "Wrapped BNB", "firstSeenBlock": 1000001, "logo": null }, "state": { "atBlock": 1000008, "atIndex": 0, "reserve0": "700000000", "reserve1": "7" }, "price": { "baseToken": "0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777", "quoteToken": "0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c", "usd": 0.006, "quote": 0.00001 }, "liquidityUsd": 4204200, "windows": { "5m": { "volumeUsd": 600, "buyVolumeUsd": 600, "sellVolumeUsd": 0, "trades": 2, "buys": 2, "sells": 0, "traders": 1, "priceChange": 0 }, "1h": { "volumeUsd": 618, "buyVolumeUsd": 612, "sellVolumeUsd": 6, "trades": 5, "buys": 4, "sells": 1, "traders": 4, "priceChange": -0.019999999999999907 }, "6h": { "volumeUsd": 822, "buyVolumeUsd": 762, "sellVolumeUsd": 60, "trades": 31, "buys": 21, "sells": 10, "traders": 8, "priceChange": 0 }, "24h": { "volumeUsd": 1368, "buyVolumeUsd": 1170, "sellVolumeUsd": 198, "trades": 102, "buys": 69, "sells": 33, "traders": 8, "priceChange": null } } } } {"id":"p1","type":"tick","data":{"price":{"baseToken":"0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777","quoteToken":"0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c","usd":0.006,"quote":0.00001}}} ``` Errors: `400` (`token is required`, `pool is required`), `404` (`unknown token`, `unknown pool`), `503` when the body cannot be built. Each ends the subscription only. ## Live data: Charts ### Candles Subscribe to `candles` with `pool`, or `token` (charted in the chart pool [its page](https://app.aurelia.build/docs/token) names when you subscribe), `res` (`1m` by default: `1m`, `5m`, `15m`, `1h`, `4h`, `1d`) and `currency` (`usd` by default, or `quote`). The server builds the bars from each indexed swap by the rules of [the candles](https://app.aurelia.build/docs/pool-candles), so a chart needs no folding of its own. Subscribe: ```json {"op":"subscribe","id":"c","channel":"candles","chain":"solana","token":"So11111111111111111111111111111111111111112","res":"1m","currency":"usd"} ``` | type | When | data | |---|---|---| | `subscribed` | Once | Names the `pool`, with `res`, `currency`, and `token` when you gave one | | `candle` | Right after `subscribed` when the pool has a bar; then whenever the bar in progress changes, at most 4 a second | The bar, `closed: false` | | `candle` | When a trade opens the next bar: the previous bar's final values, just before the new bar | The bar, `closed: true` | A bar is `t` (its start, unix seconds), `o`, `h`, `l`, `c`, `v` (USD volume) and `n` (trades: the priced ones in `usd`, all of them in `quote`). Keep bars by `t`: a message replaces the bar with its `t`, or adds it. In `usd`, only a priced swap opens a bar, and a swap worth under $0.01 never counts. 1. Subscribe and wait for `subscribed`, which names the pool. 2. Load the bars so far with [`GET /pools/:chain/:address/candles`](https://app.aurelia.build/docs/pool-candles) for that pool, with the same `res` and `currency`. 3. Apply each `candle` by its `t`, those that arrived while the history loaded included. The REST page holding now can be a minute old; the channel's bars are not. 4. On `resubscribed`, load the bars again: a bar that closed during the outage may not be sent. Captured on a test instance: the bar in progress, then its final values when the next minute's first trade lands: ```json {"type":"subscribed","id":"c1","channel":"candles","chain":"evm:56","token":"0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777","pool":"0x702e6533e0182381606adbe3ccbb77f682ccc58b","res":"1m","currency":"usd"} {"id":"c1","type":"candle","data":{"t":1790618520,"o":0.006,"h":0.006,"l":0.006,"c":0.006,"v":600,"n":2,"closed":false}} {"id":"c1","type":"candle","data":{"t":1790618520,"o":0.006,"h":0.006,"l":0.006,"c":0.006,"v":600,"n":2,"closed":true}} ``` Captured on a test instance: a refused subscription: ```json {"op":"subscribe","id":"x1","channel":"candles","chain":"evm:56","pool":"0x702e…c58b","res":"2m"} {"id":"x1","type":"error","status":400,"error":"res must be one of 1m, 5m, 15m, 1h, 4h, 1d"} ``` Errors: `400` (a bad `res` or `currency`, or not exactly one of `pool` and `token`), `404` (`unknown pool`, `unknown token`, `no pool charts this token`), `503 candles unavailable`. ## Live data: Trades ### Trades Subscribe to `trades` with exactly one of `token`, `pool` or `wallet`. Each `trades` message carries the scope's trades of one indexed batch of blocks, newest first, as the rows of [`GET /trades`](https://app.aurelia.build/docs/trades) for the same scope: from the token's side with `token`, else from each swap's base; with the trader's labels. Subscribe: ```json {"op":"subscribe","id":"sol","channel":"trades","chain":"solana","token":"So11111111111111111111111111111111111111112"} ``` Captured on a test instance: a token's feed, trimmed to 1 trade: ```json {"type":"subscribed","id":"t1","channel":"trades","chain":"evm:56","token":"0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777"} { "id": "t1", "type": "trades", "data": [ { "time": 1790618628209, "block": 1000751, "txIndex": 0, "eventIndex": 4, "tx": "0xa8c3338c4c9514edbed29ea1fce567486d9ae1bf1062b6996f15d1149cbd035f", "pool": "0x702e6533e0182381606adbe3ccbb77f682ccc58b", "protocol": "pancakeswap-v2", "side": "buy", "token": { "address": "0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777", "symbol": "TAX", "name": "Tax Token", "decimals": 18 }, "amount": "50000", "counter": { "address": "0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c", "symbol": "WBNB", "name": "Wrapped BNB", "decimals": 18 }, "counterAmount": "0.5", "priceUsd": 0.006, "amountUsd": 300, "priced": true, "trader": "0x3304e22ddaa22bcdc5fca2269b418046ae7b566a", "labels": [ "sniper", "fresh" ] } ] } ``` #### Ordering and gaps - Within a message trades are newest first, and messages arrive in the order their blocks were indexed. - Nothing is replayed. Swaps indexed before `subscribed`, or during an outage (`resubscribed`), were not sent: on both, fetch the recent trades with [`GET /trades`](https://app.aurelia.build/docs/trades) and merge them, keyed by `tx`, `pool` and `eventIndex`. - A trade is sent as soon as it is known, and once. On Solana that is when it executes (`processed`), on Base when its flashblock is built, on other chains when its block arrives. Until its block is sealed it carries `preconfirmed: true`, and REST returns it about half a second later on Solana. - A `retract` message names trades already sent that did not happen: `{"type":"retract","data":[{"block","tx","pool","eventIndex"}]}`. The chain abandoned the slot or reorganised, and REST will not return them. Drop those rows, matching `block` as well: the same transaction may land again in another block, and arrives then as a new trade. #### Clients All three take a REST snapshot on every (re)subscription, merge without duplicates, and reconnect with backoff. Live clients, Browser: ```javascript // Live trades of one token in a web page: one socket, a trades subscription, a REST snapshot // on every (re)subscription, and reconnection. Same origin as the Aurelia console, whose /api // forwards to the API: your signed-in session is the credential. const API = "/api"; const CHAIN = "solana"; const TOKEN = "So11111111111111111111111111111111111111112"; // WSOL const list = document.body.appendChild(document.createElement("ol")); let trades = []; // newest first const key = (t) => `${t.tx}:${t.pool}:${t.eventIndex}`; const newest = (a, b) => b.time - a.time || b.block - a.block || b.txIndex - a.txIndex || b.eventIndex - a.eventIndex; function merge(batch) { const byKey = new Map(trades.map((t) => [key(t), t])); for (const t of batch) byKey.set(key(t), t); trades = [...byKey.values()].sort(newest).slice(0, 200); list.replaceChildren(...trades.slice(0, 50).map((t) => { const li = document.createElement("li"); const usd = t.priceUsd === null ? "unpriced" : `$${t.priceUsd.toPrecision(4)}`; li.textContent = `${new Date(t.time).toLocaleTimeString()} ${t.side} ${t.amount} at ${usd} ${t.labels.join(" ")}`; return li; })); } // Swaps indexed while this subscription was not live were not sent: fetch them. async function snapshot() { const res = await fetch(`${API}/trades?chain=${CHAIN}&token=${TOKEN}&limit=50`); if (res.ok) merge((await res.json()).trades); } let delay = 1000; function connect() { const url = new URL(`${API}/live`, location.href); url.protocol = url.protocol === "https:" ? "wss:" : "ws:"; const ws = new WebSocket(url); ws.onopen = () => ws.send(JSON.stringify({ op: "subscribe", id: "sol", channel: "trades", chain: CHAIN, token: TOKEN })); ws.onmessage = (e) => { const msg = JSON.parse(e.data); if (msg.type === "subscribed") delay = 1000; if (msg.type === "subscribed" || msg.type === "resubscribed") snapshot(); else if (msg.type === "trades") merge(msg.data); else if (msg.type === "error") console.error(msg.status, msg.error); // that subscription ended }; ws.onclose = (e) => { if (e.code === 4401 || e.code === 4402) return console.error("refused:", e.reason); // fix the key or add credit: do not retry let wait = delay; if (e.code === 4429) { try { wait = Math.max(wait, JSON.parse(e.reason).retryAfter * 1000 || 60_000); } catch {} } setTimeout(connect, wait); delay = Math.min(delay * 2, 30_000); }; } connect(); ``` Live clients, Bun / Node: ```javascript // live.mjs: `bun live.mjs` or `node live.mjs` (Node 22+ has fetch and WebSocket built in). // Prints a pool's swaps as they are indexed, and reconnects when the connection drops. const API = process.env.AURELIA_API ?? "https://app.aurelia.build/api"; const KEY = process.env.AURELIA_KEY; // your API key, from your account page const CHAIN = "solana"; const POOL = "Czfq3xZZDmsdGdUyrNLtRhGc47cXcZtLG4crryfu44zE"; // SOL / USDC, Orca const seen = new Set(); function print(batch) { for (const t of [...batch].reverse()) { // batches are newest first; print oldest first const key = `${t.tx}:${t.pool}:${t.eventIndex}`; if (seen.has(key)) continue; if (seen.size > 10_000) seen.clear(); seen.add(key); const usd = t.priced ? `$${t.priceUsd.toFixed(4)}` : "unpriced"; console.log(new Date(t.time).toISOString(), t.side, t.amount, t.token.symbol, usd, t.labels.join(" ")); } } // Swaps indexed while this subscription was not live were not sent: fetch them. async function snapshot() { const res = await fetch(`${API}/trades?chain=${CHAIN}&pool=${POOL}&limit=20`, { headers: { authorization: `Bearer ${KEY}` }, }); if (res.ok) print((await res.json()).trades); else console.error("snapshot failed:", res.status, await res.text()); } let delay = 1000; function connect() { const ws = new WebSocket(`${API.replace(/^http/, "ws")}/live?apiKey=${KEY}`); ws.onopen = () => ws.send(JSON.stringify({ op: "subscribe", id: "pool", channel: "trades", chain: CHAIN, pool: POOL })); ws.onmessage = (e) => { const msg = JSON.parse(e.data); if (msg.type === "subscribed") delay = 1000; if (msg.type === "subscribed" || msg.type === "resubscribed") snapshot(); else if (msg.type === "trades") print(msg.data); else if (msg.type === "error") console.error("subscription ended:", msg.status, msg.error); }; ws.onclose = (e) => { if (e.code === 4401 || e.code === 4402) { console.error("refused:", e.reason); // bad credentials or no credit: do not retry process.exit(1); } let wait = delay; if (e.code === 4429) { try { wait = Math.max(wait, JSON.parse(e.reason).retryAfter * 1000 || 60_000); } catch {} } console.error(`closed (${e.code}), reconnecting in ${wait} ms`); setTimeout(connect, wait); delay = Math.min(delay * 2, 30_000); }; } connect(); ``` Live clients, Python: ```python # live.py: pip install requests websockets, then python live.py # Prints a pool's swaps as they are indexed, and reconnects when the connection drops. import asyncio, json, os, sys from datetime import datetime, timezone import requests import websockets API = os.environ.get("AURELIA_API", "https://app.aurelia.build/api") KEY = os.environ["AURELIA_KEY"] # your API key, from your account page CHAIN = "solana" POOL = "Czfq3xZZDmsdGdUyrNLtRhGc47cXcZtLG4crryfu44zE" # SOL / USDC, Orca seen = set() def show(batch): for t in reversed(batch): # batches are newest first; print oldest first key = (t["tx"], t["pool"], t["eventIndex"]) if key in seen: continue seen.add(key) when = datetime.fromtimestamp(t["time"] / 1000, timezone.utc).isoformat() usd = f"${t['priceUsd']:.4f}" if t["priced"] else "unpriced" print(when, t["side"], t["amount"], t["token"]["symbol"], usd, " ".join(t["labels"]), flush=True) def snapshot(): # Swaps indexed while this subscription was not live were not sent: fetch them. headers = {"Authorization": f"Bearer {KEY}"} r = requests.get(f"{API}/trades", params={"chain": CHAIN, "pool": POOL, "limit": 20}, headers=headers, timeout=10) r.raise_for_status() show(r.json()["trades"]) async def main(): url = API.replace("http", "ws", 1) + f"/live?apiKey={KEY}" subscribe = {"op": "subscribe", "id": "pool", "channel": "trades", "chain": CHAIN, "pool": POOL} delay = 1 while True: wait = delay try: async with websockets.connect(url) as ws: await ws.send(json.dumps(subscribe)) async for raw in ws: msg = json.loads(raw) if msg["type"] == "subscribed": delay = 1 if msg["type"] in ("subscribed", "resubscribed"): await asyncio.to_thread(snapshot) elif msg["type"] == "trades": show(msg["data"]) elif msg["type"] == "error": print("subscription ended:", msg["status"], msg["error"], file=sys.stderr) except (OSError, websockets.WebSocketException) as e: close = getattr(e, "rcvd", None) if close and close.code in (4401, 4402): sys.exit(f"refused: {close.reason}") # bad credentials or no credit: do not retry if close and close.code == 4429: try: wait = max(wait, json.loads(close.reason).get("retryAfter") or 60) except ValueError: wait = max(wait, 60) print(f"disconnected ({e}), reconnecting in {wait} s", file=sys.stderr) await asyncio.sleep(wait) delay = min(delay * 2, 30) asyncio.run(main()) ``` ## Live data: Holders ### Holders Subscribe to `holders` with `token`. The first message after `subscribed` is the [token holders](https://app.aurelia.build/docs/token-holders) answer for the token's 100 largest holders, and again each time it changes. Each message replaces the one before: there are no deltas to merge. Subscribe: ```json {"op":"subscribe","id":"h","channel":"holders","chain":"evm:56","token":"0x0e09fabb73bd3ade0a17ecc321fd13a19e81ce82"} ``` | Chain | Rebuilt | |---|---| | BSC | After each indexed block whose transfers move the token, at most once a second. | | Solana | After each indexed swap of the token, at most every 5 s: each rebuild reads the chain (at most once a minute). | | Both | Every 30 s regardless: prices and PnL move, and Solana transfers outside DEX transactions are not indexed. | Captured on a test instance, trimmed to 2 holders: ```json {"type":"subscribed","id":"h1","channel":"holders","chain":"evm:56","token":"0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777"} { "id": "h1", "type": "holders", "data": { "chain": "evm:56", "token": { "address": "0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777", "symbol": "TAX", "name": "Tax Token", "decimals": 18 }, "source": "indexed-transfers", "complete": true, "coverage": "Every holder: the index holds the token's whole history, and its largest balances match the chain.", "indexedSince": 1790546261920, "block": 1000750, "supply": "1000000000", "priceUsd": 0.006, "count": 13, "top10Share": 0.30015722, "holders": [ { "address": "0x702e6533e0182381606adbe3ccbb77f682ccc58b", "balance": "699837340", "share": 0.69983734, "valueUsd": 4199024.04, "tags": [ "pool" ], "labels": [], "position": null }, { "address": "0x0f325bd174c70440fa62c31dce9f69d54baf717e", "balance": "200000000", "share": 0.2, "valueUsd": 1200000, "tags": [ "creator" ], "labels": [ "creator" ], "position": null } ], "next": null } } ``` Errors, as the route answers them: `404`, `422`, `502`, `503`. A refusal (4xx) ends the subscription; a failure (5xx) on an open one is dropped, and the last `holders` stands until the next. ## Live data: Wallets ### Balances and positions Subscribe to `balances` with `wallet`: the [wallet portfolio](https://app.aurelia.build/docs/wallet-portfolio) answer (its first 100 holdings), and again each time it changes. Subscribe to `positions` with `wallet`: the [wallet positions](https://app.aurelia.build/docs/wallet-positions) answer for its 200 most recently traded tokens; with `token` too (an address, or `native`), `position` messages carry that one position. Each message replaces the one before; a new `block` or `balancesAt` alone is no change, and is not sent. Subscribe: ```json {"op":"subscribe","id":"b","channel":"balances","chain":"solana","wallet":"Fm1Nzv3EcgGMW2MFHHGKjdVDbvHsMMtoRdoBDmUmsFFE"} {"op":"subscribe","id":"q","channel":"positions","chain":"solana","wallet":"Fm1Nzv3EcgGMW2MFHHGKjdVDbvHsMMtoRdoBDmUmsFFE","token":"native"} ``` | Trigger | Why | |---|---| | An indexed block moved the wallet | A swap it made, or an indexed transfer from or to it. Read at once, before other wallets. | | Every 30 s | Prices move, and some changes are not indexed: gas and BNB moved by contracts on BSC, anything outside DEX transactions on Solana. | | The server's feed is back after an outage | Everything followed is read again. | Balances come from the chain, so their reads are limited: a wallet's at most once every 5 s however many follow it, and at most 5 wallets a second per chain, moved wallets first. A socket holds few `balances` subscriptions (`live.balances` in [`GET /plans`](https://app.aurelia.build/docs/plans)). A read that fails is tried again later, doubling the wait, and the last message stands meanwhile. Positions come from the index: a topic is read at most once a second. Captured on a test instance: balances, trimmed to 2 holdings: ```json {"type":"subscribed","id":"b1","channel":"balances","chain":"evm:56","wallet":"0x3304e22ddaa22bcdc5fca2269b418046ae7b566a"} { "id": "b1", "type": "balances", "data": { "chain": "evm:56", "wallet": "0x3304e22ddaa22bcdc5fca2269b418046ae7b566a", "indexedSince": 1790546261920, "labels": [ "sniper", "fresh" ], "fundedBy": { "address": "0x8894e0a0c962cb723c1976a4421c95949be2d4e3", "amount": "0.25", "time": 1790616461920, "tx": "0x124aa51a8b73a7c373249b80ec4beb9fa4e80b4aa78a8bc73a64c781b3f2925d", "labels": [] }, "fundedByReason": null, "block": 1000748, "balancesAt": 1790618266812, "totalValueUsd": 214134, "pnl": { "realizedUsd": 59.5089770354906, "unrealizedUsd": 6.776263578034403e-14, "totalUsd": 59.50897703549067 }, "holdings": [ { "kind": "token", "token": { "address": "0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777", "symbol": "TAX", "name": "Tax Token", "decimals": 18 }, "balance": "21500000", "priceUsd": 0.006, "valueUsd": 129000, "allocation": 0.6024265179747261, "thin": false, "position": { "quote": false, "trades": 22, "buys": 12, "sells": 10, "unpricedTrades": 0, "bought": "20118000", "sold": "9800", "open": "20108200", "boughtUsd": 1008, "soldUsd": 60, "avgBuyPriceUsd": 0.00005010438413361169, "avgSellPriceUsd": 0.006122448979591836, "priceUsd": 0.006, "priceAt": 1790618400000, "realizedPnlUsd": 59.5089770354906, "unrealizedPnlUsd": null, "totalPnlUsd": 59.5089770354906, "firstTradeAt": 1790546864920, "lastTradeAt": 1790618578665 } }, { "kind": "lp", "protocol": "pancakeswap-v2", "pool": "0x702e6533e0182381606adbe3ccbb77f682ccc58b", "position": null, "lpToken": { "address": "0x702e6533e0182381606adbe3ccbb77f682ccc58b", "balance": "2000000000000000000000", "supply": "100000000000000000000000", "share": 0.02 }, "tokens": [ { "token": { "address": "0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777", "symbol": "TAX", "name": "Tax Token", "decimals": 18 }, "amount": "14000000", "fees": null, "raw": { "amount": "14000000000000000000000000", "fees": null }, "priceUsd": 0.006, "valueUsd": 84000 }, { "token": { "address": "0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c", "symbol": "WBNB", "name": "Wrapped BNB", "decimals": 18 }, "amount": "0.14", "fees": null, "raw": { "amount": "140000000000000000", "fees": null }, "priceUsd": 600, "valueUsd": 84.00000000000001 } ], "range": null, "valueUsd": 84084, "feesUsd": null, "allocation": 0.39267001036734006, "thin": false } ], "unpriced": 0, "count": 6, "liquidity": { "protocols": [ "pancakeswap-v2", "pancakeswap-v3", "uniswap-v2", "uniswap-v3" ], "incomplete": [], "notCovered": [ { "protocol": "pancakeswap-infinity-cl", "reason": "its positions are not enumerable per owner" } ], "error": null } } } ``` Captured on a test instance: one position, then the wallet buys more: ```json {"type":"subscribed","id":"q1","channel":"positions","chain":"evm:56","wallet":"0x3304e22ddaa22bcdc5fca2269b418046ae7b566a","token":"0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777"} {"id":"q1","type":"position","data":{"chain":"evm:56","wallet":"0x3304e22ddaa22bcdc5fca2269b418046ae7b566a","labels":["sniper","fresh"],"indexedSince":1790546261920,"position":{"token":{"address":"0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777","symbol":"TAX","name":"Tax Token","decimals":18},"quote":false,"trades":22,"buys":12,"sells":10,"unpricedTrades":0,"bought":"20118000","sold":"9800","open":"20108200","boughtUsd":1008,"soldUsd":60,"avgBuyPriceUsd":0.00005010438413361169,"avgSellPriceUsd":0.006122448979591836,"priceUsd":0.006,"priceAt":1790618400000,"realizedPnlUsd":59.5089770354906,"unrealizedPnlUsd":null,"totalPnlUsd":59.5089770354906,"firstTradeAt":1790546864920,"lastTradeAt":1790618578665}}} {"id":"q1","type":"position","data":{"chain":"evm:56","wallet":"0x3304e22ddaa22bcdc5fca2269b418046ae7b566a","labels":["sniper","fresh"],"indexedSince":1790546261920,"position":{"token":{"address":"0xa9fcda9bd321adeb7cbbbd30ef2dd0ed1d657777","symbol":"TAX","name":"Tax Token","decimals":18},"quote":false,"trades":23,"buys":13,"sells":10,"unpricedTrades":0,"bought":"20168000","sold":"9800","open":"20158200","boughtUsd":1308,"soldUsd":60,"avgBuyPriceUsd":0.00006485521618405395,"avgSellPriceUsd":0.006122448979591836,"priceUsd":0.006,"priceAt":1790618400000,"realizedPnlUsd":59.36441888139627,"unrealizedPnlUsd":null,"totalPnlUsd":59.36441888139627,"firstTradeAt":1790546864920,"lastTradeAt":1790618628209}}} ``` Errors: `400` (`wallet` missing), `429 too_many_balances`, and as the routes answer them (`422`, `502`). ## Live data: Pulse ### Pulse lists Subscribe to `pulse` with `view` and any of [Pulse](https://app.aurelia.build/docs/pulse)'s filters, `sort`, `order`, `limit` and `offset`, as parameters of the message. They are checked as REST checks them: a bad one is `400` with the same error text. Subscribe: ```json {"op":"subscribe","id":"g","channel":"pulse","chain":"solana","view":"graduating","progress":"0.5..","limit":20} ``` | type | When | data | |---|---|---| | `subscribed` | Once, first | Echoes `view`, `sort`, `order`, `limit`, `offset` | | `pulse` | Right after, with the whole list; then after each rebuild that changed the list or a listed token | `view`, `total`, `updatedAt`, `order`, `tokens` | - `order` is the whole list, as addresses in order. It is authoritative: render it. - `tokens` carries only the rows that are new or changed since the previous message. The first message, and the first after `resubscribed`, carries every row. - Keep a map from address to row, apply `tokens` to it, and render `order`. A token missing from `order` has left the list: filters are checked again at every rebuild. Captured on a test instance: ```json {"type":"subscribed","id":"u1","channel":"pulse","chain":"evm:56","view":"new","sort":"createdAt","order":"desc","limit":1,"offset":0} { "id": "u1", "type": "pulse", "data": { "view": "new", "total": 2, "updatedAt": 1790618622692, "order": [ "0x5eed00000000000000000000000000000000c444" ], "tokens": [ { "address": "0x5eed00000000000000000000000000000000c444", "symbol": "NEWT3", "name": "Newt Three", "logo": "http://localhost:7170/logos/evm:56/0x5eed00000000000000000000000000000000c444?v=cccccccccccc", "launchpad": "fourmeme", "pool": "0xed80c90b543397140863599d535a4427ed378a30e094a01a025bea52acb9fcc7", "createdAt": 1790548061920, "creator": "0x0f325bd174c70440fa62c31dce9f69d54baf717e", "socials": { "website": null, "twitter": null, "telegram": null, "discord": null, "others": {} }, "priceUsd": 0.000015000000000000002, "marketCapUsd": 15000.000000000002, "liquidityUsd": null, "progress": 0.18, "graduatedAt": null, "latestTradeAt": 1790548064920, "creatorNetShare": 0, "sniperCount": 2, "sniperNetShare": 0.022, "windows": { "5m": { "priceChange": 0, "volumeUsd": 0, "buyVolumeUsd": 0, "sellVolumeUsd": 0, "trades": 0, "buys": 0, "sells": 0, "traders": 0, "buyers": 0, "sellers": 0, "feesPaidUsd": null }, "1h": { "priceChange": 0, "volumeUsd": 0, "buyVolumeUsd": 0, "sellVolumeUsd": 0, "trades": 0, "buys": 0, "sells": 0, "traders": 0, "buyers": 0, "sellers": 0, "feesPaidUsd": null }, "6h": { "priceChange": 0, "volumeUsd": 0, "buyVolumeUsd": 0, "sellVolumeUsd": 0, "trades": 0, "buys": 0, "sells": 0, "traders": 0, "buyers": 0, "sellers": 0, "feesPaidUsd": null }, "24h": { "priceChange": null, "volumeUsd": 330, "buyVolumeUsd": 330, "sellVolumeUsd": 0, "trades": 2, "buys": 2, "sells": 0, "traders": 2, "buyers": 2, "sellers": 0, "feesPaidUsd": null } } } ] } } ``` Client: ```javascript // Follow a Pulse list: keep rows by address, apply each message's tokens, render its order. // Bun, Node 22+ or a browser. Reconnects with backoff; a refused key is not retried. const subscribe = { op: "subscribe", id: "new", channel: "pulse", chain: "solana", view: "new", launchpad: "pumpfun", marketCapUsd: "5000..", limit: 20, }; const rows = new Map(); // address → token function render(order) { const lines = order.map((address) => { const t = rows.get(address); const cap = t.marketCapUsd === null ? "-" : `$${Math.round(t.marketCapUsd)}`; const progress = t.progress === null ? "" : ` ${Math.round(t.progress * 100)}%`; return `${t.symbol ?? address.slice(0, 6)} ${cap}${progress}`; }); console.log(lines.join("\n"), "\n"); } let delay = 1000; function connect() { const ws = new WebSocket("wss://app.aurelia.build/api/live"); ws.onopen = () => ws.send(JSON.stringify(subscribe)); ws.onmessage = (e) => { const msg = JSON.parse(e.data); if (msg.type === "subscribed") delay = 1000; if (msg.type === "error") return console.error(msg.status, msg.error); // fix the filters if (msg.type !== "pulse") return; for (const t of msg.data.tokens) rows.set(t.address, t); const listed = new Set(msg.data.order); for (const address of rows.keys()) if (!listed.has(address)) rows.delete(address); // left the list render(msg.data.order); }; ws.onclose = (e) => { if (e.code === 4401) return console.error("refused:", e.reason); setTimeout(connect, delay); delay = Math.min(delay * 2, 30_000); }; } connect(); ``` ## Account Keys, short-lived tokens for your users, plans and limits, and the service's own status. ### Authentication and limits 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 1. Sign in with Google on the [sign-in page](https://app.aurelia.build/#/login). 2. On [your account](https://app.aurelia.build/#/account), create a key and copy it: it is shown once. Keys start with `aur_`; a key made before that prefix starts with `mb_` and keeps working. 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: [`POST /auth/token`](https://app.aurelia.build/docs/auth-token) makes one with an API key, and `GET /auth/token` with a session signed in to this site. 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. #### Tokens for your users Building a platform, app or site on Aurelia? Keep your API key on your server and give your users short-lived tokens instead. Your server calls [`POST /auth/token`](https://app.aurelia.build/docs/auth-token) with the key and gets a JWT that lasts 15 minutes. The browser or app sends that token to the API directly, so your key is never exposed and you do not have to relay the requests yourself. 1. Your user signs in to your own app. 2. Your app asks your server for a token. Your server checks the user, calls `POST /auth/token` with your key, and passes on the answer. 3. The browser or app calls the API with `Authorization: Bearer `, or opens `/live?jwt=`. 4. Before the token expires, the browser or app asks your server for another. An open socket closes with `4401` when its token expires: reconnect with a fresh one. A token from your server, used in the browser, curl: ```bash curl -s -X POST -H 'Authorization: Bearer aur_YOUR_KEY' 'https://app.aurelia.build/api/auth/token' # {"token":"eyJhbGciOiJFZERTQSIs…","expiresIn":900} ``` A token from your server, used in the browser, Your server (JavaScript): ```javascript // Behind your own sign-in. The key stays here. export async function aureliaToken(req) { const user = await yourSignIn(req); if (!user) return new Response(null, { status: 401 }); const res = await fetch("https://app.aurelia.build/api/auth/token", { method: "POST", headers: { Authorization: `Bearer ${process.env.AURELIA_KEY}` }, }); if (!res.ok) return new Response(null, { status: 502 }); return Response.json(await res.json()); // { token, expiresIn } } ``` A token from your server, used in the browser, Your server (Python): ```python import os import requests # Behind your own sign-in. The key stays here. def aurelia_token(): r = requests.post( "https://app.aurelia.build/api/auth/token", headers={"Authorization": f"Bearer {os.environ['AURELIA_KEY']}"}, timeout=10, ) r.raise_for_status() return r.json() # {"token": "…", "expiresIn": 900} ``` A token from your server, used in the browser, The browser: ```javascript // Asks your server for a token, and for another a minute before it expires. let token, renewAt = 0; async function aureliaToken() { if (Date.now() < renewAt) return token; const got = await (await fetch("/your-server/aurelia-token")).json(); token = got.token; renewAt = Date.now() + (got.expiresIn - 60) * 1000; return token; } const res = await fetch("https://app.aurelia.build/api/tokens/solana/So11111111111111111111111111111111111111112", { headers: { Authorization: `Bearer ${await aureliaToken()}` }, }); console.log(await res.json()); const ws = new WebSocket(`wss://app.aurelia.build/api/live?jwt=${await aureliaToken()}`); ws.onopen = () => ws.send(JSON.stringify({ op: "subscribe", id: "sol", channel: "token", chain: "solana", token: "So11111111111111111111111111111111111111112" })); ws.onclose = (e) => { if (e.code === 4401) { /* the token expired: get another and reconnect */ } }; ``` | About a token | How it works | |---|---| | What it reaches | Every data route and `/live`, as the key that made it. It cannot make another token (403 `api_key_required`), and it cannot reach your account, keys or billing. | | How long it lasts | 15 minutes, always. The answer's `expiresIn` says so in seconds. | | Stopping it | Revoking the key stops all its tokens within 5 s. One token cannot be revoked alone. | | What it costs | Making a token costs 1 credit. What its holder then does is charged to your account at the usual prices. | | Where it shows | [Your account page](https://app.aurelia.build/#/account) counts it all under the key that made it, per endpoint, with making tokens as its own line. | | Limits | The burst limit and the open sockets (5 on every plan) are your account's, shared by all your keys and tokens however many users hold them. With more live viewers than that, keep the sockets on your server and pass on what your users need. | | What is inside | `sub` (your account's id), `keyId` (the key's id), `iss`, `aud`, `iat` and `exp`, with no email or name. Whoever holds it can read them. | A token spends your credit until it expires, and whoever holds it can copy it. So make them only for users you have signed in, and limit how often each user can ask for one. #### 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/*` (apart from `POST /auth/token`) are open and not metered; [`GET /plans`](https://app.aurelia.build/docs/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. Making a short-lived token (`POST /auth/token`) costs 1. On the WebSocket, opening a socket and each subscription cost 1, and every data message it receives costs 0.01. 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](https://app.aurelia.build/#/account) 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](https://app.aurelia.build/#/account) 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 `/live` costs one credit, and so does every `subscribe` on it: a subscription is metered like the REST request it replaces, and refused like one (`429 rate_limited` with `retryAfter`, `402 insufficient_balance`), as an `error` message for that subscription. Each data message it then sends costs 0.01 credits. - Open sockets per account, subscriptions per socket and `balances` subscriptions per socket are capped by plan (`live` in [`GET /plans`](https://app.aurelia.build/docs/plans)). Past a socket's caps a subscribe is refused with `429 too_many_subscriptions` or `429 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: `4401` unauthorized, `4402` out of credit, `4429` rate limited or `too_many_connections`. A signed-in caller's credential is checked again every 5 s; the socket closes with `4401` once 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: ```bash curl -si -H 'Authorization: Bearer aur_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](https://app.aurelia.build/#/account). | | 403 | `{"error":"api_key_required"}` | A JWT or a session asked [`POST /auth/token`](https://app.aurelia.build/docs/auth-token) for a token: only an API key can make one. Nothing is charged. | | 429 | `{"error":"rate_limited","retryAfter":}` | Over the burst limit. Wait `retryAfter` seconds, also sent as `Retry-After`. | No key, and a bad key, captured on a test instance: ```bash curl -s -w ' %{http_code}\n' 'https://app.aurelia.build/api/chains' {"error":"unauthorized"} 401 curl -s -w ' %{http_code}\n' -H 'Authorization: Bearer aur_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=aur_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 `aur_YOUR_KEY`. A request with your key, curl: ```bash curl -s -H 'Authorization: Bearer aur_YOUR_KEY' \ 'https://app.aurelia.build/api/tokens/solana/So11111111111111111111111111111111111111112' ``` A request with your key, JavaScript: ```javascript const res = await fetch("https://app.aurelia.build/api/tokens/solana/So11111111111111111111111111111111111111112", { headers: { Authorization: "Bearer aur_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: ```python import requests r = requests.get( "https://app.aurelia.build/api/tokens/solana/So11111111111111111111111111111111111111112", headers={"Authorization": "Bearer aur_YOUR_KEY"}, timeout=10, ) r.raise_for_status() print(r.json()) ``` The socket with your key, JavaScript: ```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 every visitor can read a key: use a short-lived token your server made with it. const { token } = await (await fetch("/your-server/aurelia-token")).json(); const live = new WebSocket(`wss://app.aurelia.build/api/live?jwt=${token}`); live.onopen = () => live.send(JSON.stringify({ op: "subscribe", id: "sol", channel: "token", chain: "solana", token: "So11111111111111111111111111111111111111112" })); ``` The socket with your key, Python: ```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 | ### Short-lived token: `POST /auth/token` A JWT made with your API key, valid for 15 minutes. Your server hands it to a browser or app, which calls the API with it directly, so your key never leaves your server. **Parameters** None. **Response** | Field | Type | Meaning | |---|---|---| | `token` | string | The JWT. Send it as `Authorization: Bearer `, or as `jwt=` on [`/live`](https://app.aurelia.build/docs/live). | | `expiresIn` | number | Seconds until it expires: 900. | **Example** (captured 2026-11-04 23:54 UTC on a test instance) ```bash curl -s -X POST -H 'Authorization: Bearer ' 'https://app.aurelia.build/api/auth/token' ``` ```json { "token": "eyJhbGciOiJFZERTQSIsImtpZCI6IkVxUjVHdkdSOEJsdUFKVGlkYnFRUk9UdlpTa29sYndoIn0.eyJzdWIiOiJyY0dpYUtLbWV6VnJ3cWZCa2VLVk9WUUo1OVFlbzdrNSIsImtleUlkIjoibzBhbWdJM1c5TkhBMkxnYjRZc1JiMnRsZ0xCQmlnRjIiLCJpYXQiOjE3OTM4MzY0OTEsImV4cCI6MTc5MzgzNzM5MSwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo3MDcxIiwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdDo3MDcxIn0.YX4gbfclZcsxpXS9epWKkcSvyBjsPSMw8ODe2axmjrCeej-lYK9QFBKzn_cB2XHpN29yvaL33Xda4GI-vOaLDg", "expiresIn": 900 } ``` **Notes** - Send your API key as `Authorization: Bearer` or `x-api-key`. Call this from your server: it does not answer web pages on other sites. [Tokens for your users](https://app.aurelia.build/docs/auth) shows the whole flow. - A token works on every data route and on `/live`, as the key that made it. It cannot make another token (403 `api_key_required`, nothing charged), and it cannot reach your account, keys or billing. - Making a token costs 1 credit. What its holder then does is charged to your account at the usual prices, and [your account page](https://app.aurelia.build/#/account) counts both under the key that made it. - Revoking the key stops its tokens within 5 s. A token cannot be revoked alone, and it cannot be extended: make another. - Tokens share your account's limits: the burst limit and the open sockets count across all your keys and tokens, however many users hold them. - Its claims are `sub` (your account), `keyId` (the key), `iss`, `aud`, `iat` and `exp`, with no email or name. Whoever holds it can read them. To check one yourself, verify it against `/auth/jwks` (EdDSA). ### Plans: `GET /plans` The plans and their limits, the WebSocket caps included, as the API enforces them, so a client never hardcodes them. **Parameters** None. **Response** | Field | Type | Meaning | |---|---|---| | `[]` | array | One entry per plan: `free`, `paid`. | | `name` | string | The plan, as sent back in `X-Aurelia-Plan`. | | `burstPerSecond`, `burstCapacity` | number | The burst limit: a bucket refilling `burstPerSecond` a second and holding `burstCapacity`, so a page's first batch of requests goes through. | | `freeCredits` | number | On `free` only: the credits every account gets once. They do not renew. | | `prices` | array | On `paid` only: what credits past the free ones cost, in USD: `from`, the size of one deposit, and `per1000`, the price of 1,000 credits on a deposit that size or larger. | | `minimumDeposit` | number | On `paid` only: what deposits must total, in USD, before any of them is credited. | | `live.sockets` | number | `/live` sockets open at once, per account. | | `live.subscriptions` | number | Subscriptions one socket holds at once. | | `live.balances` | number | Of those, `balances` subscriptions: each reads its wallet from the chain. | **Example** (captured 2026-11-01 14:56 UTC on a test instance) ```bash curl -s -H 'Authorization: Bearer ' 'https://app.aurelia.build/api/plans' ``` ```json [ { "name": "free", "burstPerSecond": 5, "burstCapacity": 20, "freeCredits": 10000, "live": { "sockets": 5, "subscriptions": 25, "balances": 5 } }, { "name": "paid", "burstPerSecond": 30, "burstCapacity": 120, "prices": [ { "from": 0, "per1000": 0.28 }, { "from": 280, "per1000": 0.224 } ], "minimumDeposit": 5, "live": { "sockets": 5, "subscriptions": 100, "balances": 10 } } ] ``` **Notes** - Open and not metered. - See [Authentication and limits](https://app.aurelia.build/docs/auth) for who gets which plan, and [the socket](https://app.aurelia.build/docs/live) for what the caps count. ### Health: `GET /health` Whether the API can reach each of its three stores. Use it for uptime checks. **Parameters** None. **Response** | Field | Type | Meaning | |---|---|---| | `healthy` | boolean | `true` when all three services answer. | | `services.postgres` | string | `"ok"`, or the error message. | | `services.clickhouse` | string | `"ok"`, or the error message. | | `services.dragonfly` | string | `"ok"`, or the error message. | **Example** (captured 2026-09-28 18:03 UTC on a test instance) ```bash curl -s -H 'Authorization: Bearer ' 'https://app.aurelia.build/api/health' ``` ```json { "healthy": true, "services": { "postgres": "ok", "clickhouse": "ok", "dragonfly": "ok" } } ``` **Notes** - `200` when healthy, `503` with the same body when any service is down. - Never cached: every call checks all three. - For a liveness probe that touches nothing, `GET /` answers `{"name":"aurelia","status":"ok"}`. - Open and not metered, like `/`, `/plans` and `/auth/*`: it needs no key and costs no credits. ### Chains: `GET /chains` Every listed chain: how far indexing has got and how far behind the tip it is, and the chain's last 24 hours: volume, trades, traders, new tokens and pools. **Parameters** None. **Response** | Field | Type | Meaning | |---|---|---| | `chains[]` | array | One per listed chain: `evm:56` (BSC), `solana`, `evm:8453` (Base), `evm:4663` (Robinhood Chain) and `evm:1` (Ethereum). | | `chains[].head` | number \| null | Latest block (on Solana, confirmed slot) seen at the tip. `null` when no listener has reported one in the last 2 minutes. | | `chains[].listener`, `parser` | { number, hash } \| null | The last block fetched from the chain and queued, and the last block fully indexed: everything up to it is queryable. | | `chains[].lag` | number \| null | Blocks (on Solana, slots) between `head` and `parser`; counted from `listener` while `head` is unknown. | | `chains[].gaps` | number | Block ranges deliberately skipped, for example when public RPC fell too far behind. Known gaps, never silent ones. | | `chains[].indexedTime` | number \| null | Block time of `parser`, unix ms: `Date.now() - indexedTime` is how far behind real time the data is. | | `chains[].indexedSince` | number \| null | Block time of the oldest indexed block, unix ms: history starts there. | | `chains[].stats24h` | object | `volumeUsd`, `trades` and `traders` (approximate, ~1%) of the chain's swaps over the last 24 h, each swap once; `newTokens`, tokens whose creation Aurelia saw; `newPools`, pools created in the chain's blocks of the last 24 h (`null` without such a block). | | `updatedAt` | number | When the answer was built, unix ms. | **Example** (captured 2026-09-28 18:03 UTC on a test instance) ```bash curl -s -H 'Authorization: Bearer ' 'https://app.aurelia.build/api/chains' ``` ```json { "chains": [ { "chain": "evm:56", "head": null, "listener": null, "parser": { "number": 1000750, "hash": "0x00000000000000000000000000000000000000000000000000000000000f452e" }, "gaps": 0, "lag": null, "indexedTime": 1790618578665, "indexedSince": 1790546261920, "stats24h": { "volumeUsd": 67218, "trades": 131, "traders": 8, "newTokens": 3, "newPools": 4 } }, { "chain": "solana", "head": null, "listener": null, "parser": { "number": 449600022, "hash": "5q3oRWqw8jV5Wdw7vbCE9Bn1MG4xyCZjK3VTk6UGk26Y" }, "gaps": 0, "lag": null, "indexedTime": 1790618141920, "indexedSince": 1790546261920, "stats24h": { "volumeUsd": 31848, "trades": 61, "traders": 4, "newTokens": 1, "newPools": 1 } } ], "updatedAt": 1790618612055 } ``` **Notes** - Cached 15 s.