# HardBasis API — full documentation > Sats-native perpetual-futures API for algorithmic and AI-agent traders: sign up, fund, trade, and withdraw over one Bitcoin rail with no human in the loop. Money crosses the wire as decimal STRINGS — never JSON numbers. Testnet only today. > Numbers are resolved from the source constants, not typed. --- # Overview — https://docs.hardbasis.com/api/ # API overview The HardBasis API is a sats-native perpetual-futures API built for algorithmic and AI-agent traders: an agent can sign up, fund, trade, and withdraw over one Bitcoin rail with no human in the loop. This section documents the public surface. Start with the **[Quickstart](/api/quickstart)** — the zero-human-step loop, end to end, copy-pasteable. Then the **[REST reference](/api/reference/)** (generated from the spec) and the **[WebSocket](/api/websocket)** protocol. ## Environments Testnet is the only public environment today; production is gated. Test sats have no value. | Environment | Base URL | | ----------- | ------------------------------- | | Testnet | `https://testnet.hardbasis.com` | | Local dev | `http://localhost:8787` | Do not hardcode the environment from the hostname. `GET /deployment` returns `{"deployment":"testnet"}` or `{"deployment":"prod"}` — use it to drive any "this is testnet, funds have no value" banner your client shows. ## Machine-readable spec The API is generated from typed contracts, and so is this reference. The canonical machine-readable description is served at **[`/openapi.json`](/openapi.json)** (OpenAPI 3.1) — point an SDK generator or a try-it tool at that URL. An [`llms.txt`](/llms.txt) index (and a full-text [`llms-full.txt`](/llms-full.txt)) is served for agents. ## The public-surface promise Everything here describes the **public** API — the `/v1/*` account and market endpoints, plus `/healthz` and `/deployment`. Admin and operational surfaces are not part of this reference and are not reachable from it; the reference is generated from the public spec alone, enforced by test. ## Money is never a JS number Every monetary value — balances, prices, funding rates, timestamps, contract counts — crosses the wire as a **decimal string**, never a JSON number, because a double cannot hold a satoshi-precise integer. Read [Wire encoding](/api/wire-encoding) before you parse anything: it is the one thing a client must get right. --- # Quickstart for agents — https://docs.hardbasis.com/api/quickstart # Quickstart for agents The whole point of HardBasis's API is that an agent can go from nothing to a live, hedged, self-limiting trading loop **without a human step**. This page is that loop, end to end, as copy-pasteable `curl`. Every response is a decimal- string JSON body (see [Wire encoding](/api/wire-encoding)); pipe through `jq`. This whole sequence was run end to end against the deployed testnet gateway on 2026-08-11 (gateway SHA `94f4623`), withdrawal included via the **Spark path** (settled to `state:"paid"`). A Lightning payout is not exercisable on testnet's sim rail — see step 8 — so the withdrawal example uses Spark. Set your base URL once: ```bash export HB=https://testnet.hardbasis.com ``` ## 1. Sign up (testnet self-serve) Testnet lets you mint an account with no operator action. Signup returns a full-scope **master key** — shown exactly once, so save it. ```bash curl -fsS -X POST "$HB/v1/signup" \ -H "idempotency-key: $(uuidgen)" \ -H "content-type: application/json" -d '{}' | jq . # → { "accountId": "...", "apiKey": "hb_...", ... } export MASTER=hb_... # from the response; shown once, never recoverable ``` ## 2. Fund it from the faucet The testnet faucet credits a fixed amount through the normal deposit path. ```bash curl -fsS -X POST "$HB/v1/faucet" \ -H "x-api-key: $MASTER" -H "idempotency-key: $(uuidgen)" | jq . ``` ## 3. See the market Read the live market id straight from the list and keep it — every later call uses it, so nothing can drift from what the venue actually serves: ```bash curl -fsS "$HB/v1/markets" | jq '.[0] | {marketId, status, tickSizeQ8, priceDp}' export MARKET=$(curl -fsS "$HB/v1/markets" | jq -r '.[0].marketId') # e.g. btc-usd ``` `priceDp` tells you how many decimal places prices carry; `tickSizeQ8` is the minimum price increment (a `q8` fixed-point integer — [Wire encoding](/api/wire-encoding)). ## 4. Stream prices (optional but recommended) Prices, per-market stats, and your account events come over one WebSocket. See [WebSocket](/api/websocket) for the full protocol. ```bash # with websocat: subscribe to the oracle feed, then the auth'd account channel websocat "wss://testnet.hardbasis.com/v1/ws" <<'WS' {"op":"auth","apiKey":"REPLACE_MASTER"} {"op":"subscribe","channel":"prices","feedId":"btc-usd"} WS ``` ## 5. Place an order Size is an integer count of $1-notional `contracts` (a decimal string). The `idempotency-key` **is** the order id, so a retry is safe. ```bash curl -fsS -X POST "$HB/v1/orders" \ -H "x-api-key: $MASTER" -H "idempotency-key: $(uuidgen)" \ -H "content-type: application/json" \ -d "{\"marketId\":\"$MARKET\",\"side\":\"buy\",\"type\":\"market\",\"contracts\":\"10\"}" | jq . ``` Check the fill and your position: ```bash curl -fsS "$HB/v1/account" -H "x-api-key: $MASTER" | jq . curl -fsS "$HB/v1/positions" -H "x-api-key: $MASTER" | jq . ``` ## 6. Delegate a trade-only key for the bot loop Put the master key cold and run the loop on a **`trade`+`read`** key. A stolen delegate can lose you a position, never your balance — it cannot withdraw. See [Authentication](/api/authentication). ```bash curl -fsS -X POST "$HB/v1/sessions" \ -H "x-api-key: $MASTER" -H "idempotency-key: $(uuidgen)" \ -H "content-type: application/json" \ -d '{"label":"bot","scopes":["read","trade"]}' | jq . export BOT=hb_... # the delegate key, shown once ``` ## 7. Arm the dead-man's switch Before the loop runs unattended, arm `cancel-all-after`: if your bot stops calling in, the engine cancels its resting orders on its own. Re-arm on a timer; disarm by sending a zero deadline. ```bash curl -fsS -X POST "$HB/v1/cancel-all-after" \ -H "x-api-key: $BOT" -H "idempotency-key: $(uuidgen)" \ -H "content-type: application/json" -d '{"timeoutMs":"60000"}' | jq . ``` ## 8. Withdraw Withdrawals need a `withdraw`-scoped key and always work — they never gate on trading state or halts. **Always quote first.** The quote (a `trade`-scoped call) returns the fee before you commit and, for a Spark destination, whether the address is **first-seen** — a wrong Spark address is irreversible, so a first-seen destination at or above `confirmThresholdMsat` sets `confirmRequired` and returns a `confirmToken` you must echo into the withdrawal. ```bash # Spark (the native rail): quote by address + amount, then withdraw. curl -fsS -X POST "$HB/v1/withdrawals/quote" \ -H "x-api-key: $MASTER" -H "content-type: application/json" \ -d '{"toAddress":"sprt1q…","amountMsat":"50000000"}' | jq . # → { "path":"spark","feeMsat":"…","firstSeen":true,"confirmRequired":false, # "confirmThresholdMsat":"…" } # first-seen ≥ threshold also returns confirmToken curl -fsS -X POST "$HB/v1/withdrawals" \ -H "x-api-key: $MASTER" -H "idempotency-key: $(uuidgen)" \ -H "content-type: application/json" \ -d '{"toAddress":"sprt1q…","amountMsat":"50000000"}' | jq . # → { "railRef":"…","queued":false } ``` Confirm it settled: ```bash curl -fsS "$HB/v1/withdrawals?limit=1" -H "x-api-key: $MASTER" \ | jq '.withdrawals[0] | {state, path, railRef, paidTsMs}' # → { "state":"paid","path":"spark", … } ``` The same endpoint takes a bolt11 instead of an address — `{"invoice":"lnbc…"}` on both the quote and the withdrawal. A real deployment on the Spark rail settles either path. On **testnet's sim rail a Lightning payout is not exercisable** (the sim rail exposes no payable-invoice source over the API, so every bolt11 comes back `unknown payout invoice`), which is why the runnable example above uses the native Spark path. That is the whole loop: **signup → faucet → market → subscribe → order → delegate a trade-only key → arm cancel-all-after → withdraw**, no human in it. Endpoint-by-endpoint details — every field, every response — are in the generated [REST reference](/api/reference/). --- # Wire encoding — https://docs.hardbasis.com/api/wire-encoding # Wire encoding The one thing a client must get right. **Every bigint crosses the wire as a decimal string** — money, prices, rates, timestamps, contract counts. A JSON number is never used for a monetary value, because an IEEE double cannot hold a satoshi-precise integer without silently rounding it. So a field's JSON type is `string`, and it is validated against the pattern `^-?\d+$`. Parse it into a big-integer type (`BigInt` in JS, `int`/`decimal` elsewhere) — never `parseFloat`. ## Units: read them off the field Each numeric field declares its unit in the machine-readable spec as `x-hardbasis-unit`, and the [REST reference](/api/reference/) prints that unit beside every field. There are a handful: | unit | meaning | example | | ----------- | ---------------------------------------------------------- | -------------------------------- | | `msat` | money, in millisatoshi | `"1000"` = one sat | | `sats` | money, in whole satoshis | `"50"` = fifty sats | | `q8` | a price, fixed-point 1e8 | `"6724150000000"` = `$67,241.50` | | `q9` | a rate, fixed-point 1e9 | `"300000"` = three bps | | `ms` | a timestamp or duration, in milliseconds | `"1712345678000"` | | `seq` | a monotonic sequence number / pagination cursor | `"41207"` | | `contracts` | an integer count of $1-notional contracts | `"10"` | | `int` | a dimensionless integer (decimal places, leverage, counts) | `"2"` | Conversions are exact integer arithmetic: ```text 1 sat = 1000 msat 1 BTC = 100000000 sats = 100000000000 msat price = q8_value / 1e8 # dollars rate = q9_value / 1e9 # fraction; ×100 for %, ×10000 for bps ``` An SDK generated from `/openapi.json` keys off `x-hardbasis-unit` to emit a big-integer type rather than a float, so a well-generated client cannot make the number mistake. ## Nulls and absence `null` is emitted for a field that is present but has no value; a field that does not apply is omitted. A rolling aggregate that has no data yet is served as explicit `null`, never zero — absence over invention. ## Timestamps `ms` timestamps are milliseconds since the Unix epoch, UTC, as decimal strings. There is no timezone in the wire value; render in the reader's zone client-side. --- # Authentication — https://docs.hardbasis.com/api/authentication # Authentication Every `/v1/*` account endpoint takes an API key in the `x-api-key` header. On the WebSocket, send `{"op":"auth","apiKey":"hb_…"}` before subscribing to the `account` channel. ## Keys are shown once A key is stored as a SHA-256 digest, never in the clear. The value crosses the wire at mint time and lives only in your client; the server keeps the hash. So a key is **shown exactly once** — at creation — and cannot be recovered, only replaced. Save it when you mint it. Keys are provisioned two ways: `POST /v1/signup` (testnet self-serve, no operator action) and, for operators, an admin route. Both mint a full-scope **master key**. ## Scopes Each key carries a set of capabilities. They are **independent, not a ladder** — `trade` does not imply `read`. Ask for exactly the set you need. | scope | grants | | ---------------- | ----------------------------------------------------------------------- | | `read` | account / positions / orders / history reads · the WS `account` channel | | `trade` | orders, cancels, withdrawal quotes, deposit invoices, referral ops | | `withdraw` | `POST /v1/withdrawals` | | `admin-sessions` | mint and revoke sessions (`POST /v1/sessions`, `/v1/sessions/revoke`) | A call whose key lacks the route's scope is refused with `403 {"code":"insufficient_scope"}`. Scopes restrict **which** key may act, never **when**: a `withdraw` key still withdraws in every halt mode. ## The delegate-key pattern This is the intended shape for an agent, and the reason scopes exist: 1. **Sign up**, get the master key, and put it cold. 2. **Mint a `["read","trade"]` delegate** with `POST /v1/sessions` for the bot loop. 3. Run unattended on the delegate. A stolen delegate key can lose you a position; it cannot touch your balance, because it has no `withdraw` scope. Minting intersects the requested scopes with the minter's own, so a key can never mint a more capable key than itself. Minting is a privileged operation: it draws the order rate budget, and an account may hold at most a fixed number of live keys at once (see [Rate limits](/api/rate-limits)). Beyond that, revoke an unused session before minting another. ## Bearer-token threat model The key is a bearer credential — whoever holds it can act within its scopes. Treat it like a secret: never log it, never put it in a URL, scope it down for anything unattended, and rotate by minting a new key and revoking the old. There is no password or account-recovery flow behind a key; possession is the credential. --- # Rate limits — https://docs.hardbasis.com/api/rate-limits # Rate limits Requests are metered with token buckets. There are **three independent budgets** — exhausting one never blocks another: - **`read`** — reads and cheap non-order mutations. - **`order`** — order entry (place, cancel, mint a session, `cancel-all-after`). - **`withdraw`** — `POST /v1/withdrawals` alone. The payout path gets its own budget so a busy trading loop can never stand between an account and its money. ## Two levels: per-key and per-account Each budget is enforced at **two** levels. The **per-key** bucket isolates one delegate from another. The **per-account** bucket is a second ceiling _above_ it, drawn on the account across every key it holds — so minting more keys cannot multiply your throughput. The numbers below are the testnet starting values, in requests per minute, with the burst a quiet bucket may spend at once. They are policy served from the same config `GET /v1/limits` reports — always trust that endpoint over this table. | budget | per-key / min | burst | per-account / min | | ---------- | ---------------------------------- | --------------------------------- | -------------------------------------- | | `read` | 600 | 120 | 3,000 | | `order` | 60 | 20 | 180 | | `withdraw` | 10 | 5 | 20 | ## Read your own limits `GET /v1/limits` returns your tier's budgets and how much of each remains — the authoritative, live view. A static page can go stale; that endpoint cannot. ## Headers on every metered response | header | meaning | | ----------------------- | ----------------------------------------- | | `X-RateLimit-Limit` | the binding budget's per-minute limit | | `X-RateLimit-Remaining` | tokens left in the binding budget | | `X-RateLimit-Reset` | epoch seconds when the budget next admits | | `Retry-After` | seconds to wait, sent on a `429` | A refused request returns `429` with a machine `code` and `Retry-After`. A well-behaved client reads `X-RateLimit-Remaining` and paces itself rather than walking into the `429`. ## The live-key cap Independently of the buckets, an account may hold at most 20 live keys at once. Minting past that returns `403 {"code":"session_limit"}`; revoke an unused session first. Together with the per-account ceiling this closes the "mint more keys to get more budget" bypass. ## One gateway, in-memory buckets (testnet) On testnet the buckets are per-gateway and in-memory. That is exactly the single-gateway deployment testnet runs; horizontal scale needs a shared bucket store and is tracked, not pretended. --- # REST reference — https://docs.hardbasis.com/api/reference/ # REST reference _Documents gateway spec version `1.4.0`._ Generated from the public OpenAPI spec; the machine-readable source is served at [`/openapi.json`](/openapi.json) (SDK generators and try-it tools read that). Every money, price, rate, duration and count field crosses the wire as a decimal **string**, and its unit (`x-hardbasis-unit`) is shown beside it — see [Wire encoding](/api/wire-encoding). Base URL and auth are on the [Overview](/api/) and [Authentication](/api/authentication) pages. ## System ### `GET /healthz` Liveness + DB reachability probe. **Auth:** none (public) **Responses** - **200** — OK - `ok` · boolean · required - `db` · boolean · required - `tsMs` · string · unit `ms` · required - **503** — Service Unavailable - `ok` · boolean · required - `db` · boolean · required - `tsMs` · string · unit `ms` · required Example `200` response: ```json { "ok": true, "db": true, "tsMs": "100000" } ``` --- ### `GET /deployment` Deployment posture (prod/testnet) — drives the testnet banner. **Auth:** none (public) **Responses** - **200** — OK - `deployment` · enum(`prod`, `testnet`) · required Example `200` response: ```json { "deployment": "prod" } ``` --- ### `GET /v1/limits` The caller's rate-limit tier + per-budget config and remaining. **Auth:** API key (`x-api-key`) **Responses** - **200** — OK - `tier` · string · required - `budgets` · map(enum(`read`, `order`, `withdraw`) → object) · required - each enum(`read`, `order`, `withdraw`) key → object: - `limitPerMin` · integer · required - `burst` · integer · required - `remaining` · integer · required - **401** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **403** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **429** — 429 — a token bucket is exhausted. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional Example `200` response: ```json { "tier": "string", "budgets": { "read": { "limitPerMin": 0, "burst": 0, "remaining": 0 } } } ``` ## Proof of Reserves ### `GET /por` Proof-of-Reserves snapshot (figures, staleness, liability root, vault exposure). **Auth:** none (public) **Responses** - **200** — OK - `tsMs` · string · unit `ms` · required - `ageMs` · string · unit `ms` · required - `stale` · boolean · required - `staleAfterMs` · string · unit `ms` · required - `status` · enum(`clean`, `warn`, `page`) · required - `driftMsat` · string · unit `msat` · required - `consolidationVerified` · boolean · required - `snapshot` · any · required - `history` · object[] · required - `tsMs` · string · unit `ms` · required - `status` · string · required - `driftMsat` · string · unit `msat` · required - `liability` · object · required - `rootHash` · string · required - `totalMsat` · string · unit `msat` · required - `leafCount` · integer · required - `rootSaltHex` · string · required - `tsMs` · string · unit `ms` · required - `vaultExposure` · object · required - `netPctOfEquityQ9` · string \| null · unit `q9` · required - `quantumQ9` · string · unit `q9` · required - `tsMs` · string · unit `ms` · required - `consolidationVerified` · boolean · required - **404** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional Example `200` response: ```json { "tsMs": "100000", "ageMs": "100000", "stale": true, "staleAfterMs": "100000", "status": "clean", "driftMsat": "100000", "consolidationVerified": true, "snapshot": null, "history": [ { "tsMs": "100000", "status": "string", "driftMsat": "100000" } ], "liability": { "rootHash": "string", "totalMsat": "100000", "leafCount": 0, "rootSaltHex": "string", "tsMs": "100000" }, "vaultExposure": { "netPctOfEquityQ9": "100000", "quantumQ9": "100000", "tsMs": "100000", "consolidationVerified": true } } ``` --- ### `GET /por/dashboard` Proof-of-Reserves dashboard (HTML, engine-served). **Auth:** none (public) **Responses** - **200** — OK --- ### `GET /v1/por/proof` Merkle inclusion proof of the account's liability in the current PoR tree. **Auth:** API key (`x-api-key`) **Responses** - **200** — OK - `rootHash` · string · required - `totalMsat` · string · unit `msat` · required - `rootSaltHex` · string · required - `treeTsMs` · string · unit `ms` · required - `leaf` · object · required - `accountTag` · string · required - `balanceMsat` · string · unit `msat` · required - `index` · integer · required - `path` · object[] · required - `siblingHash` · string · required - `siblingSumMsat` · string · unit `msat` · required - `siblingOnLeft` · boolean · required - **401** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **403** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **404** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **429** — 429 — a token bucket is exhausted. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional Example `200` response: ```json { "rootHash": "string", "totalMsat": "100000", "rootSaltHex": "string", "treeTsMs": "100000", "leaf": { "accountTag": "string", "balanceMsat": "100000", "index": 0 }, "path": [ { "siblingHash": "string", "siblingSumMsat": "100000", "siblingOnLeft": true } ] } ``` ## Markets ### `GET /v1/markets` All markets (skew quantized §13). **Auth:** none (public) **Responses** - **200** — OK Example `200` response: ```json [ { "marketId": "string", "underlying": "string", "contractType": "inverse", "oracleFeedId": "string", "quantoMultiplierMsat": "100000", "status": "live", "tickSizeQ8": "100000", "priceDp": "100000", "minOrderContracts": "100000", "maxOrderContracts": "100000", "takerFeeQ9": "100000", "maxLeverage": "100000", "imRateQ9": "100000", "mmRateQ9": "100000", "baseSpreadQ9": "100000", "fundingRateHourlyQ9": "100000", "nextFundingTsMs": "100000", "oracleStalenessMs": "100000", "openInterestContracts": "100000", "skewQ9": "100000", "lastPrice": { "midQ8": "100000", "tsMs": "100000" } } ] ``` --- ### `GET /v1/markets/{id}` One market. **Auth:** none (public) **Parameters** - `id` (path), required — string **Responses** - **200** — OK - `marketId` · string · required - `underlying` · string · required - `contractType` · enum(`inverse`, `quanto`) · required - `oracleFeedId` · string · required - `quantoMultiplierMsat` · string \| null · unit `msat` · required - `status` · enum(`live`, `reduce_only`, `halted`) · required - `tickSizeQ8` · string · unit `q8` · required - `priceDp` · string · unit `int` · required - `minOrderContracts` · string · unit `contracts` · required - `maxOrderContracts` · string · unit `contracts` · required - `takerFeeQ9` · string · unit `q9` · required - `maxLeverage` · string · unit `int` · required - `imRateQ9` · string · unit `q9` · required - `mmRateQ9` · string · unit `q9` · required - `baseSpreadQ9` · string · unit `q9` · required - `fundingRateHourlyQ9` · string · unit `q9` · required - `nextFundingTsMs` · string · unit `ms` · required - `oracleStalenessMs` · string · unit `ms` · required - `openInterestContracts` · string · unit `contracts` · required - `skewQ9` · string · unit `q9` · required - `lastPrice` · object \| null · required - **404** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional Example `200` response: ```json { "marketId": "string", "underlying": "string", "contractType": "inverse", "oracleFeedId": "string", "quantoMultiplierMsat": "100000", "status": "live", "tickSizeQ8": "100000", "priceDp": "100000", "minOrderContracts": "100000", "maxOrderContracts": "100000", "takerFeeQ9": "100000", "maxLeverage": "100000", "imRateQ9": "100000", "mmRateQ9": "100000", "baseSpreadQ9": "100000", "fundingRateHourlyQ9": "100000", "nextFundingTsMs": "100000", "oracleStalenessMs": "100000", "openInterestContracts": "100000", "skewQ9": "100000", "lastPrice": { "midQ8": "100000", "tsMs": "100000" } } ``` --- ### `GET /v1/markets/{id}/funding` Last 100 funding boundaries (skew quantized §E). **Auth:** none (public) **Parameters** - `id` (path), required — string **Responses** - **200** — OK Example `200` response: ```json [ { "tsMs": "100000", "rateQ9": "100000", "skewQ9": "100000" } ] ``` --- ### `GET /v1/markets/{id}/stats24h` 24h volume + traded hi/lo, derived from fills. **Auth:** none (public) **Parameters** - `id` (path), required — string **Responses** - **200** — OK - `marketId` · string · required - `windowMs` · string · unit `ms` · required - `basis` · string · required - `fills` · integer · required - `volumeContracts` · string · unit `contracts` · required - `hiQ8` · string \| null · unit `q8` · required - `loQ8` · string \| null · unit `q8` · required - `truncated` · boolean · required - **404** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional Example `200` response: ```json { "marketId": "string", "windowMs": "100000", "basis": "string", "fills": 0, "volumeContracts": "100000", "hiQ8": "100000", "loQ8": "100000", "truncated": true } ``` --- ### `GET /v1/markets/{id}/candles` OHLC candles (interval 60, 3600 or 86400). **Auth:** none (public) **Parameters** - `id` (path), required — string - `interval` (query) — enum(`60`, `3600`, `86400`) - `limit` (query) — string **Responses** - **200** — OK - `interval` · number · required - `candles` · object[] · required - `tsMs` · string · unit `ms` · required - `oQ8` · string · unit `q8` · required - `hQ8` · string · unit `q8` · required - `lQ8` · string · unit `q8` · required - `cQ8` · string · unit `q8` · required - `prints` · integer · required - `seeded` · boolean · optional - `volumeContracts` · string · unit `contracts` · optional - **404** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional Example `200` response: ```json { "interval": 0, "candles": [ { "tsMs": "100000", "oQ8": "100000", "hQ8": "100000", "lQ8": "100000", "cQ8": "100000", "prints": 0, "seeded": true, "volumeContracts": "100000" } ] } ``` --- ### `GET /v1/public/ticker` Landing-page ticker (microcached). **Auth:** none (public) **Responses** - **200** — OK - `asOfMs` · string · unit `ms` · required - `tickers` · object[] · required - `marketId` · string · required - `last` · object \| null · required - `stalenessMs` · string · unit `ms` · required Example `200` response: ```json { "asOfMs": "100000", "tickers": [ { "marketId": "string", "last": { "midQ8": "100000", "tsMs": "100000" }, "stalenessMs": "100000" } ] } ``` ## Account ### `GET /v1/account` Account balances, rail address, per-path deposit fees. **Auth:** API key (`x-api-key`) **Responses** - **200** — OK - `accountId` · string · required - `userId` · string · required - `railAddress` · string · required - `autoSweepThresholdMsat` · string \| null · unit `msat` · required - `freeMsat` · string · unit `msat` · required - `reservedMsat` · string · unit `msat` · required - `depositFees` · object[] · required - `path` · enum(`spark`, `l1`, `lightning`) · required - `description` · string · required - `recommendedMinimumSats` · string · unit `sats` · optional - **401** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **403** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **429** — 429 — a token bucket is exhausted. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional Example `200` response: ```json { "accountId": "string", "userId": "string", "railAddress": "string", "autoSweepThresholdMsat": "100000", "freeMsat": "100000", "reservedMsat": "100000", "depositFees": [ { "path": "spark", "description": "string", "recommendedMinimumSats": "100000" } ] } ``` --- ### `GET /v1/positions` Open positions for the account. **Auth:** API key (`x-api-key`) **Responses** - **200** — OK - **401** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **403** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **429** — 429 — a token bucket is exhausted. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional Example `200` response: ```json [ { "marketId": "string", "side": "long", "contracts": "100000", "entryPriceQ8": "100000", "marginMsat": "100000", "fundingClockTsMs": "100000" } ] ``` --- ### `GET /v1/fills` Fills history (cursor-paginated). **Auth:** API key (`x-api-key`) **Parameters** - `limit` (query) — string - `beforeSeq` (query) — string **Responses** - **200** — OK - `fills` · object[] · required - `orderId` · string · required - `accountId` · string · required - `marketId` · string · required - `side` · enum(`buy`, `sell`) · required - `closedContracts` · string · unit `contracts` · required - `openedContracts` · string · unit `contracts` · required - `execPriceQ8` · string · unit `q8` · required - `oracle` · object · required - `spreadQ9` · string · unit `q9` · required - `tradeNotionalMsat` · string · unit `msat` · required - `feeMsat` · string · unit `msat` · required - `impactMsat` · string · unit `msat` · required - `rebateDivertedMsat` · string · unit `msat` · required - `realizedPnlMsat` · string · unit `msat` · required - `fundingSettledMsat` · string · unit `msat` · required - `releasedMarginMsat` · string · unit `msat` · required - `reservedMarginMsat` · string · unit `msat` · required - `deficitMsat` · string · unit `msat` · required - `referral` · object \| null · optional - `positionAfter` · object \| null · required - `seq` · string · unit `seq` · required - `tsMs` · string · unit `ms` · required - `nextBeforeSeq` · string \| null · unit `seq` · required - **401** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **403** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **429** — 429 — a token bucket is exhausted. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional Example `200` response: ```json { "fills": [ { "orderId": "string", "accountId": "string", "marketId": "string", "side": "buy", "closedContracts": "100000", "openedContracts": "100000", "execPriceQ8": "100000", "oracle": { "midQ8": "100000", "confQ9": "100000", "tsMs": "100000" }, "spreadQ9": "100000", "tradeNotionalMsat": "100000", "feeMsat": "100000", "impactMsat": "100000", "rebateDivertedMsat": "100000", "realizedPnlMsat": "100000", "fundingSettledMsat": "100000", "releasedMarginMsat": "100000", "reservedMarginMsat": "100000", "deficitMsat": "100000", "referral": { "code": "string", "grossFeeMsat": "100000", "discountMsat": "100000", "rewardMsat": "100000", "policyVersion": "100000" }, "positionAfter": { "side": "long", "contracts": "100000", "entryPriceQ8": "100000", "marginMsat": "100000" }, "seq": "100000", "tsMs": "100000" } ], "nextBeforeSeq": "100000" } ``` ## Deposits ### `POST /v1/deposit-invoices` Create a Lightning deposit invoice. **Auth:** API key (`x-api-key`) **Parameters** - `idempotency-key` (header), required — string **Request body** - `amountMsat` · string · unit `msat` · optional - `expirySeconds` · integer · optional - `memo` · string · optional ```json { "amountMsat": "100000", "expirySeconds": 0, "memo": "string" } ``` **Responses** - **201** — Created - `invoice` · string · required - `paymentRef` · string · required - `expiresAtMs` · string · unit `ms` · required - `depositFees` · object[] · required - `path` · enum(`spark`, `l1`, `lightning`) · required - `description` · string · required - `recommendedMinimumSats` · string · unit `sats` · optional - **401** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **403** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **429** — 429 — a token bucket is exhausted. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional Example `201` response: ```json { "invoice": "string", "paymentRef": "string", "expiresAtMs": "100000", "depositFees": [ { "path": "spark", "description": "string", "recommendedMinimumSats": "100000" } ] } ``` --- ### `GET /v1/deposits` Deposit history (cursor-paginated). **Auth:** API key (`x-api-key`) **Parameters** - `limit` (query) — string - `beforeSeq` (query) — string **Responses** - **200** — OK - `deposits` · object[] · required - `accountId` · string · required - `amountMsat` · string · unit `msat` · required - `feeMsat` · string · unit `msat` · required - `railRef` · string · required - `seq` · string · unit `seq` · required - `tsMs` · string · unit `ms` · required - `nextBeforeSeq` · string \| null · unit `seq` · required - **401** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **403** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **429** — 429 — a token bucket is exhausted. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional Example `200` response: ```json { "deposits": [ { "accountId": "string", "amountMsat": "100000", "feeMsat": "100000", "railRef": "string", "seq": "100000", "tsMs": "100000" } ], "nextBeforeSeq": "100000" } ``` ## Sessions & keys ### `GET /v1/sessions` The account's live/revoked sessions (never the key value). **Auth:** API key (`x-api-key`) **Responses** - **200** — OK - `tsMs` · string · unit `ms` · required - `sessions` · object[] · required - `keyId` · string · required - `accountId` · string · required - `label` · string · required - `scopes` · enum(`read`, `trade`, `withdraw`, `admin-sessions`)[] · required - `uaClass` · string \| null · required - `ipCountryFirst` · string \| null · required - `createdTsMs` · string · unit `ms` · required - `lastSeenTsMs` · string \| null · unit `ms` · required - `revokedTsMs` · string \| null · unit `ms` · required - `revokedBy` · string \| null · required - `revokeReason` · string \| null · required - `current` · string · unit `int` · required - **401** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **403** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **429** — 429 — a token bucket is exhausted. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional Example `200` response: ```json { "tsMs": "100000", "sessions": [ { "keyId": "string", "accountId": "string", "label": "string", "scopes": [ "read" ], "uaClass": "string", "ipCountryFirst": "string", "createdTsMs": "100000", "lastSeenTsMs": "100000", "revokedTsMs": "100000", "revokedBy": "string", "revokeReason": "string", "current": "100000" } ] } ``` --- ### `POST /v1/sessions` Mint a new session key (delegate key primitive). **Auth:** API key (`x-api-key`) **Parameters** - `idempotency-key` (header), required — string **Request body** - `label` · string · optional - `scopes` · enum(`read`, `trade`, `withdraw`, `admin-sessions`)[] · optional ```json { "label": "string", "scopes": [ "read" ] } ``` **Responses** - **201** — Created - `apiKey` · string · optional - `keyId` · string · required - `scopes` · enum(`read`, `trade`, `withdraw`, `admin-sessions`)[] · required - **401** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **403** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **429** — 429 — a token bucket is exhausted. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional Example `201` response: ```json { "apiKey": "string", "keyId": "string", "scopes": [ "read" ] } ``` --- ### `POST /v1/sessions/revoke` Revoke one session or all-but-current. **Auth:** API key (`x-api-key`) **Parameters** - `idempotency-key` (header), required — string **Request body** - `keyId` · string · optional - `allButCurrent` · boolean · optional ```json { "keyId": "string", "allButCurrent": true } ``` **Responses** - **200** — OK - `revoked` · string[] · required - **401** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **403** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **404** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **429** — 429 — a token bucket is exhausted. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional Example `200` response: ```json { "revoked": [ "string" ] } ``` ## Liquidations ### `GET /v1/liquidations` Liquidation history (cursor-paginated). **Auth:** API key (`x-api-key`) **Parameters** - `limit` (query) — string - `beforeSeq` (query) — string **Responses** - **200** — OK - `liquidations` · object[] · required - `accountId` · string · required - `positionId` · string · required - `marketId` · string · required - `side` · enum(`long`, `short`) · required - `closedContracts` · string · unit `contracts` · required - `partial` · boolean · required - `execPriceQ8` · string · unit `q8` · required - `oracle` · object · required - `spreadQ9` · string · unit `q9` · required - `realizedPnlMsat` · string · unit `msat` · required - `fundingSettledMsat` · string · unit `msat` · required - `penaltyMsat` · string · unit `msat` · required - `remainderMsat` · string · unit `msat` · optional - `deficitMsat` · string · unit `msat` · required - `insuranceCoveredMsat` · string · unit `msat` · required - `adlTriggered` · boolean · required - `remainingContracts` · string · unit `contracts` · required - `remainingMarginMsat` · string · unit `msat` · required - `seq` · string · unit `seq` · required - `tsMs` · string · unit `ms` · required - `nextBeforeSeq` · string \| null · unit `seq` · required - **401** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **403** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **429** — 429 — a token bucket is exhausted. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional Example `200` response: ```json { "liquidations": [ { "accountId": "string", "positionId": "string", "marketId": "string", "side": "long", "closedContracts": "100000", "partial": true, "execPriceQ8": "100000", "oracle": { "midQ8": "100000", "confQ9": "100000", "tsMs": "100000" }, "spreadQ9": "100000", "realizedPnlMsat": "100000", "fundingSettledMsat": "100000", "penaltyMsat": "100000", "remainderMsat": "100000", "deficitMsat": "100000", "insuranceCoveredMsat": "100000", "adlTriggered": true, "remainingContracts": "100000", "remainingMarginMsat": "100000", "seq": "100000", "tsMs": "100000" } ], "nextBeforeSeq": "100000" } ``` ## Funding ### `GET /v1/funding` Funding-flow history (cursor-paginated). **Auth:** API key (`x-api-key`) **Parameters** - `limit` (query) — string - `beforeSeq` (query) — string **Responses** - **200** — OK - `funding` · object[] · required - `seq` · string · unit `seq` · required - `boundaryTsMs` · string · unit `ms` · required - `marketId` · string · required - `rateQ9` · string · unit `q9` · required - `flowMsat` · string · unit `msat` · required - `paidMsat` · string · unit `msat` · required - `positionId` · string · required - `nextBeforeSeq` · string \| null · unit `seq` · required - **401** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **403** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **429** — 429 — a token bucket is exhausted. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional Example `200` response: ```json { "funding": [ { "seq": "100000", "boundaryTsMs": "100000", "marketId": "string", "rateQ9": "100000", "flowMsat": "100000", "paidMsat": "100000", "positionId": "string" } ], "nextBeforeSeq": "100000" } ``` ## Orders ### `GET /v1/orders` Orders (optionally ?status= filtered), or ?state=resting|armed triggers (cursor-paginated). **Auth:** API key (`x-api-key`) **Parameters** - `state` (query) — enum(`resting`, `armed`) - `status` (query) — enum(`accepted`, `filled`, `canceled`, `rejected`)[] - `limit` (query) — string - `beforeSeq` (query) — string **Responses** - **200** — OK - **401** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **402** — 402 — reserved for L402/Lightning-metered access (PAPI-8). Not returned today; documented so it lands additively. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **403** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **429** — 429 — a token bucket is exhausted. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional --- ### `POST /v1/orders` Submit an order, bracket, or trigger (idempotency key = order id). **Auth:** API key (`x-api-key`) **Parameters** - `idempotency-key` (header), required — string **Request body** - `marketId` · string · required - `side` · enum(`buy`, `sell`) · required - `type` · enum(`market`, `limit`, `stop_market`, `take_profit_market`) · required - `contracts` · string · unit `contracts` · required - `limitPriceQ8` · string · unit `q8` · optional - `triggerPriceQ8` · string · unit `q8` · optional - `maxSlippageQ9` · string · unit `q9` · optional - `reduceOnly` · boolean · required - `trigger` · object · optional - `kind` · enum(`entry`, `stop`, `take_profit`) · required - `level` · string · unit `q8` · required - `bracket` · object · optional - `entryKind` · enum(`market`, `trigger`) · required - `entryLevel` · string · unit `q8` · optional - `stopLevel` · string · unit `q8` · optional - `takeProfitLevel` · string · unit `q8` · optional - `stopContracts` · string · unit `contracts` · optional - `takeProfitContracts` · string · unit `contracts` · optional ```json { "marketId": "string", "side": "buy", "type": "market", "contracts": "100000", "limitPriceQ8": "100000", "triggerPriceQ8": "100000", "maxSlippageQ9": "100000", "reduceOnly": true, "trigger": { "kind": "entry", "level": "100000" }, "bracket": { "entryKind": "market", "entryLevel": "100000", "stopLevel": "100000", "takeProfitLevel": "100000", "stopContracts": "100000", "takeProfitContracts": "100000" } } ``` **Responses** - **201** — Created - `orderId` · string · required - `status` · enum(`accepted`, `filled`, `canceled`, `rejected`, `resting`, `working`) · required - **400** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **401** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **402** — 402 — reserved for L402/Lightning-metered access (PAPI-8). Not returned today; documented so it lands additively. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **403** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **429** — 429 — a token bucket is exhausted. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional Example `201` response: ```json { "orderId": "string", "status": "accepted" } ``` --- ### `GET /v1/orders/{id}` One order's status. **Auth:** API key (`x-api-key`) **Parameters** - `id` (path), required — string **Responses** - **200** — OK - `orderId` · string · required - `marketId` · string · required - `side` · enum(`buy`, `sell`) · required - `type` · enum(`market`, `limit`, `stop_market`, `take_profit_market`) · required - `contracts` · string · unit `contracts` · required - `status` · enum(`accepted`, `filled`, `canceled`, `rejected`) · required - `reason` · string \| null · required - **401** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **403** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **404** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **429** — 429 — a token bucket is exhausted. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional Example `200` response: ```json { "orderId": "string", "marketId": "string", "side": "buy", "type": "market", "contracts": "100000", "status": "accepted", "reason": "string" } ``` --- ### `DELETE /v1/orders/{id}` Cancel an order / trigger / bracket by id. **Auth:** API key (`x-api-key`) **Parameters** - `id` (path), required — string - `idempotency-key` (header), required — string **Responses** - **200** — OK - `orderId` · string · required - `canceled` · boolean · required - **401** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **403** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **429** — 429 — a token bucket is exhausted. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional Example `200` response: ```json { "orderId": "string", "canceled": true } ``` --- ### `POST /v1/cancel-all-after` Dead-man's-switch: arm/refresh/disarm cancel-all-after. **Auth:** API key (`x-api-key`) **Parameters** - `idempotency-key` (header), required — string **Request body** - `timeoutMs` · string · unit `ms` · required ```json { "timeoutMs": "100000" } ``` **Responses** - **200** — OK - `armed` · boolean · required - `deadlineTsMs` · string \| null · unit `ms` · required - **400** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **401** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **403** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **429** — 429 — a token bucket is exhausted. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional Example `200` response: ```json { "armed": true, "deadlineTsMs": "100000" } ``` ## Withdrawals ### `POST /v1/withdrawals/quote` Fee quote + wrong-address first-seen check. **Auth:** API key (`x-api-key`) **Request body** - `invoice` · string · optional - `toAddress` · string · optional - `amountMsat` · string · unit `msat` · optional ```json { "invoice": "string", "toAddress": "string", "amountMsat": "100000" } ``` **Responses** - **200** — OK - **401** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **403** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **429** — 429 — a token bucket is exhausted. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional --- ### `GET /v1/withdrawals` Withdrawal history (cursor-paginated). **Auth:** API key (`x-api-key`) **Parameters** - `limit` (query) — string - `beforeSeq` (query) — string **Responses** - **200** — OK - `withdrawals` · object[] · required - `idempotencyKey` · string · required - `amountMsat` · string · unit `msat` · required - `feeMsat` · string \| null · unit `msat` · required - `state` · enum(`requested`, `queued`, `paid`, `returned`) · required - `path` · enum(`spark`, `lightning`) · required - `queuedReason` · string \| null · required - `railRef` · string \| null · required - `requestedTsMs` · string · unit `ms` · required - `paidTsMs` · string \| null · unit `ms` · required - `nextBeforeSeq` · string \| null · unit `seq` · required - **401** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **402** — 402 — reserved for L402/Lightning-metered access (PAPI-8). Not returned today; documented so it lands additively. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **403** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **429** — 429 — a token bucket is exhausted. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional Example `200` response: ```json { "withdrawals": [ { "idempotencyKey": "string", "amountMsat": "100000", "feeMsat": "100000", "state": "requested", "path": "spark", "queuedReason": "string", "railRef": "string", "requestedTsMs": "100000", "paidTsMs": "100000" } ], "nextBeforeSeq": "100000" } ``` --- ### `POST /v1/withdrawals` Withdraw free balance (spark or lightning). **Auth:** API key (`x-api-key`) **Parameters** - `idempotency-key` (header), required — string **Request body** - `amountMsat` · string · unit `msat` · required - `toAddress` · string · optional - `confirmToken` · string · optional ```json { "amountMsat": "100000", "toAddress": "string", "confirmToken": "string" } ``` **Responses** - **201** — Created - `railRef` · string · required - `queued` · boolean · required - **401** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **402** — 402 — reserved for L402/Lightning-metered access (PAPI-8). Not returned today; documented so it lands additively. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **403** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **428** — Confirmation Required - `error` · string · required - `code` · string · optional - `reason` · string · required - `firstSeen` · boolean · required - `confirmThresholdMsat` · string · unit `msat` · required - **429** — 429 — a token bucket is exhausted. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional Example `201` response: ```json { "railRef": "string", "queued": true } ``` ## Referral ### `GET /v1/referral` The account's referral status. **Auth:** API key (`x-api-key`) **Responses** - **200** — OK - `tsMs` · string · unit `ms` · required - `program` · object · required - `enabled` · boolean · required - `paused` · boolean · required - `policyVersion` · string · unit `int` · required - `policy` · object · required - `shareQ9` · string · unit `q9` · required - `shareTiers` · object[] · required - `refereeDiscountQ9` · string · unit `q9` · required - `refereeDiscountWindowMs` · string · unit `ms` · required - `refereeDiscountVolumeCapMsat` · string \| null · unit `msat` · required - `rewardWindowMs` · string · unit `ms` · required - `holdbackMs` · string · unit `ms` · required - `minClaimMsat` · string · unit `msat` · required - `eligibility` · object · required - `eligible` · boolean · required - `gates` · object[] · required - `code` · object \| null · required - `referredBy` · object \| null · required - `stats` · object \| null · required - `bindWindow` · object \| null · required - **401** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **403** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **429** — 429 — a token bucket is exhausted. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional Example `200` response: ```json { "tsMs": "100000", "program": { "enabled": true, "paused": true, "policyVersion": "100000" }, "policy": { "shareQ9": "100000", "shareTiers": [ { "minActiveReferees": "100000", "shareQ9": "100000" } ], "refereeDiscountQ9": "100000", "refereeDiscountWindowMs": "100000", "refereeDiscountVolumeCapMsat": "100000", "rewardWindowMs": "100000", "holdbackMs": "100000", "minClaimMsat": "100000" }, "eligibility": { "eligible": true, "gates": [ { "gate": "string", "met": true, "requiredMs": "100000", "currentMs": "100000", "requiredMsat": "100000", "currentMsat": "100000", "required": "100000", "current": "100000" } ] }, "code": { "code": "string", "createdTsMs": "100000", "disabled": true }, "referredBy": { "code": "string", "boundTsMs": "100000", "discountQ9": "100000", "discountExpiresTsMs": "100000", "discountVolumeCapMsat": "100000", "discountVolumeUsedMsat": "100000", "discountActive": true }, "stats": { "referees": "100000", "activeReferees": "100000", "tier": { "shareQ9": "100000" }, "lifetimeEarnedMsat": "100000", "earned30dMsat": "100000", "claimableMsat": "100000", "pendingHoldbackMsat": "100000", "claimedMsat": "100000", "forfeitedMsat": "100000" }, "bindWindow": { "openUntilTsMs": "100000" } } ``` --- ### `POST /v1/referral/code` Create the account's referral code. **Auth:** API key (`x-api-key`) **Parameters** - `idempotency-key` (header), required — string **Request body** - `code` · string · optional ```json { "code": "string" } ``` **Responses** - **201** — Created - `code` · string · required - `createdTsMs` · string · unit `ms` · required - **401** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **403** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **429** — 429 — a token bucket is exhausted. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional Example `201` response: ```json { "code": "string", "createdTsMs": "100000" } ``` --- ### `POST /v1/referral/bind` Bind to a referrer's code (grace window). **Auth:** API key (`x-api-key`) **Parameters** - `idempotency-key` (header), required — string **Request body** - `code` · string · required ```json { "code": "string" } ``` **Responses** - **200** — OK - `bound` · boolean · required - `referredBy` · object · required - `code` · string · required - `boundTsMs` · string · unit `ms` · required - `discountQ9` · string · unit `q9` · required - `discountExpiresTsMs` · string · unit `ms` · required - `discountVolumeCapMsat` · string \| null · unit `msat` · required - `discountVolumeUsedMsat` · string · unit `msat` · required - `discountActive` · boolean · required - **401** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **403** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **429** — 429 — a token bucket is exhausted. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional Example `200` response: ```json { "bound": true, "referredBy": { "code": "string", "boundTsMs": "100000", "discountQ9": "100000", "discountExpiresTsMs": "100000", "discountVolumeCapMsat": "100000", "discountVolumeUsedMsat": "100000", "discountActive": true } } ``` --- ### `GET /v1/referral/earnings` Referral earnings (cursor-paginated). **Auth:** API key (`x-api-key`) **Parameters** - `limit` (query) — string - `beforeSeq` (query) — string **Responses** - **200** — OK - `earnings` · object[] · required - `seq` · string · unit `seq` · required - `fillSeq` · string \| null · unit `seq` · required - `tsMs` · string · unit `ms` · required - `ref` · string \| null · required - `grossFeeMsat` · string · unit `msat` · required - `netFeeMsat` · string · unit `msat` · required - `shareQ9` · string · unit `q9` · required - `rewardMsat` · string · unit `msat` · required - `status` · enum(`accrued`, `claimed`, `forfeited`) · required - `epochDay` · string · unit `ms` · required - `code` · string \| null · required - `nextBeforeSeq` · string \| null · unit `seq` · required - **401** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **403** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **429** — 429 — a token bucket is exhausted. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional Example `200` response: ```json { "earnings": [ { "seq": "100000", "fillSeq": "100000", "tsMs": "100000", "ref": "string", "grossFeeMsat": "100000", "netFeeMsat": "100000", "shareQ9": "100000", "rewardMsat": "100000", "status": "accrued", "epochDay": "100000", "code": "string" } ], "nextBeforeSeq": "100000" } ``` --- ### `GET /v1/referral/referees` The account's referees. **Auth:** API key (`x-api-key`) **Parameters** - `limit` (query) — string - `beforeSeq` (query) — string **Responses** - **200** — OK - `referees` · string · unit `int` · required - `nextBeforeSeq` · string \| null · unit `seq` · required - **401** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **403** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **429** — 429 — a token bucket is exhausted. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional Example `200` response: ```json { "referees": "100000", "nextBeforeSeq": "100000" } ``` --- ### `POST /v1/referral/claim` Claim vested referral rewards. **Auth:** API key (`x-api-key`) **Parameters** - `idempotency-key` (header), required — string **Responses** - **201** — Created - `claimedMsat` · string · unit `msat` · required - `accruals` · string · unit `int` · required - **401** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **403** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **429** — 429 — a token bucket is exhausted. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional Example `201` response: ```json { "claimedMsat": "100000", "accruals": "100000" } ``` ## Promoter ### `POST /v1/promoter/apply` Apply for a promoter profile. **Auth:** API key (`x-api-key`) **Parameters** - `idempotency-key` (header), required — string **Request body** - `displayName` · string · required - `channels` · object[] · required - `kind` · string · required - `url` · string · required - `audience` · string · required - `plan` · string · required - `jurisdictions` · string[] · required - `acceptedTermsVersion` · string · required ```json { "displayName": "string", "channels": [ { "kind": "string", "url": "string", "audience": "string" } ], "plan": "string", "jurisdictions": [ "string" ], "acceptedTermsVersion": "string" } ``` **Responses** - **201** — Created - `promoterId` · string · required - `status` · string · required - **401** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **403** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **429** — 429 — a token bucket is exhausted. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional Example `201` response: ```json { "promoterId": "string", "status": "string" } ``` --- ### `GET /v1/promoter` Promoter profile + status. **Auth:** API key (`x-api-key`) **Responses** - **200** — OK - **401** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **403** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **404** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **429** — 429 — a token bucket is exhausted. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional --- ### `GET /v1/promoter/codes` The promoter's codes. **Auth:** API key (`x-api-key`) **Responses** - **200** — OK - **401** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **403** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **429** — 429 — a token bucket is exhausted. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional --- ### `POST /v1/promoter/codes` Create a promoter code. **Auth:** API key (`x-api-key`) **Parameters** - `idempotency-key` (header), required — string **Request body** - `code` · string · required - `label` · string · required - `kickbackQ9` · string · unit `q9` · required ```json { "code": "string", "label": "string", "kickbackQ9": "100000" } ``` **Responses** - **201** — Created - **401** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **403** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **429** — 429 — a token bucket is exhausted. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional --- ### `GET /v1/promoter/stats` Promoter stats (days window). **Auth:** API key (`x-api-key`) **Parameters** - `days` (query) — string **Responses** - **200** — OK - **401** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **403** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **429** — 429 — a token bucket is exhausted. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional --- ### `GET /v1/promoter/earnings` Promoter earnings (cursor-paginated). **Auth:** API key (`x-api-key`) **Parameters** - `limit` (query) — string - `beforeSeq` (query) — string **Responses** - **200** — OK - `earnings` · object[] · required - `seq` · string · unit `seq` · required - `fillSeq` · string \| null · unit `seq` · required - `tsMs` · string · unit `ms` · required - `ref` · string \| null · required - `grossFeeMsat` · string · unit `msat` · required - `netFeeMsat` · string · unit `msat` · required - `shareQ9` · string · unit `q9` · required - `rewardMsat` · string · unit `msat` · required - `status` · enum(`accrued`, `claimed`, `forfeited`) · required - `epochDay` · string · unit `ms` · required - `code` · string \| null · required - `nextBeforeSeq` · string \| null · unit `seq` · required - **401** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **403** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **429** — 429 — a token bucket is exhausted. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional Example `200` response: ```json { "earnings": [ { "seq": "100000", "fillSeq": "100000", "tsMs": "100000", "ref": "string", "grossFeeMsat": "100000", "netFeeMsat": "100000", "shareQ9": "100000", "rewardMsat": "100000", "status": "accrued", "epochDay": "100000", "code": "string" } ], "nextBeforeSeq": "100000" } ``` --- ### `POST /v1/promoter/claim` Claim vested promoter rewards. **Auth:** API key (`x-api-key`) **Parameters** - `idempotency-key` (header), required — string **Responses** - **201** — Created - `claimedMsat` · string · unit `msat` · required - `accruals` · string · unit `int` · required - **401** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **403** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **429** — 429 — a token bucket is exhausted. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional Example `201` response: ```json { "claimedMsat": "100000", "accruals": "100000" } ``` ## Onboarding ### `POST /v1/signup` Self-serve session (testnet profile only). **Auth:** none (public) **Parameters** - `idempotency-key` (header), required — string **Responses** - **201** — Created - `apiKey` · string · required - `accountId` · string · required - `railAddress` · string · required - `referredBy` · object · optional - `code` · string · required - `boundTsMs` · string · unit `ms` · required - **403** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **409** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional Example `201` response: ```json { "apiKey": "string", "accountId": "string", "railAddress": "string", "referredBy": { "code": "string", "boundTsMs": "100000" } } ``` --- ### `POST /v1/faucet` Testnet faucet grant (rate-limited). **Auth:** API key (`x-api-key`) **Parameters** - `idempotency-key` (header), required — string **Responses** - **201** — Created - `creditedMsat` · string · unit `msat` · required - `grantsRemainingThisHour` · integer · required - **401** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **403** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **429** — 429 — a token bucket is exhausted. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional Example `201` response: ```json { "creditedMsat": "100000", "grantsRemainingThisHour": 0 } ``` ## Leaderboard ### `GET /v1/leaderboard` A leaderboard board (?board=pnl|roi&window=7d|30d|all&limit=). Bounded. **Auth:** none (public) **Parameters** - `board` (query) — enum(`pnl`, `roi`) - `window` (query) — enum(`7d`, `30d`, `all`) - `limit` (query) — string **Responses** - **200** — OK - `board` · enum(`pnl`, `roi`) · required - `window` · enum(`7d`, `30d`, `all`) · required - `rows` · object[] · required - `rank` · string · unit `int` · required - `handle` · string · required - `displayName` · string \| null · required - `pnlMsat` · string · unit `msat` · required - `roiQ9` · string · unit `q9` · required - `volumeContracts` · string · unit `contracts` · required - `trades` · string · unit `int` · required - `winRateQ9` · string \| null · unit `q9` · required - `maxDrawdownQ9` · string · unit `q9` · required - `computedTsMs` · string \| null · unit `ms` · required - `cadenceMinutes` · string · unit `int` · required - `gates` · object · required - `minEquityMsat` · string · unit `msat` · required - `minTrades` · string · unit `int` · required - `minVolumeContracts` · string · unit `contracts` · required - `windows` · enum(`7d`, `30d`, `all`)[] · optional - `testnet` · boolean · required Example `200` response: ```json { "board": "pnl", "window": "7d", "rows": [ { "rank": "100000", "handle": "string", "displayName": "string", "pnlMsat": "100000", "roiQ9": "100000", "volumeContracts": "100000", "trades": "100000", "winRateQ9": "100000", "maxDrawdownQ9": "100000" } ], "computedTsMs": "100000", "cadenceMinutes": "100000", "gates": { "minEquityMsat": "100000", "minTrades": "100000", "minVolumeContracts": "100000" }, "windows": [ "7d" ], "testnet": true } ``` --- ### `GET /v1/leaderboard/me` The caller's own placements + per-window eligibility checklist. **Auth:** API key (`x-api-key`) **Responses** - **200** — OK - `optedOut` · boolean · required - `handle` · string \| null · required - `displayName` · string \| null · required - `placements` · object[] · required - `board` · enum(`pnl`, `roi`) · required - `window` · enum(`7d`, `30d`, `all`) · required - `rank` · string · unit `int` · required - `pnlMsat` · string · unit `msat` · required - `roiQ9` · string · unit `q9` · required - `volumeContracts` · string · unit `contracts` · required - `trades` · string · unit `int` · required - `winRateQ9` · string \| null · unit `q9` · required - `maxDrawdownQ9` · string · unit `q9` · required - `eligibility` · object[] · required - `window` · enum(`7d`, `30d`, `all`) · required - `eligible` · boolean · required - `reason` · enum(`gates_failed`, `insufficient_history`, `coverage_gap`, `window_unserved`) \| null · optional - `eligibleAtMs` · string \| null · unit `ms` · optional - `checks` · object[] · required - **401** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **403** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **429** — 429 — a token bucket is exhausted. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional Example `200` response: ```json { "optedOut": true, "handle": "string", "displayName": "string", "placements": [ { "board": "pnl", "window": "7d", "rank": "100000", "pnlMsat": "100000", "roiQ9": "100000", "volumeContracts": "100000", "trades": "100000", "winRateQ9": "100000", "maxDrawdownQ9": "100000" } ], "eligibility": [ { "window": "7d", "eligible": true, "reason": "gates_failed", "eligibleAtMs": "100000", "checks": [ { "gate": "string", "ok": true, "thresholdMsat": "100000", "threshold": "100000", "valueMsat": "100000", "value": "100000" } ] } ] } ``` --- ### `GET /v1/leaderboard/trader/{handle}` A public trader profile (404 for opted-out OR unknown, indistinguishably). **Auth:** none (public) **Parameters** - `handle` (path), required — string **Responses** - **200** — OK - `handle` · string · required - `displayName` · string \| null · required - `tradingSince` · string \| null · required - `placements` · object[] · required - `board` · enum(`pnl`, `roi`) · required - `window` · enum(`7d`, `30d`, `all`) · required - `rank` · string · unit `int` · required - `pnlMsat` · string · unit `msat` · required - `roiQ9` · string · unit `q9` · required - `volumeContracts` · string · unit `contracts` · required - `trades` · string · unit `int` · required - `winRateQ9` · string \| null · unit `q9` · required - `maxDrawdownQ9` · string · unit `q9` · required - `equityCurve` · object[] · required - `dayTsMs` · string · unit `ms` · required - `indexQ9` · string · unit `q9` · required - `testnet` · boolean · required Example `200` response: ```json { "handle": "string", "displayName": "string", "tradingSince": "string", "placements": [ { "board": "pnl", "window": "7d", "rank": "100000", "pnlMsat": "100000", "roiQ9": "100000", "volumeContracts": "100000", "trades": "100000", "winRateQ9": "100000", "maxDrawdownQ9": "100000" } ], "equityCurve": [ { "dayTsMs": "100000", "indexQ9": "100000" } ], "testnet": true } ``` --- ### `GET /v1/leaderboard/trader/{handle}/og.png` Server-rendered 1200×630 Open Graph share card (PNG) for a profile, from the same quantized cache (ETag on the recompute stamp; 404 for opted-out OR unknown, indistinguishably). **Auth:** none (public) **Parameters** - `handle` (path), required — string **Responses** - **200** — OK --- ### `POST /v1/leaderboard/settings` Opt-out/in + display-name claim (own public identity). **Auth:** API key (`x-api-key`) **Parameters** - `idempotency-key` (header), required — string **Request body** - `optedOut` · boolean · optional - `displayName` · string \| null · optional ```json { "optedOut": true, "displayName": "string" } ``` **Responses** - **200** — OK - `optedOut` · boolean · required - `handle` · string \| null · required - `displayName` · string \| null · required - `placements` · object[] · required - `board` · enum(`pnl`, `roi`) · required - `window` · enum(`7d`, `30d`, `all`) · required - `rank` · string · unit `int` · required - `pnlMsat` · string · unit `msat` · required - `roiQ9` · string · unit `q9` · required - `volumeContracts` · string · unit `contracts` · required - `trades` · string · unit `int` · required - `winRateQ9` · string \| null · unit `q9` · required - `maxDrawdownQ9` · string · unit `q9` · required - `eligibility` · object[] · required - `window` · enum(`7d`, `30d`, `all`) · required - `eligible` · boolean · required - `reason` · enum(`gates_failed`, `insufficient_history`, `coverage_gap`, `window_unserved`) \| null · optional - `eligibleAtMs` · string \| null · unit `ms` · optional - `checks` · object[] · required - **401** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **403** — Refusal — a stable machine `code` (ErrorCode) beside human `error` prose. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional - **429** — 429 — a token bucket is exhausted. - `error` · string · required - `code` · [`ErrorCode`](#errorcode) · optional Example `200` response: ```json { "optedOut": true, "handle": "string", "displayName": "string", "placements": [ { "board": "pnl", "window": "7d", "rank": "100000", "pnlMsat": "100000", "roiQ9": "100000", "volumeContracts": "100000", "trades": "100000", "winRateQ9": "100000", "maxDrawdownQ9": "100000" } ], "eligibility": [ { "window": "7d", "eligible": true, "reason": "gates_failed", "eligibleAtMs": "100000", "checks": [ { "gate": "string", "ok": true, "thresholdMsat": "100000", "threshold": "100000", "valueMsat": "100000", "value": "100000" } ] } ] } ``` ## Schemas Types referenced by the fields above. ### ErrorCode Enum — one of: `validation`, `unauthorized`, `insufficient_scope`, `compliance_refused`, `session_limit`, `referral_forbidden`, `not_found`, `idempotency_key_reused`, `conflict`, `confirmation_required`, `rate_limited`, `feature_unavailable`, `deposits_halted`, `unavailable`, `insufficient_balance`, `insufficient_margin`, `position_limit`, `oi_cap`, `reduce_only`, `market_halted`, `oracle_stale`, `body_too_large` --- ### FundingHistoryEntry - `tsMs` · string · unit `ms` · required - `rateQ9` · string · unit `q9` · required - `skewQ9` · string · unit `q9` · required --- ### OrdersPage - `orders` · object[] · required - `orderId` · string · required - `marketId` · string · required - `side` · enum(`buy`, `sell`) · required - `type` · enum(`market`, `limit`, `stop_market`, `take_profit_market`) · required - `contracts` · string · unit `contracts` · required - `status` · enum(`accepted`, `filled`, `canceled`, `rejected`) · required - `reason` · string \| null · required - `nextBeforeSeq` · string \| null · unit `seq` · required --- ### Position - `marketId` · string · required - `side` · enum(`long`, `short`) · required - `contracts` · string · unit `contracts` · required - `entryPriceQ8` · string · unit `q8` · required - `marginMsat` · string · unit `msat` · required - `fundingClockTsMs` · string · unit `ms` · required --- ### PublicMarket - `marketId` · string · required - `underlying` · string · required - `contractType` · enum(`inverse`, `quanto`) · required - `oracleFeedId` · string · required - `quantoMultiplierMsat` · string \| null · unit `msat` · required - `status` · enum(`live`, `reduce_only`, `halted`) · required - `tickSizeQ8` · string · unit `q8` · required - `priceDp` · string · unit `int` · required - `minOrderContracts` · string · unit `contracts` · required - `maxOrderContracts` · string · unit `contracts` · required - `takerFeeQ9` · string · unit `q9` · required - `maxLeverage` · string · unit `int` · required - `imRateQ9` · string · unit `q9` · required - `mmRateQ9` · string · unit `q9` · required - `baseSpreadQ9` · string · unit `q9` · required - `fundingRateHourlyQ9` · string · unit `q9` · required - `nextFundingTsMs` · string · unit `ms` · required - `oracleStalenessMs` · string · unit `ms` · required - `openInterestContracts` · string · unit `contracts` · required - `skewQ9` · string · unit `q9` · required - `lastPrice` · object \| null · required --- ### TriggersPage - `triggers` · object[] · required - `orderId` · string · required - `marketId` · string · required - `kind` · enum(`entry`, `stop`, `take_profit`) · required - `side` · enum(`buy`, `sell`) · required - `contracts` · string · unit `contracts` · required - `triggerPriceQ8` · string · unit `q8` · required - `reduceOnly` · boolean · required - `status` · enum(`resting`, `armed`, `blocked`, `executed`, `cancelled`) · required - `reason` · string \| null · required - `ocoGroup` · string \| null · required - `nextBeforeSeq` · string \| null · unit `seq` · required --- # WebSocket — https://docs.hardbasis.com/api/websocket # WebSocket One socket carries market data and your account events: `wss://…/v1/ws`. The protocol is version `1`, echoed in the auth acknowledgement so a client can detect a server it does not understand. ## Channels | channel | carries | auth | | --------- | ---------------------------------------------------------------- | ---- | | `prices` | oracle prints for a feed (`feedId`) — the tick stream | no | | `stats` | per-market stats each print: mark, funding rate, OI (`marketId`) | no | | `account` | your account's events as they land in the log | yes | ## Client operations Send JSON frames: ```json { "op": "auth", "apiKey": "hb_…" } { "op": "subscribe", "channel": "prices", "feedId": "btc-usd" } { "op": "subscribe", "channel": "stats", "marketId": "btc-usd-perp" } { "op": "unsubscribe", "channel": "prices", "feedId": "btc-usd" } ``` Authenticate before subscribing to `account`. `subscribe`/`unsubscribe` are mirror images — shed a channel without reconnecting. The server echoes each subscription in an ack (with the `feedId`/`marketId`), and an invalid op comes back as an error frame carrying the machine `code` and the offending `op` so you can correlate it. ## Heartbeat and staleness The server emits a heartbeat frame (`{"op":"ping"}`) on a fixed cadence, so a quiet feed is distinguishable from a dead socket. You may reply with `{"op":"pong"}` (accepted as a harmless no-op) or ignore it — the server does not require a reply and does not close a socket that stays silent. Every price/stats frame carries the oracle timestamp and a staleness input. A live figure must either be live or **look** dead: if the socket goes quiet past the staleness threshold, render your mark/funding/OI as visibly stale rather than showing a last-known value with live confidence. ## Reconnect doctrine On reconnect, do **not** trust the stream to backfill the gap. Take a REST snapshot first (the relevant `GET /v1/*`), then resume the stream, and dedup on the event `seq` — the same monotonic sequence number the `account` channel and the history endpoints share, so history↔live dedup is one comparison. Frames you do not recognise (a newer server, an additive field) must be ignored, not treated as an error: the protocol evolves additively (see [Changelog](/api/changelog)). --- # Errors — https://docs.hardbasis.com/api/errors # Errors Every non-2xx body is `{"error": "", "code": ""}` (the `428` confirmation response carries a few extra fields). **Match on `code`, never on the prose** — the message may change; the code will not. `code` is drawn from a **closed enum** that evolves additively: a new refusal class adds a member, but no member is ever renamed or removed (a contract test fails CI on a break). So a client can safely switch on it, and an unknown code means a newer server — handle it as a generic failure, don't crash. ## Status codes | status | meaning | | ------ | ------------------------------------------------------------ | | `400` | validation / insufficient balance or margin | | `401` | missing or unknown key | | `403` | compliance refusal, insufficient scope, or a program refusal | | `404` | unknown market / order / account, or a route not served here | | `409` | idempotency or uniqueness conflict | | `428` | confirmation required (first-seen withdrawal destination) | | `429` | rate limited (see [Rate limits](/api/rate-limits)) | | `503` | deposits halted — never affects withdrawals | ## The code vocabulary The full set, from the spec's `ErrorCode` enum (the [REST reference](/api/reference/) links it, and `/openapi.json` carries it verbatim): | code | typical status | meaning | | ------------------------ | -------------- | --------------------------------------------- | | `validation` | `400` | malformed or out-of-range request | | `insufficient_balance` | `400` | not enough free balance | | `insufficient_margin` | `400` | order would breach margin | | `unauthorized` | `401` | missing or unknown API key | | `insufficient_scope` | `403` | key lacks the route's scope | | `compliance_refused` | `403` | signup/deposit refused by the compliance gate | | `referral_forbidden` | `403` | referral action not permitted | | `session_limit` | `403` | account is at its live-key cap | | `not_found` | `404` | unknown resource, or route not served here | | `conflict` | `409` | uniqueness conflict | | `idempotency_key_reused` | `409` | a key reused on a different request | | `confirmation_required` | `428` | withdrawal needs a confirmation token | | `rate_limited` | `429` | a token bucket is exhausted | | `deposits_halted` | `503` | deposits paused (withdrawals unaffected) | | `feature_unavailable` | `501` | feature not wired for this deployment | | `market_halted` | `409`/`400` | the market is halted | | `reduce_only` | `409`/`400` | market is reduce-only; order would increase | | `oracle_stale` | `409`/`400` | oracle too stale to price | | `oi_cap` | `409`/`400` | open-interest cap reached | | `position_limit` | `409`/`400` | position limit reached | | `body_too_large` | `413` | request body exceeds the cap | | `unavailable` | `503` | temporarily unavailable | The status a given code rides can vary with context; the code is the stable signal. Build against `code`. --- # Changelog & deprecation policy — https://docs.hardbasis.com/api/changelog # Changelog & deprecation policy ## The compatibility covenant `/v1` evolves **additively**. A response may gain a field and a request may gain an optional one, at any time, without notice. So: - **Ignore unknown fields.** A client that rejects a response because it carries a field the client has not seen will break on the next additive release. Parse what you need, ignore the rest. - **Never depend on field order** or on the absence of a field. - A field is **never removed or re-typed** in `/v1`. Removing or re-typing a field is a breaking change, and breaking changes only ever land under a new major version (`/v2`) — never in place. A contract-snapshot test fails CI if a `/v1` field is removed or re-typed, so the covenant is enforced, not just promised. The same rule governs the WebSocket protocol and the error-code enum: additive only, versioned in the auth ack (`v`), removals deferred to a new major. ## Deprecation When something must eventually go, it is deprecated first, not deleted. A deprecated surface keeps working, is marked in this changelog and in the spec, and is removed only after a notice window of **at least ninety days** — and only in a new major version, never in `/v1`. ## Versioning The `info.version` field in [`/openapi.json`](/openapi.json) tracks the **API contract**, not our software release. Read it this way: - A purely **additive** change within `/v1` — a new endpoint, a new optional field — bumps the **minor** (`1.1.0`, `1.2.0`, …). - The **major** bumps only alongside a breaking `/v2` surface, and never before one exists. - It is deliberately **not** the npm package version. SDK generators stamp `info.version` into the version of the client they emit, so it has to mean "which contract" — not "which build of our server." **Deployment status is never carried by the version string.** Whether a given environment is live, and what code it runs, is told by the `servers` block and by `GET /deployment` — not by `info.version`. ## Reading changes The machine-readable source of truth is [`/openapi.json`](/openapi.json): diff it between releases to see exactly what changed. Each reference page is stamped with the contract version it documents, so you can tell whether the reference in front of you describes the surface you are calling. ## Releases Dated entries land here as the API changes. Today the public API is on testnet; nothing here is an offer, a solicitation, or advice. ### 1.4.0 — 2026-08-20 Additive. `GET /v1/markets/:id/candles`: each candle may carry `volumeContracts` — fills-derived contracts traded in the bucket, the same basis as `stats24h` (`basis: "fills"`), over exactly the bar's window. A served value — including `"0"` — is the true sum; an absent field means the deployment cannot tell (pre-launch index-history bars carry none). `prints` is unchanged and remains a print count, not volume. ### 1.3.0 — 2026-08-20 Additive. `GET /v1/markets/:id/candles`: `limit` maximum raised from `1000` to `1440` — one UTC day of the 1m base in a single request, which is exactly what a full ninety-six-bar fifteen-minute frame aggregates from. Defaults unchanged. ### 1.2.0 — 2026-08-20 Additive. `GET /v1/markets/:id/candles` with `interval=86400`: each candle may carry `seeded: true`, marking a pre-launch **index history** bar — daily OHLC taken from the market's own oracle source before this venue's first print (`prints` is `0` on such bars). A bar without the field is a real bar folded from venue prints. Clients that render candles should render seeded bars visibly distinct; clients that ignore unknown fields are unaffected. Only the daily interval ever carries the field. ### 1.1.0 — 2026-08-20 Additive. `GET /v1/markets/:id/candles`: - `interval` now also accepts `3600` and `86400` (seconds, as ever). These are permanent server-side rollups of the same oracle-print fold; `60` remains the default and keeps a retention window of four days. Aggregate intermediate frames (5m/15m/4h) client-side from the nearest served base. - `limit` maximum raised from 96 to 1000. The default stays 96; requests that never send `limit` or `interval` see identical behavior to 1.0.0.