Streaming
A browser cannot put Authorization on a WebSocket upgrade, and a token in the query string ends up in access logs. So the stream authenticates in two steps.
1. Request a connection ticket
Section titled “1. Request a connection ticket”POST /open/v1/conversation/ws-ticketAuthorization: Bearer <access token>→ { "ticket": "…", "expiresIn": 30 }
The ticket is single-use and expires in 30 seconds. Fetch a fresh one for every connection, including reconnects and resumes.
2. Connect and send the ticket
Section titled “2. Connect and send the ticket”wss://api.harperharbor.com/open/v1/conversation/ws?protocolVersion=2Resume variants add resumeStreamId=…&lastEventId=… (exact) or mode=tryResume&conversationId=… (after a page reload).
The first frame you send must be:
{ "type": "auth", "ticket": "<ticket>" }The server replies with an SSE-formatted frame and only then accepts chat frames:
event: readydata: {"accountBound":true}On failure it sends event: error with {"code":"unauthorized","retryable":false} and closes. Do not retry the connection with the same credentials. Resolve the authorization failure before requesting another ticket.
Frames
Section titled “Frames”C→S send · rewrite · continue · backward · stopS→C streamMeta · thinking · answer · messageMeta · operationStatus · done · error · resumeUnavailable · sessionExpired · …Do not include an account identifier in a payload. The connection is already bound to an account by the ticket; a payload field is ignored. Ignore unknown S→C events so your client can handle new events without failing.
Reconnecting
Section titled “Reconnecting”After a disconnection, request a new ticket and reconnect with the resume parameters. If the server answers resumeUnavailable, fall back to GET /conversation/replay for the latest reply and GET /conversation/operations for the state of anything in flight.