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

# Agent Quickstart

> Connect Claude Code, Cursor, Codex, or any MCP client to Joyride's paper-trading environment in 5 commands.

## Who this is for

Use this guide if you want to connect an AI agent to Joyride quickly without building against the raw HTTP or WebSocket APIs yourself.

**No API key required.** Create your account in the web app at [joyride.exchange](https://joyride.exchange) (referral code + terms attestation), then sign in from the CLI with the same wallet — the CLI is login-only. Invite/referral codes are redeemed in the web app, not the CLI.

**Current mode:** Paper trading only. No real money is at risk.

## Prerequisites

* Node.js >= 20.0.0
* A local MCP client such as Claude Code, Cursor, Codex, or VS Code Copilot

## The 5-command flow

```bash theme={null}
# 1. Install the Joyride CLI (ships the MCP server too)
npm install -g joyride-cli

# 2. Create a wallet
joyride setup

# 3. Authenticate (stores a JWT at ~/.joyride/session.json)
joyride login

# 4. Configure your MCP client — pick one:
joyride mcp install --client claude    # Claude Code
joyride mcp install --client cursor    # Cursor
joyride mcp install --client codex     # Codex

# 5. Restart your MCP client and start trading
```

That's it. Detailed steps below.

## 1. Install the CLI

```bash theme={null}
npm install -g joyride-cli
```

This installs the `joyride` command and the bundled MCP server. Verify:

```bash theme={null}
joyride --help
```

## 2. Create or import a wallet

Run the setup wizard:

```bash theme={null}
joyride setup
```

Choose one of:

* `Create new wallet` for a fresh paper-trading identity
* `Import existing wallet` if you already have a Solana keypair

The wizard writes:

* config to `~/.joyride/config.toml`
* an encrypted keystore to `~/.joyride/wallets/`

Back up the private key shown during wallet creation. The keystore is encrypted locally, but the private key display is your recovery path.

## 3. Authenticate

```bash theme={null}
joyride login
```

This prompts for your keystore passphrase, performs a SIWS (Sign-In With Solana) handshake over the core trading WebSocket, and stores the resulting JWT at `~/.joyride/session.json`. Signing in with a wallet that has no Joyride account fails with a "sign up at joyride.exchange" message — create the account in the web app first.

The **server** sets how long a session lasts — run `joyride auth status` to see its exact expiry rather than assuming a fixed lifetime. You only need to re-run `joyride login` once it expires. On a shared or untrusted machine, run `joyride logout` when you are done.

## 4. Configure your MCP client

### Auto-install (recommended)

Pick the command for your client:

```bash theme={null}
joyride mcp install --client claude     # Claude Code
joyride mcp install --client cursor     # Cursor
joyride mcp install --client codex      # Codex
```

Each command writes the correct config for you. For Cursor, existing MCP servers in `~/.cursor/mcp.json` are preserved.

### Manual config (if your client isn't listed)

The MCP server runs over stdio. Any MCP-compatible client can launch it using the same command and args:

```json theme={null}
{
  "mcpServers": {
    "joyride": {
      "command": "joyride",
      "args": ["mcp", "serve"],
      "env": {
        "JOYRIDE_HTTP_URL": "https://joyride.exchange"
      }
    }
  }
}
```

For VS Code Copilot, put this at `.vscode/mcp.json` with the top-level key `servers` instead of `mcpServers`.

The MCP server reads the JWT from `~/.joyride/session.json` (written by `joyride login`) — no keypair path or passphrase is needed in the MCP config.

## 5. Restart and verify

Restart your MCP client. In Claude Code, run `/mcp` — you should see:

```text theme={null}
joyride · ✔ connected
```

## Try a first session

Start with read-only prompts:

* `List available SOL instruments`
* `Show my balance`
* `Get a quote for <any instrument from the list>`

Then try a paper-trading prompt:

* `Place a limit buy for 1 contract of <instrument> at $4.00`

Don't hard-code instrument IDs — Joyride's 0DTE contracts refresh daily. Always ask the agent to list current instruments first.

<Note>
  **Date labels may look like "tomorrow."** Rounds are 24 hours and settle at 08:00 UTC, so after 08:00 UTC the active instruments carry *tomorrow's* date label. Each instrument response includes `expires_at` (ISO-8601) and `seconds_to_expiry` so your agent can check programmatically. See [Trading Hours and Rollover](./exchange/trading-hours) for the full schedule.
</Note>

## Supported clients

| Client          | Auto-install                          | Status                   |
| --------------- | ------------------------------------- | ------------------------ |
| Claude Code     | `joyride mcp install --client claude` | Supported and tested     |
| Cursor          | `joyride mcp install --client cursor` | Supported and tested     |
| Codex           | `joyride mcp install --client codex`  | Supported and tested     |
| VS Code Copilot | Manual config                         | Configuration documented |

## Direct API integration (no MCP)

If your agent framework doesn't use MCP (LangGraph, AutoGen, CrewAI, custom Python/TypeScript orchestrators, n8n, etc.), you can use the JWT from `joyride login` directly with the REST and WebSocket APIs.

<Warning>
  The JWT in `session.json` expires (check `session.expiresAt`, Unix ms). If you get HTTP 401, the token has expired — run `joyride login` to refresh. Long-running agents should check `expiresAt` and refresh proactively.
</Warning>

### TypeScript

```typescript theme={null}
// Save as .mjs or wrap in an async function
import fs from "node:fs";
import os from "node:os";
import path from "node:path";

// Read and validate the session
const session = JSON.parse(
  fs.readFileSync(path.join(os.homedir(), ".joyride/session.json"), "utf-8")
);
if (Date.now() >= session.expiresAt) {
  throw new Error("Session expired. Run `joyride login` to refresh.");
}

// Read: fetch balances from the query API (values are decimal strings, 8dp)
const resp = await fetch("https://joyride.exchange/api/query/balances", {
  headers: { Authorization: `Bearer ${session.token}` },
});
if (resp.status === 401) throw new Error("Token expired. Run `joyride login`.");
const balances = await resp.json();
console.log("Balance:", balances.available, "USDC");

// Public: list instruments (no auth needed)
const instruments = await fetch("https://joyride.exchange/api/v1/market/instruments").then(r => r.json());
console.log("Active instruments:", instruments.length);
```

### Python

```python theme={null}
# pip install requests
import json, os, time, requests

session = json.load(open(os.path.expanduser("~/.joyride/session.json")))
if time.time() * 1000 >= session["expiresAt"]:
    raise RuntimeError("Session expired. Run `joyride login` to refresh.")
headers = {"Authorization": f"Bearer {session['token']}"}

# Fetch balances from the query API (values are decimal strings, 8dp)
balances = requests.get("https://joyride.exchange/api/query/balances", headers=headers).json()
print(f"Balance: ${balances['available']}")

# List instruments (no auth needed)
instruments = requests.get("https://joyride.exchange/api/v1/market/instruments").json()
print(f"Active instruments: {len(instruments)}")
```

### curl

```bash theme={null}
# requires jq (brew install jq / apt install jq)
TOKEN=$(jq -r .token ~/.joyride/session.json)
curl -H "Authorization: Bearer $TOKEN" https://joyride.exchange/api/query/balances
curl https://joyride.exchange/api/v1/market/instruments
```

### WebSocket trading

Reads are available over HTTP; order placement runs over the core trading WebSocket:

```json theme={null}
// 1. Connect to the core trading WS: wss://joyride.exchange/api/client
// 2. Resume session with your JWT:
{"jsonrpc":"2.0","id":1,"method":"public/session_resume","params":{"session_token":"<jwt>"}}
// 3. Place a limit buy. Prices and quantities are decimal strings (8dp);
//    client_order_id and nonce are required. The order outcome arrives as an
//    async order_ack / fill / reject notification.
{"jsonrpc":"2.0","id":2,"method":"private/buy","params":{"instrument_name":"SOL_USDC-27APR26-87-C","price":"0.50000000","quantity":"1.00000000","order_type":"limit","client_order_id":1,"nonce":1}}
```

The SDK (`@joyride/core`) handles `client_order_id`/`nonce` generation and ack correlation for you — prefer it over hand-rolling the wire.

## Going event-driven

The examples above use polling (request/response). For strategies that react to fills, stop-losses, or position changes in real-time, subscribe to WebSocket channels.

### CLI

```bash theme={null}
# Stream order book updates for an instrument
joyride watch book SOL_USDC-27APR26-87-C

# Stream the public trade feed
joyride watch trades SOL_USDC-27APR26-87-C

# Stream your fill notifications (requires joyride login)
joyride watch fills
```

Each event is one JSON line on stdout, pipeable to `jq` or your agent's event loop. (For spot price, use `joyride price SOL`.)

### Raw WebSocket

```json theme={null}
// On the market-data WS (wss://joyride.exchange/api/md): subscribe by symbol
{
  "jsonrpc": "2.0",
  "id": 10,
  "method": "public/subscribe",
  "params": {
    "instruments": ["SOL_USDC-27APR26-87-C"]
  }
}
```

Book snapshots/deltas and trades arrive as subscription notifications on the MD WS. Your own fills and settlement entries arrive on the core trading WS private stream after `public/session_resume`. Spot prices come from the standalone oracle WS (`wss://joyride.exchange/api/oracle`).

## Troubleshooting

### `Not authenticated. Run joyride login first.`

The MCP server could not find a valid session. Run `joyride login` in your terminal, then restart your MCP client.

### `Joyride: No config found`

Run `joyride setup` to create a wallet and initial config.

### `Wallet not configured`

Run `joyride wallet show`. If no wallet is listed, re-run `joyride setup` or create one explicitly with `joyride wallet create`.

### Instrument examples don't exist

Same-day instruments refresh daily. Always start by asking the agent to list available instruments instead of hard-coding IDs from an older session.

If you need the exact schedule, see [Trading Hours and Rollover](./exchange/trading-hours).

## Going event-driven

Polling endpoints in a loop works, but agents that react to fills, book updates, or spot prices in real time should subscribe to the event stream instead. There are two paths.

### Option A — `joyride watch` (recommended for agents)

The `joyride watch <channel>` command subscribes to a WebSocket channel and streams events as JSON lines (one event per line) to stdout. Pipe it into `jq`, `tee`, or your agent's stdin:

```bash theme={null}
# Public channels — no auth required
joyride watch book SOL_USDC-3MAR26-75-C
joyride watch trades SOL_USDC-3MAR26-75-C

# Private channels — require an active session (joyride login)
joyride watch fills
joyride watch settlements
```

Each line is a complete JSON event, so a downstream consumer can read line-by-line:

```bash theme={null}
joyride watch trades SOL_USDC-3MAR26-75-C | while IFS= read -r line; do
  echo "$line" | jq '{ts: .timestamp, px: .price, sz: .size}'
done
```

The CLI reconnects automatically on transient WebSocket disconnects and re-subscribes to the same channel, so long-running agents don't need to handle reconnect logic themselves. See [`joyride watch`](./sdk/cli#joyride-watch-channel-argument) for the full channel list, flags, and auth requirements.

### Option B — raw WebSocket

If you don't want to depend on the CLI, connect directly to the core market-data WS (`wss://joyride.exchange/api/md`) and send a JSON-RPC `public/subscribe` request with the instrument symbols.

```jsonc theme={null}
// Outbound: subscribe by symbol
{ "jsonrpc": "2.0", "id": 1, "method": "public/subscribe", "params": { "instruments": ["SOL_USDC-3MAR26-75-C"] } }

// Inbound: book snapshot/delta and trade notifications keyed by symbol
{ "jsonrpc": "2.0", "method": "subscription", "params": { "channel": "trades.SOL_USDC-3MAR26-75-C", "data": { /* trade */ } } }
```

Your own fills and settlement entries arrive on the core trading WS private stream after a SIWS-authenticated session (`joyride login`).

## Headless / agent flow (no interactive prompts)

Autonomous agents and CI containers don't have a TTY, so the `joyride setup` wizard can't prompt them. Two non-interactive paths are supported.

### Option A — `joyride setup --non-interactive`

Recommended when you want the standard config + encrypted keystore on disk.

```bash theme={null}
export JOYRIDE_KEYSTORE_PASSWORD="any-strong-passphrase"
joyride setup --non-interactive --new-wallet
```

Output is JSON on stdout, parseable by an agent:

```json theme={null}
{
  "ok": true,
  "configPath": "/home/agent/.joyride/config.toml",
  "wallet": {
    "address": "B9vwMeHguq...",
    "keystorePath": "/home/agent/.joyride/wallets/B9vwMeHguq....json",
    "privateKey": "ngn812Sjsq..."
  }
}
```

Capture `wallet.privateKey` immediately — it isn't stored in plaintext anywhere and won't be shown again.

Other flags:

* `--import-key <base58>` — bring your own Solana ed25519 private key instead of generating a new one
* `--skip-wallet` — write connection/defaults only, configure the wallet later
* `--http-url <origin>` — the single public origin all service URLs derive from; `--ws-url <url>` overrides the derived trading WS URL
* `--default-asset <SOL|BTC|ETH>` — default asset to save into config

After setup, run `joyride login` (also reads `JOYRIDE_KEYSTORE_PASSWORD`) to obtain a session JWT.

### Option B — `JOYRIDE_PRIVATE_KEY` (no on-disk keystore)

For ephemeral containers and one-shot scripts where you don't want the keystore on disk at all, the CLI accepts a Base58-encoded Solana private key from the environment and skips both `setup` and the keystore entirely:

```bash theme={null}
export JOYRIDE_PRIVATE_KEY="<base58-encoded-ed25519-secret-key>"
joyride login
joyride balance
```

Generate a fresh keypair using whichever tool you already have:

**Solana CLI** (canonical, recommended if you have it):

```bash theme={null}
solana-keygen new --no-bip39-passphrase --outfile /tmp/joyride-key.json
# Convert the byte array to the Base58 form JOYRIDE_PRIVATE_KEY expects:
npx --yes -p @scure/base node -e '
  const fs = require("fs");
  const { base58 } = require("@scure/base");
  const bytes = Uint8Array.from(JSON.parse(fs.readFileSync("/tmp/joyride-key.json", "utf8")));
  console.log(base58.encode(bytes));
'
```

**Node only** (no Solana tooling required):

```bash theme={null}
npx --yes -p @scure/base node -e '
  const c = require("crypto");
  const { base58 } = require("@scure/base");
  const kp = c.generateKeyPairSync("ed25519");
  const sk = kp.privateKey.export({ format: "der", type: "pkcs8" }).slice(-32);
  const pk = kp.publicKey.export({ format: "der", type: "spki" }).slice(-32);
  console.log(base58.encode(new Uint8Array(Buffer.concat([sk, pk]))));
'
```

Either path produces an 87–88-character Base58 string (32-byte secret seed concatenated with 32-byte public key) — exactly the format `JOYRIDE_PRIVATE_KEY` expects.

`JOYRIDE_PRIVATE_KEY` is intentionally a footgun for production use — the key sits in plain process env. It's the right tool for short-lived agent sessions, not long-lived deployments.

### Persisting state

Both options leave a session JWT at `~/.joyride/session.json` after `joyride login`. If your environment is ephemeral, persist `~/.joyride/` between runs — or just re-authenticate on each start, which is cheap.

The JWT expires at a time the **server** sets — run `joyride auth status` to see the exact expiry rather than assuming a fixed lifetime. Even with a persisted directory you'll need to re-run `joyride login` once it expires. Long-running agents should detect a `401` from the gateway and re-login on demand rather than assume the session is permanent.
