Skip to main content

Endpoints

Everything is served from the public origin https://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 the Wallet: line, a single newline before Nonce:.
The signature is ed25519 over the UTF-8 bytes of that message, base58-encoded (64 bytes). The request carries the wallet, the signature, and the exact message signed. The server rebuilds the message from 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 stored session_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.
A 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.
The fixed one-second window begins with the account’s first charged request; it is not aligned to a wall-clock second. After the window has elapsed, the rejected request can be resent as is.

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:
The standard JSON-RPC codes (-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 the operation_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