Protocol Reference
The spectral-bridge protocol is an open specification. Any conforming relay client interoperates with any conforming relay server. This page documents all three parts of the contract.
1. Adapter Contract
An adapter is any process that translates a local target into an OpenAI-compatible HTTP server, acting as a semantic boundary: only the chatbot's text response is forwarded. No raw network traffic from the internal host crosses the boundary. The relay client forwards requests to it over localhost.
Required Endpoint
POST /v1/chat/completions
The adapter must implement this endpoint. No other endpoints are required.
Request format
{"messages": [{ "role": "user", "content": "string" },{ "role": "assistant", "content": "string" }]}
messages is an ordered list of prior turns followed by the current user message. The adapter may use the full history or only the last message, depending on whether the underlying target is stateful.
model is intentionally omitted. The OpenAI spec lists it as required, but many implementations treat it as optional.
Response format
{"choices": [{"message": {"role": "assistant","content": "string"}}]}
Additional fields (id, model, usage, etc.) are optional and ignored by the relay client.
Error responses
{"error": {"message": "human-readable description"}}
Return a standard 4xx or 5xx status code. The relay client propagates the status and body to the relay server unchanged.
Concurrency
The adapter must handle concurrent requests. The relay client may forward multiple requests simultaneously, one per active conversation turn.
2. Relay Client Protocol
The relay client maintains a persistent, bidirectional WebSocket connection to the relay server. The connection is initiated outbound by the client — no inbound ports or firewall rules are required. Once established, the server pushes request frames down to the client, and the client pushes response frames back up.
Connection
Connect to:
Pass the API key as a bearer token during the WebSocket handshake:
A conforming relay client must use TLS (wss://). Plain ws:// is not permitted for conforming deployments. Reference implementations may support plain ws:// when the operator explicitly opts in (for example a --insecure-relay flag). That mode is for local development and debugging only and must not be used where relay traffic could leave a trusted network.
On successful authentication, the server sends a confirmation frame before any request frames:
{ "type": "connected" }
The client should surface this to the user (for example, print to terminal).
If authentication fails, the server closes with code 4001. The client must not retry with the same key without user intervention.
Message format
All frames carry UTF-8 encoded JSON.
Inbound: request frame (server → client)
{"type": "request","request_id": "<opaque string>","payload": {"method": "POST","headers": {},"body": {}}}
request_id is assigned by the relay server. It is opaque to the client — treat it as a correlation token and echo it back unchanged in the response frame.
The relay client always forwards requests to the adapter's POST /v1/chat/completions endpoint.
Outbound: response frame (client → server)
{"type": "response","request_id": "<opaque string>","payload": {"status": 200,"headers": { "content-type": "application/json" },"body": {}}}
request_id must match the corresponding request frame exactly.
Outbound: error frame (client → server)
If the client cannot forward the request to the local adapter (for example, the adapter is down), it sends an error response rather than leaving the request unanswered:
{"type": "response","request_id": "<opaque string>","payload": {"status": 503,"headers": { "content-type": "application/json" },"body": { "error": { "message": "Adapter unavailable" } }}}
Concurrency
The client must handle request frames concurrently. Upon receiving a request frame, the client should immediately dispatch it to the local adapter and begin listening for the next frame — not wait for the adapter to respond before reading again. Response frames may be sent back in any order; request_id provides correlation.
Keepalive
The client sends a WebSocket ping every 30 seconds. If no pong is received within 10 seconds, the connection is considered stale and the client closes it and reconnects.
Reconnection
The client reconnects automatically on any disconnect — whether caused by a network blip, relay server restart, or the client process being stopped and restarted later. Reconnection uses truncated exponential backoff:
| Attempt | Delay |
|---|---|
| 1 | 1 s |
| 2 | 2 s |
| 3 | 4 s |
| 4 | 8 s |
| 5+ | 30 s |
The client presents the same API key on reconnect. The relay server resolves the key to the same tunnel identity — the caller's endpoint does not change between reconnects.
The client must not replay or resubmit requests that were in-flight at the time of disconnect. Those requests are the relay server's responsibility to time out.
3. Relay Server
Only the /connect WebSocket endpoint is mandatory — it is the protocol boundary that any conforming relay client interoperates with. How the server exposes a forwarding endpoint to its callers is an implementation concern; the sections below describe three progressively richer designs.
Required: /connect
On connection:
- Read
Authorization: Bearer <api-key>. If absent or invalid, close with code4001. - Validate the key. The same key must always authenticate to the same connection slot across reconnects.
- Register the active WebSocket connection.
- Send
{ "type": "connected" }before any request frames.
Reconnection behavior:
- On reconnect with the same key: upsert the registered connection. Do not reject known keys.
- On concurrent connections with the same key: accept the newer connection, close the older one with a clean WebSocket close frame.
Request forwarding:
- Push request frames to the active connection.
- Await the matching response frame by
request_id. - If no active connection exists: return an error to the caller immediately (suggested: HTTP
503). - If no response frame is received within 30 seconds: time out and return an error to the caller (suggested: HTTP
504).
Suggested: Simple scenario
A single spectral-bridge client connects to the relay. The API key is a static secret (for example, set via an API_KEY environment variable). Callers forward requests via:
Suggested: Multi-client scenario
Multiple spectral-bridge clients connect simultaneously, each with its own API key. Each key maps to a relay ID — an opaque identifier used by the platform to address a specific tunnel. Callers target a specific client via:
This endpoint must not be publicly reachable. The server maintains:
On forwarded request: resolve relay-id to active connection, push the request frame, await response.
Suggested: Dynamic multi-client scenario
Extends the multi-client scenario with a provisioning endpoint that creates relay entries at runtime:
The caller supplies a relay ID; the server generates and returns an API key. This allows relay slots to be created on demand without redeploying or reconfiguring the server.
Authentication on the HTTP forwarding surface
While the WebSocket connection is authenticated with Authorization: Bearer, the protocol does not specify how callers authenticate to the relay server's HTTP forwarding surface. That is intentional: platforms will use different gateways, networks, and credential models.
What is normative is that operators treat unauthenticated access to a forwarding URL as equivalent to full use of the attached adapter — anyone who can POST the completion endpoint can drive the private target. Production deployments must enforce authentication (or equivalent access control: private network only, mutual TLS, API gateway, etc.) on any HTTP path that reaches the relay's forwarder.