Direct request/response
Direct request/response is the synchronous RPC piece of the WebSocket protocol — for operations where you need a structured response, possibly large or chunked, in response to a specific request rather than as part of a stream.
It lives on the /v1/ws/commands endpoint alongside the
command-publish surface, but is conceptually distinct: commands
are fire and continue with results arriving later; direct
requests are send and wait with a chunked response.
When you need it
Most external integrations won't. The common use cases:
- Pulling a large response that doesn't fit a single REST call — when the message is more naturally chunked.
- Operations bound to a specific live telescope where you want the hub to route through the SkyNode WS connection rather than going through a separate gateway round trip.
- Internal protocols between hub services where the bidirectional nature of the WebSocket is the cleanest transport.
If you're not sure, start with REST. Move to direct request/response only if REST doesn't fit.
Protocol shape
A direct request is one direct_request message in, followed by
a stream of response messages out:
client server
│ │
│── direct_request ──────────────────▶ │
│ │
│ ◀──── direct_response_start ─────────│ metadata + total_size
│ ◀──── direct_response_chunk ─────────│ first chunk
│ ◀──── direct_response_chunk ─────────│ ...
│ ◀──── direct_response_chunk ─────────│ last chunk
│ ◀──── direct_response_end ───────────│ stream complete
│ │
If the request fails partway through, the server sends a single
direct_error instead of direct_response_end:
│ ◀──── direct_response_start ─────────│
│ ◀──── direct_response_chunk ─────────│
│ ◀──── direct_error ──────────────────│ request failed
Message shapes
Request
{
"kind": "direct_request",
"request_id": "<client-chosen id>",
"telescope_uid": "<uid of target>",
"request_type": "<application-specific>",
"payload": { /* request-specific */ }
}
The request_type is a string discriminator chosen by the
application layer; the catalog isn't centrally enumerated — each
request type is documented (or should be) where it's implemented.
Response start
{
"kind": "direct_response_start",
"request_id": "<the same id>",
"content_type": "application/octet-stream | image/fits | …",
"total_size": 12345,
"metadata": { /* request-type-specific */ }
}
total_size lets the receiver allocate ahead. content_type
signals how to interpret the chunks.
Response chunk
{
"kind": "direct_response_chunk",
"request_id": "<the same id>",
"data_b64": "<base64-encoded chunk>"
}
Chunk size is server-decided. The receiver concatenates chunks in the order they arrive.
Response end
{
"kind": "direct_response_end",
"request_id": "<the same id>"
}
Indicates a successful end of stream. Reassemble the chunks into
the final payload of declared content_type.
Error
{
"kind": "direct_error",
"request_id": "<the same id>",
"code": "invalid | forbidden | not_found | conflict | internal",
"message": "<human-readable>",
"details": { /* */ }
}
If you receive a direct_error at any point, the request has
failed — discard any chunks received so far.
Patterns
Reassembling a response
const chunks: Uint8Array[] = [];
let contentType = "";
socket.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.kind === "direct_response_start" && msg.request_id === myId) {
contentType = msg.content_type;
chunks.length = 0;
}
if (msg.kind === "direct_response_chunk" && msg.request_id === myId) {
chunks.push(Uint8Array.from(atob(msg.data_b64), c => c.charCodeAt(0)));
}
if (msg.kind === "direct_response_end" && msg.request_id === myId) {
const total = chunks.reduce((sum, c) => sum + c.length, 0);
const payload = new Uint8Array(total);
let offset = 0;
for (const c of chunks) { payload.set(c, offset); offset += c.length; }
// Use `payload` with `contentType`
}
if (msg.kind === "direct_error" && msg.request_id === myId) {
// Handle error
}
};
Reference
The direct protocol message types live at
packages/py/skynet-sdk/skynet_sdk/schemas/ws_protocol.py
(DirectRequestMessage, DirectResponseStartMessage,
DirectResponseChunkMessage, DirectResponseEndMessage,
DirectErrorMessage).