> ## Documentation Index
> Fetch the complete documentation index at: https://docs.joyride.exchange/llms.txt
> Use this file to discover all available pages before exploring further.

# Connectivity for Bots

> Endpoints, wallet-signed authentication, session limits, keepalive, rate budgets, and error codes for the trading WebSocket

## Endpoints

Everything is served from the public origin `https://joyride.exchange`, with no separate API host.

| Surface                                                                                      | URL                                        | Auth                                                          |
| -------------------------------------------------------------------------------------------- | ------------------------------------------ | ------------------------------------------------------------- |
| Trading WebSocket (RFQ)                                                                      | `wss://joyride.exchange/api/client`        | SIWS on the socket (`public/auth` or `public/session_resume`) |
| Oracle WebSocket (spot price)                                                                | `wss://joyride.exchange/api/oracle`        | None                                                          |
| Query HTTP (self-scoped reads: balances, deposits, withdrawals, RFQ fills, vault subaccount) | `https://joyride.exchange/api/query/*`     | `Authorization: Bearer <session_token>`                       |
| Public market HTTP (config, instruments, mark IV)                                            | `https://joyride.exchange/api/v1/market/*` | None                                                          |

The trading WebSocket speaks JSON-RPC 2.0. Every request carries `"jsonrpc": "2.0"`, an `id`, a `method`, and optional `params`; every response echoes the `id`. Server pushes arrive as `"method": "subscription"` notifications with no `id`.

## Authentication

Identity is an ed25519 keypair. There are no API keys. An account is provisioned against a Solana wallet public key (see the venue pages linked at the end), and that keypair signs in from a bot, the CLI, or the web app.

Sign-in uses three methods on the trading WebSocket.

| Method                  | Request                                                      | Response                                                                                          | Errors                                                                           |
| ----------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
| `public/get_nonce`      | `{ "wallet": "<base58 pubkey>" }`                            | `{ "nonce": "<base58>" }`, single use, valid for 300 seconds                                      | `-32602` invalid params                                                          |
| `public/auth`           | `{ "wallet", "signature": "<base58 signature>", "message" }` | `{ "wallet", "account_id", "roles", "session_token" }`                                            | `1001` bad signature, message mismatch, nonce expired or reused, unknown account |
| `public/session_resume` | `{ "session_token": "<jwt>" }`                               | `{ "wallet", "account_id", "roles" }`; the socket is now authenticated and no new token is issued | `1001` token expired or invalid, unknown or reassigned account                   |

### Request a nonce

```json theme={null}
{"jsonrpc":"2.0","id":1,"method":"public/get_nonce","params":{"wallet":"7Gx2XvMk9Z1sZbY3qJ8pKcWnD4tFhRr6sA2eLmN5uVwQ"}}
```

```json theme={null}
{"jsonrpc":"2.0","id":1,"result":{"nonce":"3fA9kLm2PqRs7TuVwXyZ1bCdEfGh4JkL"}}
```

### 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:`.

