Browser and platform endpoint clients
Implement the experimental WebSocket auth, endpoint execution, subscription, error, revocation, and reconnect contract without assuming an SDK.
A controlled browser, mobile, or edge integration can call an operator-published NYXDB endpoint directly over WebSocket. This page documents the wire contract; it does not claim that NYXDB ships a browser, mobile, or edge SDK.
This is an Experimental protocol surface. The native server exposes
ws://, not wss://, and does not enforce a public zero-trust boundary. For
any untrusted client, place the listener behind reviewed TLS termination and
Origin/network policy, and start NYXDB with --require-auth. Without that
flag, a client can skip OP_AUTH and remain an implicit operator with raw SQL
and write access. Pin the engine revision and keep protocol conformance tests
with the client.
Connection sequence
Use one authenticated connection for sequential one-shot calls and a dedicated connection for each live subscription.
- Open a WebSocket and set
binaryType = "arraybuffer". - If wire-v2 negotiation is required, send capability operation
0x0Fbefore authentication. Endpoint execute/subscribe responses currently remain on v1 text payloads even on a negotiated connection. - Send
OP_AUTH(0x09) on this socket. - Wait for status
0x00and payloadauthenticated\n. - Send
OP_ENDPOINT_EXEC(0x0B) orOP_ENDPOINT_SUB(0x0C). - Correlate ordinary responses with the 16-byte request ID.
- On reconnect, repeat authentication before issuing the endpoint operation.
Authentication is connection-scoped. A token accepted on one socket does not authenticate a pool, another tab, or the next subscription socket.
Authenticate once per fresh socket. OP_AUTH is not a logout or principal
replacement operation: if a re-auth attempt fails, the connection keeps its
previous principal. Close the socket and open a new one whenever the provider,
token, or principal changes.
Outer frame
Each WebSocket binary message contains one complete engine frame. Structural lengths use network byte order.
Request := op:u8 request_id:16 payload_len:u32be payload[payload_len]
Response := request_id:16 status:u8 payload_len:u32be payload[payload_len]The browser can construct a request without a client library:
const utf8 = new TextEncoder();
function encodeRequest(op, payloadText) {
const payload = utf8.encode(payloadText);
const requestId = crypto.getRandomValues(new Uint8Array(16));
const frame = new Uint8Array(1 + 16 + 4 + payload.length);
frame[0] = op;
frame.set(requestId, 1);
new DataView(frame.buffer).setUint32(17, payload.length, false);
frame.set(payload, 21);
return { requestId, frame };
}This helper shows layout only. A production integration must also validate response lengths, cap allocations, compare request IDs, reject unknown statuses, bound all waits, and close on a malformed or contradictory frame.
Authenticate an application user
The end-user auth payload is the provider name, one NUL byte, and the opaque token:
OP_AUTH value := 0x09
OP_AUTH payload := provider_name "\0" tokenIf exactly one provider is registered, an empty provider name selects it. Name the provider explicitly when the client contract should remain stable as more providers are added.
const auth = encodeRequest(0x09, `app_sessions\0${token}`);
socket.send(auth.frame);A successful response has status 0x00 and body authenticated\n. Every
provider miss, ambiguous provider, invalid token, zero-or-many lookup result,
or provider execution failure is deliberately collapsed to status 0x01 with
body auth failed.
Operator authentication uses a different payload shape,
\0system\0user\0password. Do not send that shape from an application-user
client.
The provider referenced by an endpoint definition validates its claims schema; it is not an invocation-affinity check. A session admitted by another provider can call that endpoint when it supplies compatible claim names and types. The stored endpoint predicate must enforce every tenant and visibility boundary.
Execute a published query
The execution payload begins with an endpoint name followed by zero or more NUL-delimited client parameters in declaration order:
OP_ENDPOINT_EXEC value := 0x0B
OP_ENDPOINT_EXEC payload := endpoint_name ( "\0" parameter )*const call = encodeRequest(0x0b, `my_fills\0BTC-USD`);
socket.send(call.frame);Status 0x00 carries a v1 TSV header and rows, including after wire-v2
negotiation. Parameters are text on this wire path and are validated against
the endpoint signature. The v1 parameter form cannot distinguish a SQL NULL
from a text value.
Send client parameters only. Claims declared by the endpoint are appended by the server from the authenticated row. An extra value does not override a claim; it makes the parameter shape invalid.
The same operation can invoke a parameterized logical view by name or relation
UUID. A view requires an application read grant for the current role claim.
An endpoint is the pre-authorized bundle and does not consult the view grant
catalog.
Endpoint names resolve before logical-view names. A collision therefore selects the endpoint and its pre-authorized behavior, not the view grant the client may expect. Keep published names collision-free. Use the relation UUID when the client must target a logical view unambiguously.
Subscribe to a published query
OP_ENDPOINT_SUB uses the same payload shape:
OP_ENDPOINT_SUB value := 0x0C
OP_ENDPOINT_SUB payload := endpoint_name ( "\0" parameter )*The target endpoint must use AS STREAM SELECT. A one-shot endpoint returns
invalid parameter when used with the subscription operation. A granted view
can also be subscribed through this operation when the role has subscribe.
The response sequence uses these statuses:
| Status | Value | Client action |
|---|---|---|
| OK | 0x00 | Accept the header or row payload required by the current v1 stream shape. |
| Snapshot | 0x02 | Replace the local replica with the complete starting result. |
| Delta | 0x03 | Apply validated positional mutations in order. |
| Error | 0x01 | Treat the subscription as terminal. |
Do not assume a single row-only shape for every stream. The maintained stream contract can carry a snapshot plus positional deltas. Validate every row and index before applying it; on an impossible delta, close and rebuild from a new subscription rather than guessing state.
Sanitized application errors
Application-user connections receive stable short errors instead of schema or SQL diagnostics.
| Error body | Meaning | Recommended action |
|---|---|---|
endpoint not found | No matching endpoint or invocable view/UUID | Treat as a deployment or version mismatch; do not loop-retry. |
auth failed | The provider or token could not produce a valid claims row | Close the socket. Refresh or replace the credential, then authenticate exactly once on a fresh connection; failed re-auth does not clear a prior principal. |
auth revoked | A live gate removed the session or its provider was dropped | Stop using the credential. Obtain a new token before reconnecting. |
not authorized | The role lacks read or subscribe on an invoked view | Correct the application grant; repeating the same call cannot help. |
limit exceeded: <dimension> | Static or query-driven endpoint governance denied or terminated the work | Respect the named quota. Do not create a reconnect storm. |
invalid parameter | Arity, type, invocation shape, or endpoint mode is wrong | Fix the client request. |
endpoint failed | A sanitized engine or channel failure | Correlate with operator-side system.query_log; retry only under an explicit read/reconciliation policy. |
operation not permitted | The authenticated end-user socket attempted a non-whitelisted operation | Fix the client. End users cannot run raw SQL or block ingest. |
The server may also return bounded admission errors such as
server busy: query admission rejected or server busy: query admission timed out. Those are back-pressure signals: use jittered bounded backoff and an
overall deadline.
Revocation frames
A streaming auth gate can revoke an idle connection without waiting for a new request. The server sends an unsolicited error frame with:
- status
0x01; - body
auth revoked; and - a 16-byte all-zero request ID;
then drops the transport. On a live subscription, revocation is its terminal error and the ordinary detach path releases the subscriber and quota state.
Updating claims in a streaming auth-gate row is not a live rebind. An existing
subscription keeps its original bindings, user_id, tenant, and role. For an
identity or authorization change, remove the old token to trigger revocation,
issue a new token, and require a fresh connection and subscription.
Your dispatcher must therefore accept a valid server control frame that does not match an in-flight request only for this documented zero-ID revocation case. Any other unmatched response is protocol-fatal.
Reconnect and reconciliation
The current engine does not issue a durable resume cursor.
| Termination | Reconnect policy |
|---|---|
| Clean client cancellation | Open a new socket only when the application wants a new subscription. |
| Transport loss or server restart | Back off, reconnect, authenticate again, subscribe again, and replace state with the new snapshot. |
auth revoked or auth failed | Do not reconnect with the same credential until the application has refreshed it. |
not authorized or invalid parameter | Do not retry until configuration or request shape changes. |
| Rate-limit termination | Follow the quota policy; repeated immediate reconnects can create further denials. |
An endpoint replace or drop also does not terminate an established subscription. The old query and bindings continue, and a drop removes its governor/metering state. Treat an operator-announced endpoint policy change as a drain boundary: close the old subscription and establish a fresh one only after the reviewed definition is active.
Persist the last application state only if the application can reconcile it against a replacement snapshot. Never append a new snapshot to an abandoned replica as though it were a continuation.
Security checklist for platform clients
- Start NYXDB with
--require-authfor every browser-facing or otherwise untrusted listener. Treat a missing flag as a launch-blocking error. - Use a reviewed
wss://edge that terminates TLS before forwarding to the trusted-networkws://listener. - Restrict which origins and networks can reach that edge; the engine transport does not provide a public Origin allow-list contract.
- Keep tokens out of URLs and logs. They belong only in the binary auth frame.
- Never change principal or token by re-authenticating an existing socket; close it and authenticate once on a fresh connection.
- Configure every provider to return a stable, non-secret
user_id; otherwise the bearer token becomes the fallback identity and can appear in subscriber and governance telemetry. - Do not treat an endpoint's
USINGprovider as runtime authorization affinity; validate every required claim and enforce tenant visibility in the stored SQL. - Keep endpoint and view names collision-free, or invoke the intended logical view by UUID.
- Use one fresh random 16-byte request ID per operation.
- Set connect, auth, response, idle, and total-operation deadlines.
- Cap frame lengths before allocating and reject text WebSocket messages.
- Authenticate every newly opened socket before the endpoint operation.
- Pin the engine revision and test auth, execution, snapshot/delta handling, revocation, quota errors, and reconnect behavior before an upgrade.
For the SQL objects behind this flow, see Application access SQL. For the complete frame catalog, see Experimental wire protocol.
JDBC driver
Build and use the experimental Java 17 NYXDB JDBC driver, including URLs, typed v2 results, metadata, limitations, retries, and version compatibility.
Experimental wire protocol
The current NYXDB raw TCP and WebSocket envelope, operations, result statuses, v1/v2 negotiation, streaming termination, and binary block ingest.