TraktorAPI
Get API key

Stream

Connecting, subscribing, resuming and keeping up on the traktor.v1 trade stream.

wss://api.traktor.fun/v1/stream delivers origin: "live" trades as Traktor stores them, chosen by a flow filter.

WS /v1/stream

wss://api.traktor.fun

Trade rows matching your filters as they land, metered in stream units.

Credential
API key
Subprotocol
traktor.v1

Also takes the flow filter.

Limits

  • Sockets not yet authenticated10 at once, per IP
  • Sockets not yet authenticated500 at once, per server process
  • Frames before auth5 frames, per connection
  • Time to finish auth5 seconds, per connection
  • Frame size before auth4,096 bytes, per connection
  • Frame you send65,536 bytes, per connection
  • Refused frames before close 4007100 in 10 seconds, per connection
  • Events queued4,096 events, per subscription
  • Unread buffer before close 400830 seconds, per connection
  • Invalid keys30 a minute, per IP
  • Stream-unit count refreshed every15 minutes, per server process

Upgrade refusals

StatusBodyRetry-AfterWhen
400unsupported_subprotocolA Sec-WebSocket-Protocol header is sent and does not offer traktor.v1; a request with no such header is accepted.
429try_later5 sToo many sockets have not authenticated yet: from your IP, or on the server when it cannot make room, because no network holds two more of them than yours (an IPv6 /48 or an IPv4 address) or your upgrade URL carries a credential. Also sent while the server is draining for a deploy.
400upgrade_failedThe request offers traktor.v1 or no subprotocol, the server is not draining, and your IP and the server have room, but it is not a WebSocket upgrade.

Lifecycle

  1. Connect with the subprotocol traktor.v1, and put nothing in the URL: a query parameter named like a credential closes the socket 4015. hello arrives at once, before you authenticate, with the protocol version, the heartbeat and the frame sizes; hello.auth says how many frames you may send before you authenticate, how many bytes each may hold, and how many milliseconds you have.

  2. Send auth as your first frame, with your key in key. Authentication must finish within hello.auth.deadlineMs of connecting, the server's check of the key included, or the socket closes 4001; a server full of connections still authenticating can close the oldest from the network holding the most with 4001 sooner, to make room. The ack holds your plan's limits, the chains and apps tracked, and head, the stream's position; read the limits there rather than copying the plans table. A refused key closes the socket instead: 4002, or 4003 for a suspended account. Frames you send after auth and before its ack wait for the key check, and past ten of them each is refused rate_limited, so send your subscriptions once the ack arrives.

  3. Send subscribe with a flow filter in filter; left out, the filter's defaults apply. Its other keys are optional: sub, your name for the subscription, which the server picks when you leave it out; delivery, with maxBatch, the most events one frame carries, and maxDelayMs, the longest the server holds an event to fill a frame; and from, to resume. The ack's result.seq is the stream's position when the subscription opened.

  4. Trades arrive in events frames. d holds a batch, each element a trade row without tx and leg, which id holds. seq is the position of the batch's last trade, an opaque string: keep the latest for each subscription.

The stream carries only origin: "live" rows: a row Traktor recovers later, with origin sweep or backfill, never reaches it. The same trade can arrive twice, under two seqs, so deduplicate on id. The Quickstart has a client in TypeScript and Python.

Every frame is one JSON object with op and id. Each frame you send gets one answer, which carries the frame's id as re: an ack, an error, or a pong for a ping. Answers come in the order you sent the frames, with one exception: while a replay is being sent or auth's key is being checked, the frames you send wait for it, and one sent while twice your plan's per-second inbound rate of them already wait (during the key check, ten sent after auth) is refused rate_limited at once, ahead of the frames before it. An error for a frame with no readable id has no re, and a fault that closes the socket answers with a bye instead. Ignore a server op you do not know. A field the server does not know is refused, so a typo never widens a filter.

One channel is served, flows; a subscribe that names another answers channel_denied. A chains or platforms name outside the vocabulary answers filter_invalid, with detail.pointer at the key you sent: /filter/chains, or /filter/chain if you spelled it so. Both spellings of one key answer filter_invalid at the plural's pointer, /filter/chains. Either way no subscription opens.

Frames

You send

OpFieldsWhat it is
authid, key, client?The first frame: the API key, and an optional client name of up to 128 characters.
subscribeid, sub?, channel?, filter?, delivery?, from?Opens a subscription on a flow filter, under the name sub or one the server gives it; from resumes after a seq. delivery sets batching: maxBatch, the events one frame may carry, 1 to 500 and 50 by default, and maxDelayMs, the milliseconds an event may wait to fill one, 0 to 2,000 and 250 by default, or mode: "frame" for one event a frame; a value out of range is bad_frame.
unsubscribeid, subCloses one subscription, or a list of them at once; an unknown name removes none.
subsidLists the connection's subscriptions and where each one stands.
pingid, payload?Asks for a pong; a payload of up to 64 characters comes back in it.
byeidFlushes what is queued, acknowledges, and closes with 1000.

