Endpoints
Everything is served from the public originhttps://joyride.exchange, with no separate API host.
The trading WebSocket speaks JSON-RPC 2.0. Every request carries
"jsonrpc": "2.0", an id, a method, and optional params. Once a request parses, its response echoes the id; malformed JSON that cannot be parsed returns a parse error with "id": null. Server pushes arrive as "method": "subscription" notifications with no id.
Authentication
Identity is an ed25519 keypair. There are no API keys. An account belongs to a Solana wallet public key, created in the app at joyride.exchange, and that keypair signs in over the API, from the CLI, or in the app. Sign-in uses three methods on the trading WebSocket.Request a nonce
Sign and authenticate
The message is built byte for byte as below: a blank line between the greeting and theWallet: line, a single newline before Nonce:.
wallet and the nonce inside message and rejects any deviation, including a trailing newline or reordered lines.
roles lists what the account may do. An account the exchange has designated as a market maker carries rfq_quoter; every other account’s array is empty. A wallet with no account is rejected with 1001. Signing in never creates an account.
Resume on reconnect
A storedsession_token authenticates a new socket without a fresh signature. Resume checks that the wallet still owns the account_id in the token and returns roles from live account state. That response is a snapshot: every RFQ action checks the live role, and delivery of new open requests rechecks it for each subscribed socket. A revocation therefore blocks maker calls and stops new open requests immediately on an existing socket; a grant immediately lets that socket subscribe and call maker methods. A revoked quoter remains subscribed to targeted lifecycle and fill events for quotes where it is already the counterparty.
1001 on resume means the cached token is no longer valid; the full nonce, sign, auth sequence is required. public/session_resume on a socket already authenticated as the same account refreshes that session and evicts nothing.
Session token
The session token is the JWT that
public/auth returns after the wallet signs the nonce. It authenticates the trading WebSocket and the query API; a quote or an acceptance additionally carries a wallet signature, which the token cannot produce.
Sessions per account
RFQ subscriptions are per socket; subscribing one session does not subscribe the account’s other sessions. A client normally needs one authenticated socket and must subscribe on it after connecting or reconnecting. A client closed with
4001 and reason session_limit must not reconnect automatically: the reconnect evicts the newer session and the two clients evict each other in a loop. Reconnect only on an operator action.
Keepalive
The server never pings. The client sends a protocol-level WebSocket ping at least every 30 seconds to keep intermediaries from dropping a quiet flow, and the gateway answers each with a pong. Protocol ping and pong frames do not refresh the unauthenticated idle deadline. Until authentication succeeds, send a text or binary JSON-RPC message within each 60-second interval or the gateway reaps the socket with error
1001; authenticate promptly rather than using public calls only as a keepalive.
Rate budget
The budget is per account, so three sessions share it. A request consumes one unit when it reaches the limiter, even if later processing fails. For a maker,
rfq.maker.poll, rfq.maker.respond, and rfq.maker.decline are charged; rfq.maker.subscribe is not.
Error codes
Errors use the JSON-RPC envelope{ "code", "message", "data"? }. Close codes are WebSocket close frames, not JSON-RPC errors.
An
rfq.* call made before public/auth or public/session_resume returns
1200 with data.error: "RFQ_WRONG_ROLE" and data.retryable: false, not
1001. Do not repeat the unchanged call on the unauthenticated socket. First
authenticate or resume the session, then issue the RFQ call again as a new
attempt.
The 1200 frame:
-32700 parse, -32600 invalid request, -32601 method not found, -32602 invalid params, -32603 internal) follow the JSON-RPC 2.0 specification. A parse failure returns "id": null; once parsing succeeds, errors use the request’s id. -32602 is returned only for malformed public/* params. On an rfq.* method, malformed or unknown params are 1200 with data.error RFQ_INVALID_PARAMS, and an instrument symbol that is not listed or not open is 1200 with data.error RFQ_INVALID_INSTRUMENT.
Support
Support for live makers is support@joyride.exchange. A report that includes theoperation_id from a 1200 error, the rfq_id or quote_id in question, and the UTC time of the event can be traced directly. operation_id is a per-process counter (rfq-<n>) that restarts when the gateway restarts, so the UTC time is what makes it unique.
Next: Becoming an RFQ Quoter