```text theme={null}
Sign in to Joyride

Wallet: {wallet}
Nonce: {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.

```json theme={null}
{"jsonrpc":"2.0","id":2,"method":"public/auth","params":{"wallet":"7Gx2XvMk9Z1sZbY3qJ8pKcWnD4tFhRr6sA2eLmN5uVwQ","signature":"<base58 signature>","message":"Sign in to Joyride\n\nWallet: 7Gx2XvMk9Z1sZbY3qJ8pKcWnD4tFhRr6sA2eLmN5uVwQ\nNonce: 3fA9kLm2PqRs7TuVwXyZ1bCdEfGh4JkL"}}
```

```json theme={null}
{"jsonrpc":"2.0","id":2,"result":{"wallet":"7Gx2XvMk9Z1sZbY3qJ8pKcWnD4tFhRr6sA2eLmN5uVwQ","account_id":1042,"roles":["rfq_quoter"],"session_token":"<jwt>"}}
```

`roles` lists what the account may do. A quoter's array contains `rfq_quoter`; a taker's is empty. A wallet with no provisioned 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 recomputes `roles` from live account state, so a role change takes effect on the next resume.

```json theme={null}
{"jsonrpc":"2.0","id":3,"method":"public/session_resume","params":{"session_token":"<jwt>"}}
```

```json theme={null}
{"jsonrpc":"2.0","id":3,"result":{"wallet":"7Gx2XvMk9Z1sZbY3qJ8pKcWnD4tFhRr6sA2eLmN5uVwQ","account_id":1042,"roles":["rfq_quoter"]}}
```

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

| Parameter    | Value                                                                                                                    | Applies to                      |
| ------------ | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------- |
| Format       | JWT, HS256, issued by the trading gateway                                                                                | `session_token`                 |
| Lifetime     | 24 hours from issue (`exp` claim)                                                                                        | `session_token` TTL             |
| Revocation   | None; a token stays valid until `exp`                                                                                    | `session_token`                 |
| Invalidation | Rotate the wallet key: a token whose wallet no longer maps to the account fails `public/session_resume` and Bearer reads | `session_token`                 |
| Storage      | Keep the ed25519 secret and the token in a secrets manager, never in a repo, image, or log line                          | Wallet keypair, `session_token` |

The session token is the JWT that `public/auth` returns after the wallet signs the nonce. There is no revocation list, so a leaked token can trade the account until its `exp`. Joyride never holds a maker's private key, so the remedy is on the maker's side: generate a new keypair and email [support@joyride.exchange](mailto:support@joyride.exchange) to bind the account to the new public key. Once the account is rebound, tokens issued to the old key are refused on `public/session_resume`, and any connection still open under one ends when it disconnects.

## Sessions per account

| Parameter                         | Value                                                                          | Applies to        |
| --------------------------------- | ------------------------------------------------------------------------------ | ----------------- |
| Concurrent authenticated sessions | 3 per account                                                                  | Trading WebSocket |
| Eviction on a 4th session         | Oldest session closed with WebSocket close code `4001`, reason `session_limit` | Trading WebSocket |
| Delivery of account pushes        | Every session receives every RFQ event for the account                         | Trading WebSocket |

Every session receives every account-scoped push, so a bot needs one authenticated socket. 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

| Parameter                 | Value                               | Applies to        |
| ------------------------- | ----------------------------------- | ----------------- |
| Server-initiated ping     | Never                               | Trading WebSocket |
| Unauthenticated idle reap | 60 seconds without an inbound frame | Trading WebSocket |

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. An unauthenticated socket that sends nothing for 60 seconds is reaped with error `1001`.

## Rate budget

| Parameter      | Value                                                                                                                               | Applies to                                                                  |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| Request budget | 100 requests per second per account                                                                                                 | Every request the account sends over its trading sessions, counted together |
| Over budget    | Error `1200` with `data.error` `RFQ_RATE_LIMITED` for `rfq.maker.*` methods, error `1007` for other methods; both `retryable: true` | The rejected request only; the socket stays open                            |

The budget is per account, so three sessions share it. Each request counts as one, whether it is a poll, a response, or a decline.

```json theme={null}
{"jsonrpc":"2.0","id":4,"error":{"code":1007,"message":"Rate limit exceeded","data":{"retryable":true}}}
```

The window resets at the next second. The rejected request was never admitted and 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.

| Code           | Surface                                   | Meaning                                                                                                                                                                                                                        | Client action                                                                   |
| -------------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------- |
| `1001`         | Trading WebSocket                         | Not authenticated: bad signature, expired nonce, expired or invalid token, unknown or reassigned account, or the 60-second idle reap                                                                                           | Discard any cached token, run `public/get_nonce` then `public/auth`, and resend |
| `1007`         | Trading WebSocket                         | Rate limit exceeded; the 100 requests per second account budget is spent                                                                                                                                                       | Retry after backoff; nothing was admitted                                       |
| `1200`         | Trading WebSocket                         | RFQ error; `data.error` carries the RFQ code (for example `RFQ_NOT_READY`, or `RFQ_RATE_LIMITED` when an `rfq.maker.*` call exceeds the same budget), plus `operation_id`, `retryable`, and `rfq_id` or `quote_id` where known | Branch on `data.error`; retry only when `data.retryable` is `true`              |
| `4001` (close) | Trading WebSocket, reason `session_limit` | A fourth session evicted this one                                                                                                                                                                                              | Do not auto-reconnect; alert and wait for an operator                           |

The `1200` frame:

```json theme={null}
{"jsonrpc":"2.0","id":6,"error":{"code":1200,"message":"maker is not ready to quote","data":{"error":"RFQ_NOT_READY","message":"maker is not ready to quote","operation_id":"op_01J9X4M2QK","retryable":true,"rfq_id":"0192f1a3b4c5d6e7f8091a2b3c4d5e6f"}}}
```

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. An unknown instrument symbol in a request is rejected as `-32602`.

## Support

Support for live makers is [support@joyride.exchange](mailto: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.

Next: [Becoming an RFQ Quoter](/market-makers/becoming-a-quoter)