The server sends

OpFieldsWhat it is
helloid, protocol, server, now, auth, heartbeat, frameSent on connect: the pre-auth budget, the heartbeat, and the frame size limits.
ackid, re, resultThe answer to a frame that succeeds, ping aside: to auth, the account, its limits and the feed; to subscribe, the subscription's seq and warnings, and with from whether it resumed and what gap there is; to unsubscribe and bye, the names removed; to subs, the subscriptions.
errorid, re?, code, message, retryable, sub?, detail?A refused frame; it never closes the socket.
eventsid, sub, seq, sentAt, dTrade rows for one subscription; seq is the position of the last one.
heartbeatid, now, lagMs, subs, partialEvery heartbeat interval: each subscription's seq, dropped and queued counts, fifty at a time.
noticeid, kind, sub?, message?, detailSomething changed for a subscription or the connection; the notice kinds say what.
pongid, re, payload?, nowThe answer to ping.
byeid, code, reason, message, retryable, retryAfterMs?, detail?Sent before the server closes the socket, with the close code it is about to use.

Notice kinds

CodeMeaning
droppedEvents were dropped for backpressure, oldest first; detail has count, since, reason, policy and queued, and dropped events are never billed.
batch_pressureBatching widened under pressure; detail is the batch size and delay now in force.
replay_incompleteA resume span was not fully replayed; detail.reason says why. The subscription carries on from the ack's seq, and the rest of the span is not sent.
plan_changedThe account's plan or billing period changed; detail has the plan codes from and to, equal when only the period rolled over, and the new limits apply from now. Subscriptions already open stay; a lower cap on subscriptions, allow lists, filter score or connections applies to the next subscribe and the next connection.

Ack warnings

CodeMeaning
usd_amount_nullableA USD bound leaves out rows whose usdAmount is null.
blocktime_interpolatedSome EVM block times are interpolated; each event says so in blockTimeSource.
rbh_buy_understatedRobinhood buy notionals read about 8% low, systematically.
replay_reorderseq is monotonic and blockTime is not: a live row can arrive long after its block.
coverage_not_trackedA chain the filter names has no tracked app, or an app it names is tracked on no chain, so that name matches no trade; message names each. Sent today only for elysium, which is in the vocabulary while no app is read there.
rate_cap_below_feedThe plan's event rate is below the feed's mean on a wide filter, so drops are expected.

A heartbeat arrives every 15 seconds; the socket closes after 45 seconds without a frame from you.

The ack's warnings qualify a subscription as a whole; each event's flags qualify its own row (Flags).

Resume

After a disconnect, subscribe again with from set to the last seq you processed. If the stream has not been rebuilt since, and that seq is younger than the plan's replay window, the span after it replays in order with no gap, each event judged by the filter as it was live, and the ack says resumed: true. Otherwise the ack says resumed: false, and gap.reason says why:

  • trimmed: the span is older than the plan's replay window, or than the stream retains.

  • epoch_stale: the stream was rebuilt after that seq.

  • replay_unavailable: the server could not read the span.

The subscription still opens, and carries on from the ack's seq: the trades in the gap are not sent. A from past the stream's newest event opens nothing: it is refused bad_frame at /from, so subscribe again without it. A plan with no replay window cannot resume: its from answers trimmed, or epoch_stale once the stream has been rebuilt. Each plan's window is in Plans and limits.

A span cut short after the ack arrives as a replay_incomplete notice, with detail.reason and detail.oldestAvailable. The subscription carries on from the ack's seq, and the rest of the span is not sent.

Flow control

Each subscription queues up to 4,096 matching events. When events match faster than your plan's event rate delivers them, or faster than your socket reads, the queue fills and stays full: the oldest are dropped and counted, and each event you do receive has waited about 4,096 ÷ the matches a second, minutes on a wide filter and longer still on one that matches only a little faster than the plan delivers. To stay current, narrow the filter until a heartbeat's queued stays low.

A dropped notice arrives within about a second of a drop, with detail { count, since, reason: "backpressure", policy: "drop_oldest", queued }, and every heartbeat carries each subscription's running total. Dropped events are never billed.

Shedding never closes the socket. 4008 closes one that stops reading: its unread buffer stayed over the server's limit for 30 seconds.

While a subscription's queue is long, the server widens its batching, never narrows it, and says so in a batch_pressure notice whose detail is the batching in force. Your own batching returns, without a notice, once the queue shortens.

