wss://api.decisionnode.com /v1/sessions/{id}/stream# npm install -g wscat
WSS=wss://api.decisionnode.com/v1
SESSION=ses_3f9a1c07b2e84d5f6a0c9e12 # from the open response
wscat -c "$WSS/sessions/$SESSION/stream" \
-H "Authorization: Bearer $DECISIONNODE_API_KEY"
# then type one frame per line:
> {"seq": 1, "frame": {"battery": 0.31, "obstacle": "none"}}Each text message you send is one JSON frame. Each message you receive is the reply to one frame, or an error about one frame. Frames are answered on the session's fixed part plus its recent frames.
Authentication#
AuthorizationheaderrequiredBearer dn_live_...on the WebSocket handshake, the key that opened the session. Browsers cannot set this header on a WebSocket, so connect from your server.
Path parameters#
idstringrequired- The
session_idthat Open a session returned, for exampleses_3f9a1c07b2e84d5f6a0c9e12.
Refused connections#
A connection the server cannot take opens and closes at once, before any frame, with a close code rather than an HTTP status:
4401: the key is missing or unknown. Check theAuthorizationheader.4404: no open session with this id for your key: it ended, expired, never existed or belongs to another key. Open a new session.4409: a socket is already connected to this session. Use the socket you have, or open a new session.
Frames#
{
"seq": 17,
"frame": {
"battery": 0.31,
"wind_mps": 11.4,
"distance_home_m": 820,
"obstacle": "none"
}
}Frame
seqintegerdefaultthe last answered plus one- Your number for the frame, 0 or more. The reply carries it back, and
droppednames passed-over frames by it. framestring | object | array- What changed: text or a JSON value, at most the session's
max_frame_tokens. Required unless the frame carries animage; beside an image it is an optional short note, held to the same limit, that joins the window like any frame. imageobject- One image, which makes this an image frame, held to the image limits of
/v1/decide(up to 10 MB and 40 megapixels). The image is seen with this frame only; it does not join the window. It has noid, since a frame holds one image.media_typestringrequiredimage/jpeg,image/pngorimage/webp.datastringrequired- Base64 bytes, no
data:prefix.
questionsstring[]defaultall- The keys of the questions to answer this time, a non-empty list. Each frame is billed for the questions it asks.
resetbooleandefaultfalsetruedrops the recent frames from context before this one is answered.
Replies#
{
"seq": 17,
"answers": {
"mode": {
"type": "choice",
"choice": "return",
"confidence": 0.79,
"probabilities": {
"continue": 0.09,
"hold": 0.05,
"land": 0.02,
"return": 0.84
}
},
"abort": { "type": "truth", "truth": 0.07 }
},
"usage": { "input_tokens": 91, "output_tokens": 0 },
"safety": {
"checked": true,
"harm": 0.0021,
"self_harm": 0.0008,
"flagged": []
}
}Reply
seqinteger- The sequence number of the frame this answers.
answersobject- One answer per question asked, exactly as
/v1/decideshapes it, with the same calibration. droppedinteger[]- Only when frames were passed over: the
seqof each frame that arrived while an earlier one was being answered and was superseded by this one. A dropped frame is not answered, does not join the window and is not billed. usageobject- What this frame billed:
input_tokens(the frame, its image if it has one, and the questions it asked) andoutput_tokens, always 0. latency_msnumber- Time spent on our side for this frame, in milliseconds. On every reply; the samples here leave it out.
safetyobject- What the safety check found for this frame, as on every call:
checked, the probability of each category (harm,self_harm) andflagged, the categories above your key's thresholds. When the check did not run,checkedisfalsewith areason.
# npm install -g wscatWSS=wss://api.decisionnode.com/v1SESSION=ses_3f9a1c07b2e84d5f6a0c9e12 # from the open responsewscat -c "$WSS/sessions/$SESSION/stream" \ -H "Authorization: Bearer $DECISIONNODE_API_KEY"# then type one frame per line:> {"seq": 1, "frame": {"battery": 0.31, "obstacle": "none"}}Frame errors#
A frame the API cannot answer gets a message with its seq and an error object, error_type and message, and the socket stays open for the next frame. A refused frame's error also names the category, and the message carries safety with blocked. A rate_limit_error or overloaded_error carries retry_after_ms, the wait before you send the frame again. A frame that got an error leaves the window as it was and is not billed, except a refused frame: the model read it, so its tokens are billed.
{
"seq": 21,
"error": {
"error_type": "api_usage_error",
"message": "The frame is 5210 tokens, the limit is 1535."
}
}| Type | When | What to do |
|---|---|---|
api_usage_error | The message is not a JSON object; a field a frame does not take; seq that is not an integer of 0 or more; no frame and no image; an image that is not an object with media_type and data, cannot be read or is over the image limits; questions that is not a non-empty list of the session's question keys; reset that is not true or false; text or JSON over the session's max_frame_tokens | Fix the frame; seq is null, or missing, when the frame could not be read. For a long frame, send only what changed and move constant detail into the session's state |
rate_limit_error | Your key's input-token rate is used up for now: frames count against it | Wait retry_after_ms, then send the frame again |
overloaded_error | No capacity for this frame right now | Wait retry_after_ms when it is set, then send the frame again: it joins the window once |
api_error | A fault on our side | Send the frame again |
refusal_error | The safety check refused this frame; error.category names harm or self_harm | Change what the frame asks. The session carries on; the refused frame never joins the window, and its tokens are billed |
Close codes#
The close code says why the socket closed, and whether to open a new session with the same body. 4401, 4404 and 4409 come as soon as a connection opens; any other close ends the session. Branch on the code: the reason text beside it is for your logs and may change.
| Code | When | What to do |
|---|---|---|
1000 | The session ended on purpose: you closed the socket, or it was ended with DELETE /v1/sessions/{id} | Open a new session when you need one |
1001 | The server holding the session is going away. The session has ended | Open a new session with the same body; it opens on a server that is up |
1006 | The connection dropped without a close frame: a network fault or a proxy reset. Your WebSocket library reports this code; the server never sends it. The session has ended | Open a new session with the same body, after a capped backoff with jitter |
1011 | Something failed on our side. The session has ended | Open a new session with the same body, after a capped backoff with jitter |
4401 | The key is missing or unknown. Sent as soon as the connection opens | Check the Authorization header; opening again with the same key will not help |
4404 | No open session with this id for your key where the connection landed: it ended, expired, never existed or belongs to another key. Sent as soon as the connection opens | Open a new session with the same body |
4408 | No frame arrived for idle_timeout_seconds (120 seconds). Pings do not count. The session has ended | Open a new session with the same body when your next frame is ready |
4409 | A socket is already connected to this session. Sent as soon as the second connection opens; the first keeps the session | Use the socket you have, or open a new session |
4410 | The session reached expires_at. The session has ended | Open a new session with the same body at once; set ttl_seconds up to 3,600 for longer runs |
Limits#
| Limit | Value | Over the limit |
|---|---|---|
| Frame | Text or JSON of at most max_frame_tokens tokens (never more than 4,096; the open response's limits gives this session's value), or one image | A frame error, api_usage_error; the socket stays open |
| Image in a frame | One, of up to 10 MB and 40 megapixels, as on /v1/decide | A frame error, api_usage_error |
| Recent frames | Together at most window_tokens tokens, in the open response's limits | The oldest frames leave the window first |
| Window | 1 to 64 frames, 8 by default, the frame being answered included | 422 when opening |
| Lifetime | 300 seconds by default, 1 to 3,600 | 422 when opening; close code 4410 when reached |
| Idle | 120 seconds with no frame on an open socket | Close code 4408 |
| Sockets | One per session | Close code 4409 on a second connect |
| Token rate | Each frame's usage.input_tokens counts against your key's input-token rate, not its request rate | A frame error, rate_limit_error, with retry_after_ms; the socket stays open |
Notes#
- Send one frame per message, as a JSON object in a text message.
seqis yours: an integer of 0 or more, carried back in the reply. Rising numbers keepdroppedeasy to read; gaps are fine, so a client may skip readings.- Latest wins: if frames arrive faster than they are answered, the newest is answered and the ones passed over are listed in its reply's
dropped. To have every frame answered, send the next one after its reply. - A frame error never closes the socket. Only the close codes above end a session.
- Keepalive. The server sends WebSocket pings on an open socket, so proxies and load balancers see traffic on a quiet socket. Your library answers them with a pong on its own (both
websocketsandwsdo); a socket that stops answering pings is treated as gone, and your side sees1006. Pings and pongs are not frames: they are not billed and do not reset the idle timer. - Idle sockets. A socket that carries no frame for
idle_timeout_seconds(120 seconds) closes with4408and the session ends. A loop that may pause longer, such as a drone parked on its pad, either sends its current reading inside that time, withquestionsnaming one question so the frame bills little, or lets the session close and opens a new one when it resumes. The supervisor under Clients does the second. - For a client that stays up, see the complete supervisor: it opens a new session after every close it did not ask for, except
4401.