# RAETH Exchange — Full Agent Reference > RAETH is a high-frequency prediction market — "Trade fast. Trade often." It is a testnet CLOB (central-limit-order-book) venue whose single live product is a rolling **60-second BTC up/down binary** (the 1-minute window — series `btc-up-down-1m`): a real order book, a fresh round every 60 seconds, two-phase on-close settlement, and RLP house liquidity. RAETH is humans-first — a pro terminal anyone can trade by hand — with agents fully supported. This single document is everything an agent needs to authenticate, read market data, place/cancel/amend orders, read positions and account state, and subscribe to the realtime WebSocket. Every endpoint path and field name below exists in the live API; the BTC perp, BTC parlays, the 5m/1h binary windows, the 60s parimutuel, and sports are **dormant roadmap (gated off)** — not currently tradeable — but their API surface is documented here for completeness. The canonical machine-readable spec is `https://raeth.exchange/api/openapi.json`. **No real money is deposited or traded — RAETH is testnet.** - **REST base URL:** `https://raeth.exchange/api/v1` Every functional router is mounted under the stable client prefix `https://raeth.exchange/api/v1/…`. Pin `/v1` for stability; bare `/api/...` aliases remain only for compatibility. - **WebSocket URL:** `wss://raeth.exchange/stream` - **Auth header:** `Authorization: Bearer rk_live_…` on every authenticated request. - **Money fields:** normal live balances/PnL arrive as integer cents; values beyond JavaScript's safe integer range are decimal strings. Parse money before summing. - **OpenAPI 3.1 schema:** `https://raeth.exchange/api/openapi.json` · **Swagger UI:** `https://raeth.exchange/api/docs` - **Money model:** integer minor units everywhere. Order prices are integer **market-scaled price units** from the market `geometry` block. Quantities are integer **contracts**. Never send decimal prices — on a legacy BTC binary, a YES limit displayed as 55¢ is `"price": 55`, not `0.55`. --- ## 1. Authentication RAETH uses API keys for all trading automation. Browser sessions use HttpOnly cookies and are separate from API-key auth; mutating browser-session requests also send the readable `raeth_csrf` cookie value in the `X-Raeth-CSRF` header. ### API key format Keys are prefixed `rk_live_` followed by a high-entropy URL-safe token, e.g. `rk_live_SH7xA2mQ9fLpRvYJdKNbXcWzEuTiOkGh`. Keys are shown **once**, at creation, and cannot be retrieved later. Keys are **per-agent**, not per-account; RAETH stores only an HMAC digest. Send the key as a Bearer token on every authenticated request: ``` Authorization: Bearer rk_live_SH7xA2mQ9fLpRvYJdKNbXcWzEuTiOkGh ``` Scopes: `read` (market data + your own state) and `trade` (place/cancel/amend orders). `trade` implies `read`. ### API bootstrap — `POST /v1/auth/mcp-signup` Local/dev stacks with Google OAuth disabled can create an account, primary agent, and `read+trade` key here. Production has Google OAuth enabled: new emails must sign in via `/auth/google` or `/agents/register` first, and passwordless `mcp-signup` returns `MCP_SIGNUP_PASSWORD_REQUIRED` without disclosing whether the email exists. Existing password-backed accounts may supply `password` to mint another key on the primary Dealer Terminal. ```bash curl -s -X POST https://raeth.exchange/api/v1/auth/mcp-signup \ -H "Content-Type: application/json" \ -d '{ "email": "you@example.com", "agent_name": "my-first-bot", "password": "existing-account-password" }' ``` Response (store `api_key` — returned ONCE): ```json { "account_id": "8e2c…", "agent_id": "a1b2c3d4-…", "agent_name": "my-first-bot", "api_key": "rk_live_SH7xA2…", "paper_cash_cents": 1000000, "api_base_url": "https://raeth.exchange/api/v1", "generated_password": null, "message": "Save the api_key — it cannot be retrieved later." } ``` `generated_password` is only returned on local/dev account creation when the server generated a password. Production new users should mint/copy a key after Google sign-in. ### First order paths The live, tradeable product today: - **BTC 60s binary:** call `GET /v1/series/btc-up-down-1m/context`, trade `active_window.market_id`, and use the returned `seq_at_snapshot` for book/series WebSocket resume. This is the bounded-risk path: no leverage and no liquidation. Dormant roadmap (gated off — endpoints documented below, but NOT currently tradeable; do not route live orders to them): - **BTC perp:** call `GET /v1/markets?kind=BTC_PERP&status=OPEN`, choose native `BTC-PERP`, then call `GET /v1/markets/{market_id}/agent-context` for geometry, top-of-book, mark/index, funding, and `seq_at_snapshot`. Preview with `POST /v1/orders/preview`, place with `POST /v1/orders`, read back with `GET /v1/positions` or `GET /v1/account`, read funding at `GET /v1/funding/{market_id}/current` and `/history`, and close with the opposite side plus `reduce_only:true`. Use `slippage_cap_bps` for perp MARKET orders. - **BTC parlays:** call `POST /v1/parlays/quote`, accept with `POST /v1/parlays`, and read positions with `GET /v1/parlays`. - **Other binary windows** (5m / 1h `btc-up-down-*`), the **60s BTC parimutuel**, and **sports** markets follow the same gated-off status. ### Mint a scoped key from an existing agent — `POST /v1/me/agents/{agent_id}/keys` Use a signed-in browser session, or the Agents UI. The plaintext key is returned only once. ```bash curl -s -X POST https://raeth.exchange/api/v1/me/agents/{agent_id}/keys \ -H "Cookie: raeth_session=; raeth_csrf=" \ -H "X-Raeth-CSRF: " \ -H "Content-Type: application/json" \ -d '{"name": "algo-v2", "scopes": ["read", "trade"]}' ``` Body: `name` (string, required, ≤80 chars), `scopes` (string[], optional, defaults to `["read","trade"]`). Operator/admin credentials have a separate `/v1/agents/{agent_id}/keys` path. ### Revoke a key — `DELETE /v1/me/agents/{agent_id}/keys/{key_id}` ```bash curl -s -X DELETE https://raeth.exchange/api/v1/me/agents/{agent_id}/keys/{key_id} \ -H "Cookie: raeth_session=; raeth_csrf=" \ -H "X-Raeth-CSRF: " ``` The key stops authenticating immediately; in-flight requests are unaffected. --- ## 2. Markets & market data All read paths below are public (no key required), though sending a key enables `self_role` on trade responses. ### List markets — `GET /v1/markets` Query params: `status` (repeatable: `?status=OPEN&status=PENDING`; values `PENDING|OPEN|EXPIRED|RESOLVED|VOID`), `kind` (e.g. `BTC_BINARY`, `BTC_PERP`), `limit` (1–250, default 50). ```bash curl -s "https://raeth.exchange/api/v1/markets?kind=BTC_BINARY&status=OPEN&limit=10" \ -H "Authorization: Bearer rk_live_…" ``` Response is a list of `MarketView` objects: ```json [ { "market": { "id": "a7f3e2d1-4c5b-6a7b-8c9d-0e1f2a3b4c5d", "symbol": "BTC-1M-20260601T1030Z", "kind": "BTC_BINARY", "metadata_json": { "contract_type": "BTC_1M_UP_DOWN", "window_seconds": 60, "outcome_rule": "YES iff close_price_cents > open_price_cents (tie voids)" }, "open_at": "2026-06-01T10:29:00Z", "expiry_at": "2026-06-01T10:30:00Z", "status": "OPEN", "resolution": null, "resolution_price_cents": null, "created_at": "2026-06-01T10:29:00Z" }, "best_bid_cents": 44, "best_ask_cents": 51, "last_trade_cents": 48, "volume_24h_contracts": 1247, "open_interest_contracts": 334 } ] ``` `MarketView` fields: `market.id` (UUID), `market.symbol`, `market.kind`, `market.status`, `best_bid_cents`/`best_ask_cents` (int | null), `last_trade_cents` (int | null), `volume_24h_contracts` (int), `open_interest_contracts` (int). ### Active markets — `GET /v1/markets/active` Lightweight active-market discovery: the OPEN, live-window rows plus their current top-of-book / mark hints, ordered by nearest expiry. Query: `limit` (1–250, default 50). Public market data — cheaper than `GET /v1/markets` (no 24h stats / open interest / LP rewards / browse-presentation fields), so it is the route to poll for "what can I quote right now". Served from a short-TTL singleflight read model, so a poll storm (many clients or the house RLP/chaos cyclers — all normal clients, no special lane) collapses to one in-memory recompute. Each row: `{market, geometry, best_bid_cents, best_ask_cents, best_bid_size, best_ask_size, last_trade_cents, mark_price_cents, last_funding_rate_bps}`. ```bash curl -s "https://raeth.exchange/api/v1/markets/active?limit=50" \ -H "Authorization: Bearer rk_live_…" ``` ### Retired mirror markets Mirror markets (`BTC-PERP [HL]`, `BTCPM-5M-*`) were retired on 2026-06-17. Live discovery hides `market.metadata_json.is_mirror` / `mirror_venue` rows and direct trading rejects them while `MIRROR_MARKETS_ENABLE=false`. Agents should target the live native `btc-up-down-1m` series; native `BTC-PERP` and BTC parlays are dormant roadmap (gated off). ### BTC parlay markets (dormant / roadmap — gated off) > Parlays are built but NOT currently tradeable; the only live market is the 60s BTC binary. The API surface below is documented for completeness. BTC-native parlays use `kind=BTC_PARLAY`. They are quote-driven, one-shot CLOB binary markets: `POST /v1/parlays/quote` returns a firm bid/ask from `rlp-parlay-desk@raeth.exchange`, `POST /v1/parlays` accepts the quote and books the trade through the normal matcher, and `GET /v1/parlays` lists your parlay positions. Legs are 2-5 upcoming unopened native `btc-up-down-5m` windows from `/v1/series/btc-up-down-5m/context?upcoming_limit=5`'s `upcoming_windows`. Any leg void/tie/timeout voids/refunds the whole parlay, any leg loses resolves the parlay NO, and all legs won resolves YES. Read the stamped `BTC_PARLAY` geometry; prices are scale-4 integer units with payout price `10000`. Quote bid/ask is direction and correlation aware from recent BTC spot history, with conservative 40/60 probability clamps and a neutral 50/50 fallback only when history is unavailable. Accepted quotes expire after 10 seconds. ### Get one market — `GET /v1/markets/{market_id}` Single `MarketView` by UUID; same shape as a list element. ### Order book snapshot — `GET /v1/markets/{market_id}/book` Live L2 snapshot from the in-memory book (no DB round-trip). Query param: `depth` (1–99 price levels per side, default 20). ```bash curl -s "https://raeth.exchange/api/v1/markets/{market_id}/book?depth=5" ``` ```json { "market_id": "a7f3e2d1-…", "bids": [[44, 15], [43, 8], [42, 22]], "asks": [[51, 12], [52, 6], [53, 19]], "last_trade_price_cents": 48, "seq_at_snapshot": 84721 } ``` Each level is `[price_cents, total_qty]` (sum of all resting orders at that price). Bids are highest-first, asks lowest-first. Use the returned `seq_at_snapshot` as the WebSocket `since_seq`; replay is at-least-once, so ignore duplicate or stale seqs. ### Recent trades — `GET /v1/markets/{market_id}/trades` Newest-first, cursor-paginated. Query params: `limit` (1–1000, default 100), `cursor` (from a previous response's `next_cursor`). ```bash curl -s "https://raeth.exchange/api/v1/markets/{market_id}/trades?limit=5" \ -H "Authorization: Bearer rk_live_…" ``` ```json { "items": [ { "seq": 84720, "market_id": "a7f3e2d1-…", "price_cents": 48, "qty": 3, "taker_side": "BUY", "occurred_at": "2026-05-26T10:42:18.331Z", "self_role": null } ], "next_cursor": "eyJzZXEiOiA4NDcxMH0" } ``` Trade fields: `seq` (globally monotonic — use with WS `since_seq`), `price_cents`, `qty`, `taker_side` (`BUY` = taker lifted an ask), `occurred_at` (UTC), `self_role` (`"maker"`/`"taker"`/null — null unless authenticated and a party). ### OHLC candles — `GET /v1/markets/{market_id}/candles` Server-side OHLC buckets folded from trades, newest-first. Query params: `period_seconds` (60–86400, default 60; only 60 is persisted today), `limit` (1–2000, default 500), `gap_fill` (bool, default `true`). With `gap_fill` on, quiet buckets are forward-filled with a flat candle at the last traded price (O=H=L=C=prev close, `volume` 0, `trade_count` 0) and a live market extends flat to the current bucket, so the time axis has no holes; pass `gap_fill=false` for the raw trade-only series (e.g. backtests). `volume` is a decimal string for JS-safe large contract volume; parse with `BigInt` when summing. Price fields and `trade_count` remain JSON numbers. ```bash curl -s "https://raeth.exchange/api/v1/markets/{market_id}/candles?period_seconds=60&limit=120" ``` ### Repeating BTC series The live BTC binary rolls a fresh 60-second window continuously, grouped into a series so agents don't rediscover a new market every round. - `GET /v1/series` — list active repeating series. - `GET /v1/series/{series_id}` — one series detail. - `GET /v1/series/{series_id}/markets` — recent + upcoming individual window markets. - `GET /v1/series/btc-up-down-1m/context` — **the main bot bootstrap read.** Query params `history_limit` (recent settled windows to include) and `upcoming_limit` (server-listed unopened windows for future-leg selection). ```bash curl -s "https://raeth.exchange/api/v1/series/btc-up-down-1m/context?history_limit=6&upcoming_limit=5" ``` ```json { "series_id": "btc-up-down-1m", "name": "BTC 60s Up/Down", "kind": "BTC_BINARY", "window_seconds": 60, "seq_at_snapshot": 25973330, "spot_price_cents": 7274623, "spot_source": "median(binance,coinbase)", "feed_health": { "connected": true, "active_source": "median(binance,coinbase)" }, "active_window": { "market_id": "a8960a7d-5d6e-45da-b8c7-a7d7c85f5bae", "symbol": "BTC-1M-20260601T1006Z", "status": "OPEN", "open_at": "2026-06-01T10:05:00Z", "expiry_at": "2026-06-01T10:06:00Z", "seconds_to_close": 12, "open_price_cents": 7284148, "reference_price_cents": 7274623, "best_bid_cents": 1, "best_ask_cents": 3, "last_trade_cents": 2, "implied_yes_cents": 2, "outcome_rule": "close > open settles YES (a tie voids)", "in_the_money": "NO" }, "next_window": { "status": "PENDING", "seconds_to_open": 12 }, "upcoming_windows": [ { "status": "PENDING", "seconds_to_open": 12 } ], "recent_windows": [] } ``` Trade `active_window.market_id`. `seq_at_snapshot` is the WS-resume cursor; `implied_yes_cents` is the market-implied P(YES) in cents. ### BTC spot reference — `GET /feed/btc-spot/history` Bucketed BTC spot history for chart pre-fill and strategy context. Query param `seconds` (rolling window). Live ticks also arrive on the `feed.btc` WebSocket channel. ```bash curl -s "https://raeth.exchange/api/v1/feed/btc-spot/history?seconds=900" ``` ```json { "symbol": "btc", "source": "median(binance,coinbase)", "points": [ { "ts_ms": 1780317540000, "price_cents": 7274623 }, { "ts_ms": 1780317541000, "price_cents": 7275031 } ] } ``` `GET /feed/btc-spot/health` returns per-source feed health. ### BTC spot OHLC candles -- `GET /feed/btc-spot/candles` Full OHLC history from the durable `reference_price_ticks` table (30-day retention). Unlike `/feed/btc-spot/history` (15-minute in-memory buffer), this endpoint delivers multi-hour candlestick data. Aggregation uses the same integer median across live sources (Binance/Coinbase/Hyperliquid) as the live feed, so candles are continuous with the real-time `feed.btc` WebSocket stream. Public, no auth required. Query params: `period_seconds` (15-86400, default 300; the 15s period feeds the dormant 60s BTC parimutuel chart) and `limit` (1-500, default 500). Returns oldest-first (ascending `bucket_start_ts`). Sealed (closed) candles are immutable and never repaint. Each candle carries a `corrected` boolean (default `false`), `true` only when a sealed bucket's OHLC is served unchanged despite a later recompute of the underlying ticks diverging from the seal — the bar still does not move; the flag exists so clients can disclose the correction instead of silently repainting. Always `false` for the live (open) bar. Additive and backward-compatible. ```bash curl -s "https://raeth.exchange/api/v1/feed/btc-spot/candles?period_seconds=300&limit=500" ``` ```json { "symbol": "btc", "period_seconds": 300, "candles": [ { "bucket_start_ts": 1780317300, "period_seconds": 300, "open_cents": 7274000, "high_cents": 7276000, "low_cents": 7272000, "close_cents": 7275000, "corrected": false } ] } ``` The BTC binary terminal's spot chart sources from this endpoint instead of from the BTC perp's trade candles, so the chart is perp-independent and always live. ### Geometry-aware agent context — `GET /v1/markets/{market_id}/agent-context` One read that returns everything to decide on one market. Prices are geometry-aware: render `price / 10**geometry.price_scale` and validate with `geometry.price_grid`. Legacy BTC binaries are scale-2 cents today, but scale-4 sub-cent binaries are flag-gated and use the same API shape. Degrades to nulls when a live service isn't running; never 5xx. The example below shows the **dormant BTC perp's** geometry (the richest shape, with a `funding` block); the **live 60s binary** returns the same envelope with binary geometry — see the note after the example. ```json { "market_id": "a7f3e2d1-…", "symbol": "BTC-PERP", "kind": "BTC_PERP", "status": "OPEN", "geometry": { "underlying_asset": "BTC", "price_quote_asset": "USD", "asset_cash_scale": 6, "price_scale": 0, "size_scale": 2, "tick": 1, "min_tick": 1, "price_grid": {"type": "FIXED_TICK", "tick": 1}, "price_min": 1, "price_max": 1000000, "contract_multiplier": 10000, "contract_style": "LINEAR_PERP", "payout_price": null, "payout": null, "geometry_hash": "..." }, "best_bid": 64000, "best_ask": 64010, "last_trade": 64005, "external_fair": { "fair": 64002, "source_name": "Binance", "source_url": "https://www.binance.com/en/futures/BTCUSDT", "fetched_at": "2026-06-08T10:42:18Z" }, "funding": { "mark": 64007, "index": 64003, "next_funding_at": "2026-06-08T11:00:00Z", "last_rate_bps": 4 }, "seconds_to_close": null, "seq_at_snapshot": 84721 } ``` `best_bid`, `best_ask`, `last_trade`, and `external_fair.fair` are ALL in the market's `price_scale` units (the fair is normalized to match the book — render it the same way). The standalone `GET /v1/markets/{id}/external-fair` returns `fair_cents` in USD cents (price_scale=2: divide by 100); use `agent-context` to compare the fair against the book on its own grid. The **live BTC 60s binary** returns the same shape with binary geometry (`contract_style=BINARY_FIXED_PAYOUT`, `payout_price=100` for legacy cents or `10000` for sub-cent rows), `funding:null`, and a non-null `seconds_to_close`. Agents should always read `geometry` instead of assuming a 1-99 cent band. `GET /v1/datasets` lists every pullable dataset (free now + paid roadmap). --- ## 3. Orders ### Place an order — `POST /v1/orders` The agent is taken from your bearer key. Returns an `OrderPlaceResult`. The request body is the canonical `IncomingOrder`. ```bash curl -s -X POST https://raeth.exchange/api/v1/orders \ -H "Authorization: Bearer rk_live_…" \ -H "Content-Type: application/json" \ -d '{ "market_id": "a7f3e2d1-4c5b-6a7b-8c9d-0e1f2a3b4c5d", "side": "BUY", "type": "LIMIT", "tif": "GTC", "price": 53, "qty": 10, "client_order_id": "my-bot-001", "reduce_only": false }' ``` ```json { "order_id": "9a8b7c6d-…", "market_id": "a7f3e2d1-…", "status": "NEW", "reject_reason": null, "reject_message": null, "filled_qty": 0, "remaining_qty": 10, "avg_fill_price_cents": null, "fills": [], "effective_leverage": null } ``` **Request fields (`IncomingOrder`):** | field | type | required | notes | |---|---|---|---| | `market_id` | UUID | yes | The OPEN market to trade. | | `side` | `BUY` \| `SELL` | yes | BUY takes YES/long exposure; SELL the opposite/short side. | | `type` | `LIMIT` \| `MARKET` | yes | LIMIT requires `price`. MARKET sweeps liquidity under price protection (behaves as IOC). | | `tif` | `GTC` \| `IOC` \| `FOK` \| `POST_ONLY` \| `GTT` \| `GTD` | no | Defaults to `GTC`. `POST_ONLY` rejects if it would cross (maker-only, earns rebate). `GTT`/`GTD` require `expires_at`. | | `price` | int | LIMIT only | Market-scaled price units. Read `geometry.price_scale`, `price_grid`, `price_min`, and `price_max`. | | `qty` | int | yes | Contracts. `0 < qty ≤ 1,000,000`. | | `client_order_id` | string | no | Idempotency key. 1–128 chars of `[A-Za-z0-9_-:.]`. Reusing it returns the original result. | | `reduce_only` | bool | no | If true, may only shrink an existing position. Default false. | | `slippage_cap_cents` | int (0–100000000) | no | **Binary MARKET only** — legacy field name for additive price protection in the binary market's scaled price units. Omit for default. Rejected on perps. | | `slippage_cap_bps` | int (0–10000) | no | **Perp MARKET only** — bps past the touch. Default 50 bps on perps. Rejected on binaries. | | `leverage` | int (1–50) | no | **Perp only.** Leverage for this position. | | `margin_mode` | `CROSS` \| `ISOLATED` | no | **Perp only**, when opening from flat. Default CROSS. | | `expires_at` | datetime (UTC) | GTT/GTD only | Order expiry. | | `take_profit_price_cents` / `stop_loss_price_cents` | int | no | **Perp only** — TP/SL bracket registered at first fill (ignored for binaries). | | `self_trade_prevention` | `CANCEL_NEWEST` \| `CANCEL_OLDEST` \| `DECREMENT` | no | Default CANCEL_OLDEST. | | `display_qty` | int | no | Iceberg visible slice; LIMIT + GTC/GTT only; must be `< qty`. Margin reserves on full `qty`. | | `confirm_fat_finger` | bool | no | Set true only after a `FAT_FINGER` rejection, to confirm intent. | **Response fields (`OrderPlaceResult`):** `order_id` (UUID; zero-UUID = rejected before the ledger), `market_id`, `status` (`NEW`|`PARTIAL`|`FILLED`|`CANCELLED`|`REJECTED`), `reject_reason` (string|null), `reject_message` (string|null), `filled_qty` (int), `remaining_qty` (int), `avg_fill_price_cents` (int|null — integer-truncated VWAP; exact VWAP recoverable from `fills`), `fills` (`FillView[]`), `effective_leverage` (int|null — null for binaries). > Order placement returns **HTTP 201** with `status:"NEW"` (or 200 with `status:"REJECTED"`). An engine rejection is NOT an HTTP error — inspect `status` + `reject_reason`. Schema-validation failures (wrong types, missing fields) return HTTP 400/422 with the error envelope and never reach the engine. ### Idempotency Replaying the same `client_order_id` from the same agent returns the original result instead of placing a second order. Give every intended order a unique key; network-retry loops are then safe. ### Preview (dry-run) — `POST /v1/orders/preview` Runs the exact same pre-trade path with no ledger write. Returns `OrderPreviewResponse`: `est_fill_price_cents`, `notional_cents`, `fees_cents`, `margin_required_cents`, `est_resulting_position_qty`, `est_resulting_avg_entry_cents`, `est_resulting_liquidation_price_cents`, `est_resulting_margin_ratio_bps`, `est_max_payout_cents`, `est_max_loss_cents`, `effective_leverage`, `effective_margin_mode`, `would_reject_with`, and a `units` block. **Scales (read `units`).** Despite the shared `*_cents` suffix, two scales are in play. MONEY fields (`notional_cents`, `fees_cents`, `margin_required_cents`, `est_max_payout_cents`, `est_max_loss_cents`) are USD cents. PRICE fields (`est_fill_price_cents`, `est_resulting_avg_entry_cents`, `est_resulting_liquidation_price_cents`) are in the market's `price_scale` units — render `value / 10**units.price_scale`, NOT `/100`. On the (dormant) BTC perp `price_scale==0`, so a price field of `65000` is $65,000. `est_max_payout_cents`/`est_max_loss_cents` are `null` on perps (binary-only). An out-of-band/off-grid LIMIT price sets `would_reject_with = "PRICE_OUT_OF_RANGE"` and nulls the estimates; `would_reject_with` also surfaces `INSUFFICIENT_MARGIN` (null if the order would pass the gate). ### List your orders — `GET /orders` Newest-first, scoped to the bearer agent. Query params include `status` (comma-separated; values `NEW,PARTIALLY_FILLED,FILLED,CANCELLED,REJECTED`) and `limit`. ```bash curl -s "https://raeth.exchange/api/v1/orders?status=NEW,PARTIALLY_FILLED&limit=50" \ -H "Authorization: Bearer rk_live_…" ``` Note the asymmetry: the order **body** reports `status:"PARTIAL"`, but the query **filter** uses `PARTIALLY_FILLED`. `GET /orders/{order_id}` returns a single `OrderView`. ### Cancel — `DELETE /v1/orders/{order_id}` and `DELETE /v1/orders` ```bash # Cancel one resting order curl -s -X DELETE https://raeth.exchange/api/v1/orders/9a8b7c6d-… \ -H "Authorization: Bearer rk_live_…" # Mass-cancel every NEW/PARTIAL order for the bearer agent (optionally scoped) curl -s -X DELETE "https://raeth.exchange/api/v1/orders?market_id=" \ -H "Authorization: Bearer rk_live_…" ``` ### Amend — `POST /v1/orders/{order_id}/amend` Modifies a resting LIMIT order. Body must include at least one of `qty` (new TOTAL qty, not a delta) or `price` (new limit in market-scaled integer price units). Qty-down at the same price is an in-place amend (queue position preserved); qty-up or any price change is an ATOMIC cancel-and-replace (one engine step — no window where both/neither order is live; on success the original is always cancelled; the replacement is a fresh post that loses queue position). If the original is already gone when the swap runs, no replacement is placed (`replacement_order_id` null, `original_cancel_status=already_gone`). ```bash curl -s -X POST https://raeth.exchange/api/v1/orders/9a8b7c6d-…/amend \ -H "Authorization: Bearer rk_live_…" -H "Content-Type: application/json" \ -d '{"price": 54, "qty": 8}' ``` ### Batch — `POST /v1/orders/batch` Submit up to 50 orders in one transaction. Body: `orders` (array, 1–50 `IncomingOrder` bodies), optional `batch_id`, `fail_mode` (`best_effort` default, or `all_or_nothing`). Each `items[]` entry: `status` (`PLACED`|`REJECTED`), `order_id` (on `PLACED`), `reject_reason` (typed engine enum, null when rejected before the matcher), `reject_code` (**machine code, never null on a `REJECTED` item** — the `reject_reason` value, or `INVALID_ORDER_BODY` / `RATE_LIMITED` / `ALL_OR_NOTHING_ROLLBACK`), `reject_message` (prose). ### Atomic batch-modify — `POST /v1/orders/batch-modify` Atomically apply a SET of **your own** cancel/amend/place legs to **one** market in a single all-or-nothing engine apply — the whole batch applies or none of it does (no partial book state). Parity with Hyperliquid `batchModify`; atomicity, not size, is the point (cap `EXTERNAL_BATCH_MODIFY_MAX`, default 20 legs). Body: `market_id`, `legs` (array), optional `client_batch_id`. Each leg is one of `{"action":"cancel","order_id":…}`, `{"action":"amend","order_id":…,"qty":…,"price":…?}` (`qty` is the new TOTAL order qty — gross size incl. already-filled, same rule as `POST /orders/{id}/amend`; an amend whose new TOTAL qty ≤ the order's already-filled qty → `400 INVALID_AMEND_QTY`; same-price qty-down keeps priority; qty-up/price-change is an atomic cancel+re-place), or `{"action":"place","side":…,"price":…,"qty":…,"leverage":…?,"margin_mode":…?}`. Operates only on the orders you name — siblings are untouched. A foreign/unknown order id → `404`/`403` (never applied). A failing place leg rejects the WHOLE batch (`rejected:true`, `reason`, `failing_leg:{side,price,qty}`, counts 0). Over-cap → `400 BATCH_TOO_LARGE`. A batch of N legs counts as N writes. `client_batch_id` makes retries idempotent (same key+payload replays the prior outcome; changed payload → `409 BATCH_MODIFY_REPLAY_CONFLICT`). Flag-gated by `REPLACE_LADDER_ENABLED` (`503` when off). ### Replace ladder (bulk quote) — `POST /v1/orders/replace-ladder` Replace your WHOLE two-sided resting ladder in ONE call per market per tick (ADR-004 bulk-quote primitive). Ship the DESIRED resting ladder and the engine diffs it against your current resting orders (cancel/amend/place) in one atomic apply. Public + trade-scoped — the house RLP/chaos cyclers hit this EXACT route as normal clients, with NO special lane and NO bypass. Body: `market_id`, `target_levels` (array of `{"side":"BUY|SELL","price":,"qty":,"leverage":,"margin_mode":"CROSS|ISOLATED?"}` — `qty:0` drops the level; an EMPTY array cancels ALL your resting orders), `client_batch_id` (per-tick correlation label echoed back — NOT a dedup key), `fail_mode` (`best_effort` default, or `all_or_nothing`). Response: `{market_id, client_batch_id, cancels, amends, places, rejected, reason, failing_leg}`. Metered by EMITTED-WORK admission on the reconciled diff (cancels+amends+places), uniformly for every caller — over budget → `429 EMITTED_WORK_BUDGET_EXCEEDED` (the whole call is shed cleanly; retry next tick). Flag-gated by `REPLACE_LADDER_ENABLED` (`503 REPLACE_LADDER_DISABLED` when off). `leverage`/`margin_mode` are perp-only place-leg fields. ```bash curl -s -X POST https://raeth.exchange/api/v1/orders/replace-ladder \ -H "Authorization: Bearer rk_live_…" -H "Content-Type: application/json" \ -d '{"market_id":"","target_levels":[{"side":"BUY","price":48,"qty":10},{"side":"SELL","price":52,"qty":10}],"client_batch_id":"tick-001","fail_mode":"best_effort"}' ``` > **MCP / agent_id-explicit variants.** When driving via the RAETH MCP server, the same operations are also exposed under `/me/agents/{agent_id}/…`: `POST …/orders`, `POST …/orders/preview`, `POST …/orders/{order_id}/amend`, `POST …/orders/batch-modify`, `DELETE …/orders/{order_id}`, `DELETE …/orders`. Same engine — they just take an explicit `agent_id` in the path instead of resolving it from the bearer key. --- ## 4. Positions & account ### Account summary — `GET /account` Scoped to the bearer agent. Returns `AccountSummary`: ```bash curl -s "https://raeth.exchange/api/v1/account" -H "Authorization: Bearer rk_live_…" ``` Fields: `agent_id`, `name`, `cash_cents` (free/spendable), `margin_held_cents` (locked behind open orders/positions), `equity_cents` (= cash + margin_held + Σ unrealized PnL), `positions` (`PerpPositionView[]`, open only), `closed_positions`, `total_realized_pnl_cents`, `total_unrealized_pnl_cents`, `total_pnl_cents`, and perp risk fields (`cross_margin_ratio_bps`, `maintenance_margin_cents`, `cross_account_leverage_bps`). ### Positions — `GET /positions` Open positions for the bearer agent, as `{ items: PerpPositionView[], next_cursor, count }`. A `PerpPositionView`: `market_id`, `qty` (signed: + long, − short), `cost_basis_cents`, `realized_pnl_cents`, `funding_paid_cents`, `avg_price_cents` (computed VWAP), `side` (`LONG`/`SHORT`/`FLAT`), plus perp-only live fields (null for binaries): `leverage`, `mark_price_cents`, `liquidation_price_cents`, `unrealized_pnl_cents`, `unrealized_pnl_bps` (ROE), `notional_cents`, `margin_ratio_bps`, `margin_mode`, `isolated_margin_cents`. ### Your fills — `GET /account/fills` Trades where the bearer agent was maker or taker, newest-first, cursor-paginated (`limit` 1–500, default 100, `market_id` optional). ```bash curl -s "https://raeth.exchange/api/v1/account/fills?limit=50" -H "Authorization: Bearer rk_live_…" ``` ### Wallet & allocation (master ↔ sub-accounts) Capital is **deposited/allocated**, never minted per-agent. One account = one master wallet; each agent is a sub-wallet funded by allocation from the master, conserved by construction. - `GET /me/wallet` — consolidated `WalletView`: `total_equity_cents`, `main_account_cents` (the master/Main-account balance), `total_available_cents`, `total_margin_held_cents`, `total_unrealized_pnl_cents`, and a `sub_accounts[]` breakdown (each: `id`, `name`, `role` `primary`/`sub`, `cash_cents`, `margin_held_cents`, `equity_cents`, `unrealized_pnl_cents`, `open_position_count`). - `POST /me/agents/{agent_id}/allocate` — signed master↔agent transfer. Body `{"amount_cents": N}` where `N>0` funds the agent (master→agent) and `N<0` withdraws to the master. Zero is rejected. Returns `{ account_cash_cents, agent_id, agent_cash_cents }`. - `GET /me/agents` lists your agents (sub-wallets); `POST /me/agents` creates one. ### BTC parlays (dormant / roadmap — gated off) > Not currently tradeable; the only live market is the 60s BTC binary. API surface kept here for completeness. - `POST /parlays/quote` — body `{"qty": N, "legs": [{"window_close": "...", "direction": "UP|DOWN"}]}`. Returns a direction/correlation-aware `quote_id`, `bid_price`, `ask_price`, premiums, payout, implied multiplier, and 10-second expiry. - `POST /parlays` — body `{"quote_id": "...", "side": "BUY|SELL", "client_key": "stable-id"}`. Idempotently accepts the quote, creates a restricted `BTC_PARLAY` market, crosses user vs RLP Parlay Desk through the matcher, then halts further entry. - `GET /parlays` — authenticated list of your BTC parlay positions with per-leg state. Returns the standard list envelope `{items:[…], next_cursor, count}` (same shape as `GET /orders`); `next_cursor` is null (unpaginated). The bare-array shape was retired 2026-06-14. - Remote MCP exposes the same BTC-native flow as `quote_parlay`, `book_parlay`, and `list_parlays`. ### Leaderboard — `GET /leaderboard` User/account-level standings ranked by `net_pnl_cents` (= `trading_pnl + maker_rebate + lp_rewards − fees_paid`, integer minor-units). This is the same number as the account portfolio net PnL — one ledger view. Subscribe to `leaderboard.v1` for realtime updates. Current season rules live at `GET /v1/season/current/rules`. Each row exposes `public_id` (opaque 16-char hex derived from SHA-256 of the internal account UUID — NOT the raw UUID) and `display_name` (pseudonymous `"Trader "` or user-set handle; never an email local-part). Use `public_id` as the route key for `GET /v1/leaderboard/account/{public_id}`. The raw `account_id` UUID is no longer in the public roster (M1 fix, 2026-06-21). --- ## 5. Binary (BTC 60s up/down) specifics The live product is a rolling binary (the 1-minute window — series `btc-up-down-1m`). Every 60 seconds a fresh window opens. At expiry the BTC close snapshot is taken and the market settles: - **YES** (a BUY / long) if the close snapshot is **above** the open snapshot → pays `100¢` per contract. A tie voids (refund). - **NO** (a SELL / short) if the close is strictly **below** the open → the YES side pays `0¢`. Production geometry is row-specific. Legacy BTC binaries are `price_scale=2`, fixed tick `1`, `price_min=1`, `price_max=99`, `payout_price=100`; flag-gated sub-cent rows use `price_scale=4`, piecewise ticks, `price_min=10`, `price_max=9990`, `payout_price=10000`. The CLOB speaks `BUY`/`SELL`, not YES/NO — there is one book per window; a SELL at 55c display is the same liquidity a YES buyer lifts. No leverage, no liquidation: worst-case loss per contract is bounded (`max_loss_buy = premium`; `max_loss_sell = payout − premium`). Display probability is derived, never stored: `price / payout_price`. Settlement posts to the ledger, your `private.` WS channel, and `GET /account`. --- ## 6. Perpetual (BTC linear perp) specifics — dormant / roadmap (gated off) > The BTC linear perp is built but NOT currently tradeable; the only live market is the 60s BTC binary. This section documents its API surface for completeness. Prices on the perp are geometry-aware whole-USD (`price_scale=0` in agent-context); PnL is mark-to-market (`notional(exit) − notional(entry)`). - **Leverage:** `leverage` in the order body (1–50). Echoed back as `effective_leverage`. - **Margin mode:** `margin_mode` `CROSS` (default) or `ISOLATED`. Switch a flat position's mode with `POST /positions/{market_id}/margin-mode` (`{"mode":"ISOLATED","isolated_margin_cents":X}`). - **Price protection:** use `slippage_cap_bps` on perp MARKET orders. The legacy `slippage_cap_cents` field is binary-only and is in scaled binary price units. - **Funding:** periodic funding accrues against open perp positions. - `GET /funding/{market_id}/current` is perp-only and live-surface gated. It returns mark/index in the market's scaled price units plus `funding_source`, `mark_source`, `index_source`, index freshness/staleness, `next_funding_at`, last funding event fields, `last_funding_raw_payload_hash`, `last_funding_parser_version`, `last_funding_source_version`, and `evidence.raw_index_price_cents` when available. - `GET /funding/{market_id}/history` is also perp-only and returns cursor-paginated applied events as `{ items: [{ market_id, applied_at, rate_bps, mark_price_cents, index_price_cents, seq, funding_source, raw_payload_hash, parser_version, source_version, evidence }], next_cursor, count }`. - `GET /funding/latest-by-kind` returns latest funding for the launched BTC perp surface, not dormant GOLD/CRUDE placeholders. - Mark/index + `next_funding_at` + `last_rate_bps` also appear in `GET /v1/markets/{market_id}/agent-context` under `funding`. - **TP/SL bracket:** `take_profit_price_cents` / `stop_loss_price_cents` in the order body register trigger orders at first fill. --- ## 7. WebSocket — realtime streams Connect to `wss://raeth.exchange/stream`. Public channels can be consumed anonymously. The private channel requires a short-lived ticket. ### Mint a ticket (private channel only) — `POST /v1/auth/ws-ticket` 60-second TTL, single-use. Mint a fresh ticket immediately before each connect. ```bash curl -s -X POST https://raeth.exchange/api/v1/auth/ws-ticket \ -H "Authorization: Bearer rk_live_…" -H "Content-Type: application/json" \ -d '{"agent_id": ""}' # → { "ticket": "rw_live_XYZ…", "expires_at": "2026-05-26T10:01:00Z" } ``` ### Connect & subscribe ``` # Public-only (no auth): wss://raeth.exchange/stream # Authenticated (for private.): wss://raeth.exchange/stream?ticket=rw_live_XYZ… ``` After connecting, send a subscribe message. You may subscribe to more channels later without reconnecting. ```json { "op": "subscribe", "channels": [ "book.", "trades.", "series.btc-up-down-1m", "feed.btc", "private.", "private.account." ], "since_seq": 482910 } ``` `since_seq` (optional): the server replays persisted events from a bounded safety window before `since_seq`, then sends `{"op":"replay_complete","since_seq":…,"replayed":N}` before switching to live. Resume is at-least-once: drop duplicate/stale events with `seq <=` the latest seq you already applied for that channel/market. ### Resume after disconnect Track the highest `seq` you have received. On reconnect, re-subscribe with `since_seq` set to it. (The official frontend client uses exponential backoff from 1s, capped at 30s, with a 5s delay after a server `{"op":"shutdown"}`.) **Replay guarantees:** replay covers a bounded safety window before `since_seq` plus newer persisted ledger events. `seq` is globally monotonic, but cross-market/account channels can observe commits slightly out of seq order, so the safety window makes reconnect resume at-least-once instead of gap-prone. If replay is capped, the server sends `{"op":"resync_required","code":"WS_REPLAY_TRUNCATED",...}` before the newest replay window. If local fan-out drops committed events, it sends `{"op":"resync_required","code":"BROADCAST_QUEUE_OVERFLOW",...}`. Treat either as a gap signal: immediately refetch dependent REST snapshots and then continue or reconnect with `since_seq`. **Per-channel gap detection (`channel_seq`):** every LIVE data frame also carries a `channel_seq` object mapping each channel in the frame to a per-channel cursor `{ "seqId", "prevSeqId" }` (`prevSeqId` = the prior live event's `seqId` on THAT channel). Chain it: keep the last-seen `seqId` per channel, and if an incoming `prevSeqId` != that, a live event was missed on the channel — refetch that surface's REST snapshot. This catches gaps the bounded global-`seq` window cannot (an out-of-order `seq` on the cross-market `private.account.` channel landing outside the window). It is additive and absent on replay frames; clients that ignore it still work. The official terminal turns a detected gap into a self-issued `{"op":"resync_required","code":"WS_CHANNEL_SEQ_GAP","channels":[...]}`. ### Channel reference | channel | auth | description | |---|---|---| | `book.` | no | Full L2 book snapshot on every change (replaces previous state, not a diff). | | `trades.` | no | New trade events as they execute. | | `markets` | no | `MARKET_OPENED`, `MARKET_RESOLVED`, `MARKET_EXPIRED`. | | `series.btc-up-down-1m` | no | BTC rolling-window lifecycle (rollover + settlement). | | `feed.btc` | no | Live BTC spot ticks (up to ~4 Hz). | | `leaderboard.v1` | no | Leaderboard updates / invalidation. | | `private.` | yes (ticket) | Full execution lifecycle without polling REST: `ORDER_PLACED` (status=NEW/PARTIAL=ack; status=REJECTED=engine-reject w/ `reject_reason`), `ORDER_REJECTED` (pre-trade reject; top-level `client_order_id` for correlation, `order_id` always null), `TRADE_EXECUTED` (per-fill; aggregate for cumulative filled/avg), `ORDER_CANCELLED`, `POSITION_UPDATED`, `FUNDING_APPLIED`, `POSITION_LIQUIDATED`, `ACCOUNT_CREDIT`, `ACCOUNT_DEBIT`. | | `private.account.` | yes (ticket) | Full private events for every agent owned by the authenticated account. | ### Execution-report pattern (private channel) Subscribe to `private.` to track orders end-to-end without polling REST: ``` # 1. Accepted resting order (engine ack): { "kind": "ORDER_PLACED", "order_id": "e1f2…", "client_order_id": "bot-001", "status": "NEW", "filled_qty": 0, "remaining_qty": 10 } # 2. Immediately partially filled: { "kind": "ORDER_PLACED", "order_id": "e1f2…", "client_order_id": "bot-001", "status": "PARTIAL", "filled_qty": 3, "remaining_qty": 7 } # 3. Engine reject (e.g. POST_ONLY would cross): { "kind": "ORDER_PLACED", "order_id": "e1f2…", "client_order_id": "bot-001", "status": "REJECTED", "reject_reason": "POST_ONLY_WOULD_CROSS" } # 4. Pre-trade reject (never reached the engine): { "kind": "ORDER_REJECTED", "order_id": null, "client_order_id": "bot-002", "stage": "PRE_TRADE", "reason": "INSUFFICIENT_MARGIN", "message": "…", "order_request": {…} } # 5. Per-fill: { "kind": "TRADE_EXECUTED", "taker_order_id": "e1f2…", "price": 52, "qty": 3, "taker_side": "BUY" } ``` For `ORDER_REJECTED` events, `client_order_id` is promoted to the top level — you do not need to parse the nested `order_request` body to correlate the reject to your outgoing request. `order_id` is always `null` because no engine order id was minted. ### Event envelope Every event message carries `channels` + `event` (+ `channel_seq` on live frames); `event.kind` identifies the type, `event.seq` is a globally monotonic sequence across all channels (use it for coarse `since_seq` resume), and `channel_seq` is the per-channel cursor for fine-grained gap detection (above). ```json { "channel": "trades.a7f3e2d1-…", "event": { "kind": "TRADE_EXECUTED", "seq": 84723, "market_id": "a7f3e2d1-…", "price_cents": 48, "qty": 3, "taker_side": "BUY", "occurred_at": "2026-05-26T10:42:18.331Z" } } ``` ```json { "channel": "private.a1b2c3d4-…", "event": { "kind": "TRADE_EXECUTED", "seq": 84723, "market_id": "a7f3e2d1-…", "price_cents": 48, "qty": 2, "taker_side": "BUY", "occurred_at": "2026-05-26T10:42:18.331Z" } } ``` ### Control messages Messages without an `event` field are control frames. Respond to `ping` with `pong` (sockets idle >90s without a pong close with code `1001`). ```json { "op": "ping" } { "op": "subscribed", "channels": ["…"] } { "op": "unsubscribed", "channels": ["…"] } { "op": "replay_complete", "since_seq": 84720, "replayed": 14 } { "op": "resync_required", "code": "WS_REPLAY_TRUNCATED", "since_seq": 0, "replay_limit": 1000, "oldest_replayed_seq": 83720, "latest_replayed_seq": 84720 } { "op": "resync_required", "code": "BROADCAST_QUEUE_OVERFLOW", "dropped_seq": 84721, "latest_seq": 84745 } { "op": "shutdown" } { "op": "error", "code": "…", "message": "…" } ``` Client → server ops: `subscribe`, `unsubscribe`, `ping`, `pong`. ### WS limits & error codes Per agent: ≤6 concurrent connections; per API key: ≤3. A connection holds ≤128 active subscriptions and ≤120 ops/min. Frames >16 KiB close the socket (code `1009`). WS error codes: `BAD_JSON`, `BAD_OP`, `BAD_CHANNELS`, `TOO_MANY_CHANNELS` (>32 in one op), `CHANNEL_TOO_LONG` (>160 chars), `UNKNOWN_CHANNEL` (whole op rejected), `FORBIDDEN_CHANNEL` (subscribed to another agent's private channel), `BAD_SINCE_SEQ`, `WS_RATE_LIMITED`. --- ## 8. Rate limits Enforced **per agent** (multiple keys for one agent share the bucket): | bucket | limit | window | applies to | |---|---|---|---| | Write | 600 requests | 60 s | POST/DELETE/PATCH orders, cancels, amendments, agent writes | | Read | 6,000 requests | 60 s | GET endpoints | When throttled you get **HTTP 429** with the flat envelope `{ "code": "RATE_LIMITED", "message": "…", "request_id": "…", "retry_after_s": 3 }`. Respect the `Retry-After` header (seconds) or back off exponentially starting at 1s. **429 and order safety:** a rate-limited request is rejected BEFORE the order engine — your order was NOT placed. Retry the identical body with the SAME `client_order_id`: if the first attempt somehow reached the engine, the idempotency key replays the original outcome instead of double-placing; a new `client_order_id` would place a second order. --- ## 9. Error envelope RAETH uses one machine-parseable error format. **Order rejections are HTTP 200** with `status:"REJECTED"` (the request was valid; the engine declined it). Infrastructure errors (auth, rate limit, not found) return 4xx/5xx. Every 4xx/5xx is the SAME flat envelope — `code`/`message`/`request_id` at the top level, never nested under a `detail` key: ```json { "code": "INSUFFICIENT_MARGIN", "message": "Insufficient margin: need 47,500 cents, have 12,300 free.", "request_id": "9cb4ae023baa" } ``` Some codes add a `details` (plural) key alongside. A schema-validation `422` carries the pydantic error list; a leverage-cap `422` carries the cap: ```json { "code": "INVALID_LEVERAGE", "message": "requested leverage 50x exceeds account cap 40x", "request_id": "9cb4ae023baa", "details": { "requested_leverage": 50, "effective_leverage": 40 } } ``` Order rejection (HTTP 200): ```json { "order_id": "00000000-0000-0000-0000-000000000000", "status": "REJECTED", "reject_reason": "POST_ONLY_WOULD_CROSS", "reject_message": "POST_ONLY order would immediately match.", "filled_qty": 0, "remaining_qty": 0 } ``` ### Order reject reasons (`reject_reason`) | code | when | fix | |---|---|---| | `POST_ONLY_WOULD_CROSS` | A POST_ONLY order would cross and immediately match. | Reprice so it rests without crossing. | | `FOK_NOT_FILLABLE` | A FOK order can't fill entirely now. | Reduce qty, switch to IOC, or wait for liquidity. | | `MARKET_NO_LIQUIDITY` | A MARKET order finds no opposing orders. | Check the book first. | | `PRICE_OUT_OF_RANGE` | Limit price outside the market geometry band. | Reprice within range. | | `INVALID_QTY` | qty zero or negative. | Use a positive integer. | | `MARKET_CLOSED` | Market EXPIRED/RESOLVED/VOID. | Use an OPEN market; check `market.status`. | | `INSUFFICIENT_MARGIN` | Not enough free cash for initial margin. | Reduce qty/leverage, close a position, or allocate more cash. | | `POSITION_CAP` | Order would exceed the per-agent position cap. | Reduce qty. | | `AGENT_SIDE_COOLDOWN` | Repeated rejects put this agent/market/side into cooldown. | Stop quoting that side and retry after the cooldown window. | | `REDUCE_ONLY_VIOLATION` | A reduce_only order would open/increase a position. | Remove reduce_only or flip side. | | `NO_REFERENCE_PRICE` | MARKET order but no mark/index to enforce price protection. | Wait for the reference feed; check series context. | | `INVALID_LEVERAGE` | Leverage specified on a binary. | Omit leverage. | | `INVALID_MARGIN_MODE` | margin_mode supplied on a binary, or mismatched an existing position's mode. | Omit / match the existing mode. | | `FAT_FINGER` | Price/qty exceeds sanity bounds. | Verify; if intentional resend with `confirm_fat_finger: true`. | | `RATE_LIMITED` | Write or read rate limit exceeded. | Back off; respect `Retry-After`. | | `UNKNOWN_MARKET` | market_id doesn't exist. | Verify via `GET /v1/markets`. | | `EXCHANGE_FROZEN` | Global kill switch active. | Wait and retry. | ### HTTP status codes `400` (Pydantic validation), `401` (missing/invalid Authorization), `403` (resource owned by another agent), `404` (resource not found), `409` (duplicate name/email, market not OPEN, amend conflict), `422` (validation after JSON parse), `429` (rate limit), `502` (upstream reference-data unreachable), `503` (exchange frozen / launch-feature gate). --- ## 10. SDKs & MCP - **Python:** `pip install raeth-sdk`. Reads `RAETH_API_KEY` / `RAETH_API_URL` env vars; sync + async; typed errors (`ValidationError`, `RateLimitError`, `AuthError`) preserving the `{code, message, request_id}` envelope. Ships runnable BTC-binary starter strategies (maker/taker/grid/short-vol) under `examples/`; point the series id at the live `btc-up-down-1m` to run against the live venue. - **TypeScript/JS:** `raeth-sdk` (npm). `createClient({ apiKey })`; `instanceof` error subclasses; works in Node ≥18, Deno, Bun, browsers. - **MCP server:** the hosted remote server lives at `https://raeth.exchange/mcp` and uses OAuth/Google sign-in, no pasted key. Its tools mirror the live REST surface: `whoami`, `list_markets`, `get_market`, `get_book`, `get_market_context`, `get_candles`, `get_funding`, `get_funding_history`, `place_order`, `cancel_order`, `mass_cancel`, `preview_order`, `get_account`, `get_positions`, `list_my_orders`, `place_trigger_order`, `list_trigger_orders`, `cancel_trigger_order`, `quote_parlay`, `book_parlay`, `list_parlays`, and the strategy/data tools. `list_trigger_orders` defaults to pending triggers; pass `status=null` for history. It is the only MCP surface — the former API-key stdio MCP (`raeth-mcp`) was retired 2026-06-14; clients that can't run OAuth use the REST API or an SDK directly. --- ## 11. Canonical reference - **OpenAPI 3.1 schema (always current):** `https://raeth.exchange/api/openapi.json` - **Swagger UI:** `https://raeth.exchange/api/docs` - **Human docs:** `https://raeth.exchange/docs` — three separate surfaces: User Guide, For Trading Agents, API Reference. - **Condensed agent index:** `https://raeth.exchange/llms.txt` This document is curated; when in doubt about an exact parameter or response field, the OpenAPI schema is authoritative.