Liveness

The silence limit under Frames also counts the pong your client returns to the server's WebSocket ping, which comes at least twice within the limit; most libraries send that pong on their own. If yours does not, send a ping frame more often than the limit. After a 4009, reconnect at once.

Plan changes and revocation

The server checks your key once a minute. A change of plan or billing period arrives as a plan_changed notice, with the plan codes in detail.from and detail.to, and the new limits apply from then. Subscriptions already open stay as they are: a lower cap on subscriptions, allow lists, filter score or connections applies to your next subscribe and your next connection. A key that no longer authenticates, because it was revoked or expired or its account was suspended or closed, closes the socket 4003 at that check; the bye before it names, in detail.code, the code a REST request would get (When a key is refused). Once the month's stream units are spent, the socket closes 4006 (Stream units).

Close codes

Every close the server starts is preceded by a bye, with retryable and the close's detail. 1013, the one close that would not be, is not sent today. When a bye carries retryAfterMs, wait that long before you reconnect. After your own bye, the server sends what is queued, as far as your socket keeps reading, acknowledges it, and closes 1000; what your socket had no room for is not sent, so resume from the last seq you processed.

A frame over 65,536 bytes never reaches the server: the transport drops the connection with no bye and no close frame, and your client sees 1006.

A deploy closes 1001. Its bye carries retryAfterMs, each subscription's resume point in detail.subs[].seq, and detail.lastSeq, the earliest of them. Before that bye the server sends each subscription's queued events, as far as your socket keeps reading, and any pending dropped notice, so a resume point never lies past an event you were neither sent nor told was dropped. Reconnect after retryAfterMs, then resume each subscription with from set to its own resume point, or every one from lastSeq and deduplicate on id; a null resume point means subscribe afresh.

Treat a 4xxx code the table below does not list as not retryable, unless its bye says it is.

CodeReasonMeaningWhat to dobye first
1000byeThe server closed after your bye and its ack.Nothing to do.no
1001deployThe server is redeploying; the bye carries lastSeq and each subscription's seq.Reconnect after retryAfterMs and resume each subscription with from.yes
1003unsupported_dataA binary frame arrived; the protocol is text only.Send JSON text frames.yes
1009message_too_bigA frame sent before auth was larger than the pre-auth limit.Send a small auth frame first.yes
1013not readyNot sent today: the server attaches its socket runtime before it listens. A socket opened before then would close here, before hello and with no bye.Reconnect after a short wait.no
4000protocol_errorMore frames than the pre-auth budget arrived before auth.Send auth as the first frame.yes
4001auth_requiredAuthentication had not completed by the deadline: no auth frame arrived, or the one that did was still being checked. Also sent sooner when the server holds as many connections still authenticating as it may and closed this one to make room for another: yours was the oldest of the network holding the most, an IPv6 /48 or an IPv4 address.Send auth as soon as hello arrives.yes
4002auth_failedThe key in auth is invalid or revoked (a key past its expiry, or one whose account is closed, counts as revoked), or your IP had spent its refused-key budget when the key's cached record had lapsed; detail.code says which.Use a valid key. A key that is still valid can reconnect once its IP's budget refills, within a minute.yes
4003auth_expiredThe account was suspended at auth, or the minute's check refused the key mid-stream: either it stopped authenticating (revoked or expired, or its account suspended or closed), or your IP had spent its refused-key budget when the key's cached record had lapsed; detail.code says why.Use a live key. A key that is still valid can reconnect once its IP's budget refills, within a minute.yes
4005connection_limitThe account already holds as many sockets as its plan allows; detail has limit and inUse.Close another socket, or reuse one.yes
4006quota_exhaustedThe month's stream units are spent; detail has used, included and resetsAt. The count is the usage rollup's, which runs every fifteen minutes, so it trails what the socket has used by up to that long. The allowance is checked at auth, on a resume with from, and every minute.Wait for resetsAt, or move to a larger plan.yes
4007command_floodToo many frames were refused for rate in a short time.Send fewer frames, then reconnect.yes
4008slow_consumerThe socket stopped reading: its buffer stayed over the limit too long; detail has the dropped total.Read faster or narrow the filters, then reconnect and resume.yes
4009heartbeat_timeoutNothing arrived from you within the silence limit: no frame, and no pong to the server's WebSocket pings, which come at least twice within it.Answer the server's pings, as most WebSocket libraries do on their own, or send ping more often than the limit. Reconnect at once and resume.yes
4015credential_in_queryThe upgrade URL carried a credential-shaped query parameter.Rotate that key: it is now in logs. Send keys only in auth.yes

Every code an error frame carries is on Errors.

    Stream · Traktor API