Errors
Every error code, over HTTP and on both sockets, with the shape each arrives in.
An error carries a stable code, and the code is the only thing to branch on.
The error envelope
Every REST route answers an error with an HTTP status and this body, except the routes the next section names:
{
"error": {
"code": "invalid_parameter",
"message": "polygon is not a chain",
"requestId": "req_01m40t5ceg7k2q9xv4ra",
"details": { "parameter": "chains", "value": "polygon" },
"docs": "https://traktor.fun/docs/errors#invalid_parameter"
}
}codenames the failure; the tables below list every one.messageis for a person, and may change.requestIdis also theX-Request-Idheader. Send your ownX-Request-Idof 1 to 128 letters, digits,_and-, and the answer carries it in both places.detailsholds the code's specifics, such asparameter,value,conflictsWithorallowed, and is{}when there are none.docsis the code's row on this page,https://traktor.fun/docs/errors#<code>.
An error is never a 200.
Where the envelope is shorter
GET /v1/public/market and GET /v1/public/search answer an error with code and message only, and no requestId, details or docs:
{ "error": { "code": "invalid_query", "message": "This market query is not supported." } }Stripe's webhook, and a path outside /v1/ that no route serves, answer the same shape.
A request body over 2 MiB, to any path, is refused with 413 and no body before any route reads it: the status is the answer. A body under that but over a route's own limit gets body_too_large.
The sockets refuse an upgrade in shapes of their own; each socket's endpoint block lists its refusals.
The market stream refuses a handshake that does not offer
traktor.market.v1, or that carries a query string, with400and{ "error": { "code": "invalid_market_handshake", "protocol": "traktor.market.v1" } }, which has nomessage. Under a key, its refusals carry anerrorholdingcode,messageand, where there are any,details, at the status its endpoint block lists: a refused key, a spent allowance, a full set of the account's sockets, andtry_laterat503, withRetry-After, when the key could not be checked. Its429, and its400for a request that offers the subprotocol, has no query string and is not an upgrade, have no body: the status is the answer.The trade stream's refusals hold the code as a string, such as
{ "error": "try_later" }. Its refusal of an unsupported subprotocol addssupported, the subprotocols it speaks.
HTTP codes
| Code | Status | Meaning | What to do |
|---|---|---|---|
invalid_parameter | 400 | A parameter's value is not one this route accepts, a chain or platform is outside the vocabulary (details.value), or one filter key came under both its spellings (details.conflictsWith); details.parameter names it. | Fix the value, or send one spelling; details says what was expected. |
unknown_parameter | 400 | The query names a key this route does not read. | Remove it; details.parameter names it. On the market routes details.accepts lists the keys they read, and a token-paging key sent without views=tokens carries details.requires instead. |
screen_conflict | 400 | A floor sits above its own ceiling, such as minUsd over maxUsd. | Swap or correct the two bounds. |
window_out_of_range | 400 | windowMinutes is not a window this route serves. | Use a window from the route's parameter table. |
limit_out_of_range | 400 | limit is not an integer from 1 to the route's maximum. | Send an integer from 1 to details.limit. |
too_many_addresses | 400 | A list names more addresses or tickers than the grammar allows. | Split the list across requests or subscriptions; in an alert rule, across alerts. |
filter_version_unsupported | 400 | The filter declares a grammar version this API does not speak. | Send v=1, or leave v out. |
invalid_cursor | 400 | The cursor is malformed, or its signature does not match. | Start again without a cursor. |
cursor_query_mismatch | 400 | The cursor was minted for another filter or another order. | Send the filter and order the cursor came from. |
key_in_query | 400 | A credential-shaped query parameter was sent, such as key, api_key or token. | Send the key in Authorization: Bearer, and rotate it: it may now be in proxy logs. |
invalid_api_key | 400 | The body's apiKey is missing or is not a string of 1 to 256 characters. | Send { "apiKey": "tk_live_…" }. |
invalid_json | 400 | The body is not valid JSON. | Send a JSON object. |
invalid_body | 400 | The body is JSON but not an object. | Send a JSON object. |
unknown_field | 400 | The body carries a field this route does not take; details.pointer names it. | Remove the field. |
invalid_alert_name | 400 | The alert's name is not a string of 1 to 80 characters. | Send a shorter, non-empty name. |
invalid_alert_enabled | 400 | enabled is not a boolean. | Send true or false. |
invalid_alert_patch | 400 | The PATCH names none of name, rule or enabled; one that repeats their current values is accepted and bumps the revision. | Send name, rule or enabled with the revision. |
invalid_alert_revision | 400 | revision is not a positive integer. | Send the revision you last read. |
alert_rule_unsupported | 400 | The stored rule uses a window, field or match alerts no longer support, so it cannot be enabled as it is. | Edit the rule, then enable it. |
invalid_notification_ids | 400 | ids must hold 1 to 100 notification ids. | Send between 1 and 100 ids, or all: true. |
invalid_notification_bound | 400 | through must be a notification's createdAt and id, and goes only with all: true. | Send through from a listed notification, beside all: true. |
invalid_webhook_url | 400 | url is not an https:// address on port 443, without credentials, whose host is on the public internet. | Send the endpoint's public https URL. |
invalid_unread_filter | 400 | unread is neither true nor false. | Send unread=true, unread=false, or leave it out. |
invalid_alert | 400 | alert is not an alert id. | Send the id of one of the account's alerts, or leave it out. |
duplicate_query_parameter | 400 | A query parameter appears more than once; details.parameter names it. | Send each parameter once, several values comma-separated in one: ?chains=bnb,base. |
unknown_query_parameter | 400 | The query names a parameter this route does not read; details.parameter names it. | Remove the parameter. |
invalid_limit | 400 | limit is not an integer from 1 to 100. | Send a limit from 1 to 100. |
invalid_query | 400 | The public market or search query is malformed or names a key its resource does not take. | Fix the query against the resource's keys. |
invalid_rule | 400 | The alert rule, or a part of it, is not an object; details.pointer names it. | Fix that part so it is a JSON object. |
invalid_scope | 400 | The rule's scope names something the alert grammar does not take; details.pointer names it. | Fix the scope against GET /v1/alert-capabilities. |
invalid_condition | 400 | A condition is malformed: a missing metric, an unsupported operator, a bound that is not a number. | Fix the condition at details.pointer. |
unknown_metric | 400 | A condition names a metric the alert grammar does not know. | Use a metric from GET /v1/alert-capabilities. |
unknown_signal | 400 | A signal condition names a signal the feed does not emit. | Use a signal from GET /v1/alert-capabilities. |
metric_source_mismatch | 400 | The metric exists, but not on this rule's source. | Use a metric the rule's source offers. |
invalid_trigger | 400 | The trigger's mode, cooldown or hysteresis is outside what alerts support. | Fix the trigger at details.pointer. |
invalid_source | 400 | source is not flow, token_window or signal. | Send one of the three sources. |
invalid_window | 400 | The rule's windowMinutes is not an alert window, or a signal rule names one. | Use a window from GET /v1/alert-capabilities; leave it out on a signal rule. |
invalid_match | 400 | match needs op of all or any and at least one condition, and a signal rule's all names one signal. | Fix match. |
alert_version_unsupported | 400 | The rule's v is missing or is not 1. | Send v: 1. |
too_many_conditions | 400 | The rule holds more conditions than alerts take; details.limit says how many. | Split the rule into two alerts. |
missing_credentials | 401 | No bearer token was sent. | Send Authorization: Bearer $TRAKTOR_API_KEY. |
invalid_key | 401 | The key is malformed or unknown (the two answer alike), or it is valid but the caller's IP has spent its refused-key budget and the key is not cached yet. | Check the key; if it is right, wait a minute and retry. |
key_revoked | 401 | The key was revoked or has expired, or its account was closed. | Use another key. |
session_required | 401 | This route takes a browser session (trk_session_…), and another credential was sent. | Exchange a key at POST /v1/browser-sessions/exchange and send that session. |
session_expired | 401 | The browser session is unknown, expired or revoked, or what opened it is gone: its key was revoked or expired, its Telegram unlinked, or its account made inactive. | Exchange a key for a new session. |
sign_in_required | 401 | The Traktor sign-in is unknown, expired or revoked, or its account is suspended or closed. While market data needs a sign-in, which it does not yet, a public market read or a keyless market socket with no sign-in answers it too. | Sign in to Traktor again; a suspended or closed account stays refused. By code, read the market with an API key on the paid routes. |
subscription_inactive | 402 | The account is suspended. | Nothing in the API lifts a suspension. |
plan_forbids | 403 | The request reaches further back than the plan reads; details.maxHistoryMinutes says how far it can. | Shorten windowMinutes, or move to a plan with more history. |
account_inactive | 403 | The account is suspended or closed, so it cannot open a browser session. | Nothing in the API lifts a suspension or a closure. |
web_sign_in_required | 403 | This credential may not do this: only Traktor's own sign-in on the web may link a group, manage API keys, read the account's usage or billing, open checkout or the billing portal, or connect a Telegram, and only Traktor's own sign-in, on the web or in the app, may read or change the account's saved tokens or the phones its alerts are pushed to. | Do it from traktor.fun or the Traktor app, signed in. |
insufficient_scope | 403 | An AI agent's connection was not granted the scope this route reads, such as market:read. | Connect the agent again and grant the scope. |
route_not_found | 404 | No route lives at this path. | Check the path against the reference. |
alert_not_found | 404 | No alert with this id on your account. | List your alerts for their ids. |
webhook_not_found | 404 | No webhook with this id on your account. | List your webhooks for their ids. |
telegram_link_not_found | 404 | This Telegram chat is not linked to your account. | List your links for their chat ids. |
method_not_allowed | 405 | The route does not serve this method; the Allow header lists those it does, except on the keyless public market and search routes, which send none and read with GET and HEAD. | Use a method the route serves. |
alert_limit_reached | 409 | The account holds as many alerts as it may keep. | Delete an alert first. |
alert_revision_conflict | 409 | The alert changed since you read it. | Read it again and resend with its new revision. |
alert_once_completed | 409 | This one-shot alert has already fired. | Duplicate it, or change its rule to arm it again. |
webhook_limit_reached | 409 | The account holds as many webhooks as it may keep. | Delete a webhook first. |
cursor_expired | 410 | The cursor is past its life: details.reason is age. | Start again without a cursor. |
body_too_large | 413 | The body is over the route's size limit; details.maxBytes says it. | Send a smaller body. |
filter_too_broad | 422 | A balance floor would select too many wallets for the views to read. | Raise the floor: minWalletBalanceUsd on the metered routes, where details holds the numbers and details.parameter names it; minBalance on the public market. |
rate_limited | 429 | A budget is spent: a per-minute one (the plan's, an IP's or the account's), or, on the keyless public market and search, the server's cap on reads or searches in progress at once or on the IPs it tracks. | Wait for Retry-After, then retry. |
quota_exhausted | 429 | The month's REST units are spent, or fewer remain than this request costs; waiting within the period will not help. | Wait for details.resetsAt, or move to a larger plan. |
preview_rate_limited | 429 | Too many alert tests in a short time. | Wait for Retry-After, then retry. |
stream_capacity | 429 | The public market gateway is at capacity. | Retry after Retry-After. |
internal_error | 500 | Something failed on Traktor's side. | Retry; quote requestId if it persists. |
data_unavailable | 503 | The data behind the answer is warming up or could not be built; on the metered routes details.reason says which. | Retry after Retry-After. |
market_busy | 503 | The server is already holding as many market reads waiting on a build, or running as many builds, as it allows. | Retry shortly. |
market_fold_pending | 503 | A committed batch of trades is still being folded into the market read. | Retry shortly, backing off if it persists. |
market_warming | 503 | The public market view has not been built yet. | Retry after Retry-After. |
market_unavailable | 503 | The public market read failed, or its resource is switched off on this service. | Retry after Retry-After; a switched-off resource sends none, and a retry will not change that. |
server_draining | 503 | The server that took this public market read is shutting down for an update, and it closes the connection with this answer. | Retry after Retry-After, on a new connection. |
rate_limit_unavailable | 503 | The rate limiter could not be asked. | Retry after Retry-After. |
sign_in_unavailable | 503 | While market data needs a sign-in, the sign-in could not be checked: a store behind the check did not answer. | Retry after Retry-After. |
telegram_not_configured | 503 | This service runs without a Telegram bot. | Telegram delivery is unavailable; there is nothing to change on your side. |
Codes Traktor's own apps answer with
These come only from the profile's key routes, Traktor's Telegram Mini App, the alert worker and Stripe's webhook, so a request made with an API key never meets them.
| Code | Status | Meaning | Where it comes from |
|---|---|---|---|
invalid_alert_batch | 400 | An alert worker batch failed its own validation. | Raised inside the alert worker, which no client calls. |
invalid_saved_tokens | 400 | add or remove is not a list of at most 200 strings; details.pointer names it. | Only Traktor's own site and app call the route that raises it, to keep the account's saved tokens in step. |
invalid_followed_traders | 400 | add or remove is not a list of at most 100 strings, or merge is not a boolean; details.pointer names it. | Only Traktor's own site and app call the route that raises it, to keep the traders the account follows in step. |
invalid_push_token | 400 | token is not an Expo push token. | Only the Traktor app calls the routes that raise it, to turn alert push notifications on or off for a phone. |
invalid_push_platform | 400 | platform is neither ios nor android. | Only the Traktor app calls the routes that raise it, to turn alert push notifications on or off for a phone. |
invalid_push_install | 400 | install is not the key an install of the app makes for itself: 64 lowercase hex characters. | Only the Traktor app calls the routes that raise it, to turn alert push notifications on or off for a phone. |
invalid_key_name | 400 | A key's name is not a string of 1 to 64 characters, or it holds a control character. | Only the API tab on traktor.fun calls the routes that raise it. |
invalid_settings | 400 | A bot setting is outside what the bot reads, or the change names none. | Only Traktor's Telegram Mini App calls the routes that raise it. |
invalid_init_data | 400, 401 | Telegram's signed init data is missing, expired or not signed for this bot. | Only Traktor's Telegram Mini App calls the routes that raise it. |
invalid_agent_client | 400 | client_id names no client registered at POST /oauth/register. | Only Traktor's consent page for connecting an AI agent, traktor.fun/connect, calls the route that raises it. |
invalid_agent_redirect | 400 | redirect_uri is not one the client registered, so the page sends nobody there. | Only Traktor's consent page for connecting an AI agent, traktor.fun/connect, calls the route that raises it. |
invalid_agent_request | 400 | The authorization request is malformed; details.error is the OAuth error the page sends back to the agent, at details.redirectTo. | Only Traktor's consent page for connecting an AI agent, traktor.fun/connect, calls the route that raises it. |
invalid_signature | 400 | The Stripe-Signature header is missing, malformed, outside its time tolerance, or does not verify against the endpoint's signing secret. | Raised only by Stripe's subscription webhook, POST /v1/stripe/webhook, which no client calls. |
telegram_not_linked | 403 | This Telegram is not linked to a Traktor account. | Only Traktor's Telegram Mini App calls the routes that raise it. |
telegram_reader_not_found | 404 | The bot has no chat with this Telegram user yet. | Only Traktor's Telegram Mini App calls the routes that raise it. |
agent_connection_not_found | 404 | No live connection with this id on the account. | Only the connected apps list in Traktor's profile calls the routes that raise it. |
key_not_found | 404 | No live key with this id on the account. | Only the API tab on traktor.fun calls the routes that raise it. |
push_device_not_found | 404 | No phone with this id takes the account's push notifications. | Only the Traktor app calls the routes that raise it, to turn alert push notifications on or off for a phone. |
alert_checkpoint_conflict | 409 | The worker's checkpoint moved under a batch. | Raised inside the alert worker, which no client calls. |
alert_continuation_pending | 409 | The worker has a fan-out to finish first. | Raised inside the alert worker, which no client calls. |
key_name_taken | 409 | A live key on the account already has this name. | Only the API tab on traktor.fun calls the routes that raise it. |
agent_limit_reached | 409 | The account holds as many connected apps as it may, ten. | Only Traktor's consent page for connecting an AI agent, traktor.fun/connect, calls the route that raises it. |
key_limit_reached | 409 | The account holds as many keys as its plan allows. | Only the API tab on traktor.fun calls the routes that raise it. |
already_subscribed | 409 | The account is on a paid plan or holds a subscription, so checkout has nothing to sell it. | Only the API tab on traktor.fun calls the routes that raise it. |
billing_not_started | 409 | The account has never been through checkout, so the billing portal has nothing to show. | Only the API tab on traktor.fun calls the routes that raise it. |
push_token_taken | 409 | Another account takes this phone's push notifications, and the request came from another install of the app than the one that turned them on. | Only the Traktor app calls the routes that raise it, to turn alert push notifications on or off for a phone. |
agent_connect_off | 503 | Connecting AI agents is switched off on this server. | Only Traktor's consent page for connecting an AI agent, traktor.fun/connect, calls the route that raises it. |
billing_unavailable | 503 | Checkout and the billing portal are not open on this server, or Stripe could not be reached. | Only the API tab on traktor.fun calls the routes that raise it. |
push_not_configured | 503 | This server pushes no alerts to phones yet. | Only the Traktor app calls the routes that raise it, to turn alert push notifications on or off for a phone. |
alert_evaluation_batch_too_large | 503 | A batch would evaluate more rules than the worker's safety bound. | Raised inside the alert worker, which no client calls. |
Stream codes
An error frame on the stream answers one frame and never closes the socket. A fault that closes the socket is a close code instead, listed under close codes.
| Code | Retryable | Meaning | What to do |
|---|---|---|---|
bad_json | The frame is not JSON. | Send one JSON object per frame. | |
bad_frame | The frame is not a JSON object, or carries an unknown field, a bad value, or a from this service did not mint or one past the newest event in the feed; detail.pointer names the field where there is one. | Fix the frame; for /from, subscribe again without it. | |
missing_id | The frame has no id of 1 to 64 characters from A-Z a-z 0-9 _ . : -. | Give every frame an id. | |
unknown_op | The op is not one of the client ops. | Use a client op. | |
not_authenticated | retryable | A frame other than auth or bye arrived before auth. | Send auth first. |
already_authenticated | auth arrived on a connection that already has an account. | Send auth once per connection. | |
unknown_sub | unsubscribe named a subscription the connection does not hold; nothing was removed. | List subscriptions with subs. | |
sub_exists | A subscription with this name is already open. | Choose another name, or unsubscribe first. | |
sub_limit | The connection holds as many subscriptions as the plan allows; detail has limit and inUse. | Close a subscription, or open another socket. | |
filter_invalid | The filter fails the flow grammar: an unknown field, a value out of bounds or outside its vocabulary, one key under both its spellings, or a minimum above its maximum; detail.pointer names the field, except that a minimum above its maximum leaves it a bare /filter/. | Fix the filter; send one spelling of each key. | |
filter_too_large | A tokens or wallets list within the grammar's bound is longer than the plan's wsAllowListMax; detail has limit and got. | Shorten the list. | |
filter_too_broad | The connection's filters would score past the plan's wsFilterScoreMax; detail has score, inUse and limit. | Narrow a filter, or close a subscription. | |
channel_denied | The frame asks for a channel other than flows. | Leave channel out, or send flows. | |
rate_limited | retryable | Frames arrived faster than the plan's inbound rate, or while a burst of them was still waiting on a key check or a replay; detail.retryAfterMs says when to send again. | Send fewer frames; persistent flooding closes the socket with 4007. |
internal | retryable | The server failed while handling the frame. | Retry the frame; reconnect if it repeats. |
Market stream codes
An error frame on the market stream answers one command, with its code and, when the command carried a valid one, its id, except a stream_capacity naming a subscription the server closed to make room, which answers none. The socket's closes are listed under close codes.
| Code | Retryable | Meaning | What to do |
|---|---|---|---|
bad_frame | The frame is not a JSON object, names an op this socket does not have, or carries a field its op does not take. | Send { op, id, … } with that op's fields only. | |
invalid_id | id is not 1 to 64 characters from A-Z a-z 0-9 _ . : -. | Give the frame a valid id. | |
unknown_subscription | unsubscribe named a subscription the connection does not hold. | Check the sub. | |
subscription_exists | A subscription with this id is already open. | Use a new id. | |
subscription_limit | The connection holds as many subscriptions as it may, the server's cap or under a key the plan's. Without a key, also: your IP's connections without a key already read their share of the server's queries, and this one would count toward it. | Unsubscribe from one first. | |
invalid_query | The query is not a { resource, params } object, or names a resource, a key or a value the public market does not take, or repeats a key. Under a key it is also a resource the paid API does not read, or a chain, app, DEX signal, launchpad or signal type name the grammar does not know, which the keyless socket drops. While market data needs a sign-in, which it does not yet, a socket with no API key, ticket or sign-in is also refused anything but the front door's three reads (FRONT_DOOR_MARKET_READS). | Fix the query against the resource's keys. | |
market_unavailable | The resource is switched off on this service. | Use another resource. | |
stream_capacity | retryable | The gateway holds as many market queries as it can. Sent for a subscription you did not just open, it closed that subscription to make room for another reader's new query: your IP, or under a key your account, read at least as many queries as that reader allowed: its whole share for an HTTP read, two for a metered REST read under a key, two more than another IP's or account's connection. A query only you read goes first; a query others read too is closed only when every reader of it reads that many, and then for every one of them. | Retry shortly. |