Plans and limits
Each plan's limits, and how the API meters requests and stream units against them.
Every key belongs to an account on a plan, and the plan sets how fast and how much the account may ask.
Plans
The table is read live from GET /v1/plans, which takes no key.
| Limit | Free | Pro |
|---|---|---|
| Price | $0 a month | $99 a month |
| Requests | 30 a minute | 600 a minute |
| Burst | 30 | 600 |
| REST units | 20,000 a month | 5,000,000 a month |
| Stream units | 60,000 a month | 1,000,000 a month |
| History | 60 min | 24 h |
| Sockets | 1 | 10 |
| Subscriptions per socket | 2 | 50 |
| Events delivered | 5 a second | 300 a second |
| Event burst | 25 | 1,500 |
| Frames you send | 5 a second | 20 a second |
| Addresses per list | 10 | 50 |
| Replay window | None: cannot resume | 120 s |
| Filter score | 10 | 120 |
| API keys | 2 | 20 |
The per-minute budget counts requests: each costs one, whatever its units. The monthly allowance counts units.
Stream units, per key and clock hour: ⌈connection seconds ÷ 60⌉ + ⌈filter-score seconds ÷ 60,000⌉ + ⌈events delivered ÷ 1,000⌉.
Upgrading
Pro is bought on the web, from the API tab of your Traktor profile, and only while that tab offers Upgrade to Pro: it opens Stripe Checkout, and the account moves to Pro once Stripe confirms the payment. An account Stripe bills changes its card, reads its invoices or cancels from Manage billing on the same tab, which opens Stripe's billing portal; a cancelled plan stays Pro until the period paid for ends. The Traktor app sells nothing.
Requests and units
Each route's cost in units is on its endpoint block. The per-minute budget and the monthly allowance belong to the account, so every key it holds draws on the same two. A 304 draws no units and still counts as a request. The alert, notification and Telegram routes draw no units; their per-account limit is on their endpoint blocks and under Every limit.
Limit headers
A metered route sets these headers on every answer to a request it admits, internal_error aside, and on a 429 from its budgets:
| Header | What it says |
|---|---|
RateLimit-Limit | The per-minute budget, in requests. |
RateLimit-Remaining | Requests you can send now before the per-minute budget refuses one. |
RateLimit-Reset | Seconds until the per-minute budget is full again; on quota_exhausted, until the period's units return. |
RateLimit-Policy | The per-minute budget, its window and its burst. Its unit="rest-units" counts requests, not units. |
X-Traktor-Cost | Units the request drew: 0 when its budgets refused it or its units were refunded. |
X-Traktor-Quota-Remaining | Units left this period; absent on a 429. |
X-Traktor-Plan | The plan's id, as GET /v1/plans lists it. |
A script in a browser can read every one but X-Traktor-Plan and RateLimit-Policy.
A refusal raised before admission carries none of these headers and draws on neither budget. One raised after admission counts against the per-minute budget, and its units come back: X-Traktor-Cost: 0. Where each refusal falls:
GET /v1/marketand its views refuse every fault in the query before admission: a repeated key, the grammar's and paging's refusals,plan_forbidsandroute_not_found. What fails once the read starts comes after:filter_too_broad,data_unavailable,market_busyandmarket_fold_pending.GET /v1/signalsrefuses a fault in its query before admission, and a cursor's refusals after:invalid_cursor,cursor_query_mismatchandcursor_expired.An
internal_errorafter admission gives its units back too, and its answer carries none of these headers.
When a budget runs out
429 rate_limited: the per-minute budget is spent. Wait forRetry-After, then retry.429 quota_exhausted: the period's units are spent, or fewer remain than the request costs.details.resetsAtsays when they return, andRateLimit-Resetcounts down to it; waiting a minute will not help. The refusal still counts against the per-minute budget.503 rate_limit_unavailable: the limiter could not be asked. Retry afterRetry-After.
Neither 429 draws units.
Stream units
The trade stream, and the market stream under a key, draw on the account's monthly allowance of stream units, counted per key and clock hour by the formula under the plans table. A keyed market socket's seconds open count as connection seconds, and each view it is sent as an event delivered. The count it is checked against is refreshed every 15 minutes, so it trails your use by up to that long. The trade stream checks it when you authenticate, when you resume with from, and every minute, the market stream before the upgrade and every minute, and each closes with 4006 quota_exhausted once the count passes the allowance.
Every limit
Each route's limits, as its endpoint block states them; routes with the same limits share a row. A route that takes an API key also draws on its account's per-minute budget and units, above.
| Routes | Limits |
|---|---|
GET /v1/plans |
|
GET /v1/meta/filters |
|
GET /v1/public/market |
|
GET /v1/public/search |
|
GET /v1/stream/position |
|
GET /v1/usage, GET /v1/market/summary, GET /v1/market/tokens, GET /v1/market/series, GET /v1/market/wallets, GET /v1/tokens/{chain}/{address}, GET /v1/tokens/{chain}/{address}/signals, GET /v1/traders/directory, GET /v1/traders/{wallet}, GET /v1/traders/{wallet}/portfolio, GET /v1/stocks, GET /v1/stocks/registry, GET /v1/signals/board |
|
GET /v1/market, GET /v1/market/tape, GET /v1/market/rotation, GET /v1/market/whales |
|
GET /v1/signals |
|
GET /v1/leaderboard |
|
GET /v1/search |
|
POST /v1/browser-sessions/exchange |
|
GET /v1/browser-sessions/current, DELETE /v1/browser-sessions/current, GET /v1/alerts, GET /v1/alerts/{id}, PATCH /v1/alerts/{id}, DELETE /v1/alerts/{id}, POST /v1/alerts/{id}/duplicate, GET /v1/notifications/unread-count, POST /v1/notifications/read, GET /v1/webhooks, DELETE /v1/webhooks/{id}, GET /v1/telegram/links, DELETE /v1/telegram/links/{chatId} |
|
POST /v1/alerts |
|
POST /v1/alerts/{id}/test |
|
POST /v1/alerts/preview |
|
GET /v1/notifications |
|
POST /v1/webhooks |
|
POST /v1/webhooks/{id}/test |
|
POST /v1/telegram/link-codes |
|
WS /v1/stream |
|
WS /v1/market/stream |
|
Every per-IP limit counts an IPv6 caller by its /64.
The per-IP limits of GET /v1/public/market and GET /v1/public/search, the market stream's keyless connections and the trade stream's sockets not yet authenticated are counted by each API server on its own, and the API runs on more than one, so a client past one of those limits can still be answered by a server whose count is below it. Each server refuses an IP once its own count passes the limit, so keep to the number in the table. Every other per-IP limit, invalid keys included, and every per-account one, is counted across all of them.
Those two reads count an IP's requests in a minute that starts at its first one, not a rolling minute, so up to twice the limit can be answered across the turn of a minute. Each server counts at most 10,000 IPs on each of them; while it holds that many, a request from an IP it is not counting yet takes the place of the IP whose minute started first, which starts a new minute at its next request.
Going past "Invalid keys" never answers 429: the refusal stays 401 invalid_key, even for a valid key the server has to look up again, until the budget refills (When a key is refused).