Documentation

The order book API.

Clobber is a hosted central limit order book. You create markets over HTTP, send orders that match in microseconds, and stream every book delta and fill over WebSocket. This reference covers the whole surface: markets, accounts, orders, the realtime feed and settlement.

The API is REST over HTTPS. All requests and responses are JSON, all timestamps are RFC 3339 in UTC, and every monetary value is a decimal string rather than a float — "0.61", never 0.61 — so nothing is lost to binary rounding on the way in or out.

Base URLs

Sandbox
REST https://api.sandbox.clobber.dev/v1
WS   wss://feed.sandbox.clobber.dev
Production
REST https://api.clobber.dev/v1
WS   wss://feed.clobber.dev

The two environments are fully isolated: separate keys, markets, accounts and ledgers. Sandbox is free forever, runs the identical matching engine, and is the right place to build against. Nothing you do there can touch production balances.

Versioning

The version is in the path (/v1). Additive changes — new fields, new endpoints, new enum values — ship without a version bump, so parse defensively and ignore fields you do not recognise. Breaking changes get a new version and at least 12 months of overlap. Everything that changes is listed in the changelog.

Quickstart

From zero to a matched trade in five calls. Grab a sandbox key, then paste these in order.

1. Create a market

curl
curl -X POST https://api.sandbox.clobber.dev/v1/markets \
  -H "Authorization: Bearer $CLOBBER_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "symbol": "WILL-IT-RAIN-BA",
    "kind": "binary",
    "tick_size": "0.01",
    "settlement_currency": "USDC"
  }'

2. Create two accounts and fund them

Orders are always placed by an account, never by the API key itself. In sandbox you can credit balances directly; in production, balances move through your own funding flow.

curl
curl -X POST https://api.sandbox.clobber.dev/v1/accounts \
  -H "Authorization: Bearer $CLOBBER_KEY" \
  -d '{ "reference": "user_alice" }'
# => { "id": "acc_2fRk...", "reference": "user_alice" }

curl -X POST https://api.sandbox.clobber.dev/v1/accounts/acc_2fRk.../credits \
  -H "Authorization: Bearer $CLOBBER_KEY" \
  -d '{ "currency": "USDC", "amount": "1000.00" }'

3. Send a resting bid

curl
curl -X POST https://api.sandbox.clobber.dev/v1/orders \
  -H "Authorization: Bearer $CLOBBER_KEY" \
  -H "Clobber-Account: acc_2fRk..." \
  -H "Idempotency-Key: 7f3c1a90-bid-1" \
  -d '{
    "market": "WILL-IT-RAIN-BA",
    "side": "buy",
    "type": "limit",
    "price": "0.61",
    "qty": "250"
  }'

4. Cross it from the other account

curl
curl -X POST https://api.sandbox.clobber.dev/v1/orders \
  -H "Authorization: Bearer $CLOBBER_KEY" \
  -H "Clobber-Account: acc_9dPz..." \
  -d '{
    "market": "WILL-IT-RAIN-BA",
    "side": "sell",
    "type": "limit",
    "price": "0.61",
    "qty": "250"
  }'

# => status "filled", one fill at 0.61 x 250

5. Resolve and settle

curl
curl -X POST https://api.sandbox.clobber.dev/v1/markets/WILL-IT-RAIN-BA/resolve \
  -H "Authorization: Bearer $CLOBBER_KEY" \
  -d '{ "outcome": "yes" }'

Every YES position pays out at 1.00, every NO position at 0.00, and the ledger balances atomically. Alice paid 152.50 for 250 contracts and receives 250.00.

Next

Open a WebSocket feed in a second terminal before step 4 and you will watch the book delta and both fills arrive in the same sequence the engine produced them.

Authentication

Every request carries an API key as a bearer token. Keys are environment-scoped and never expire until you revoke them.

Request header
Authorization: Bearer ck_test_4eF9a2Kd8sQm1xVb7nRt3wYz

Test keys are prefixed ck_test_ and live keys ck_live_, so a key that leaks into the wrong environment fails loudly rather than quietly trading real balances. Keys are shown once at creation and stored only as a hash — if you lose one, roll it.

Key roles

RoleCan doUse for
adminEverything: create and resolve markets, manage accounts, issue credits, rotate keys.Your backend's control plane. One key, tightly held.
tradingPlace, query and cancel orders. Read books and trades. Cannot create or resolve markets.The service that routes end-user orders.
read_onlyGET endpoints and public feed channels only.Dashboards, analytics jobs, monitoring.

Acting on behalf of an account

Order endpoints require a Clobber-Account header naming the sub-account the order belongs to. The key authenticates your platform; the header identifies which of your users is trading. An order without it is rejected with account_required.

Request headers
Authorization: Bearer ck_live_...
Clobber-Account: acc_2fRk8Wq4vNc6
Idempotency-Key: 7f3c1a90-4e2b-4c11-9a7d-1f2e3d4c5b6a
Never ship a key to a browser

An API key placed in front-end code lets anyone drain every account on your platform. Orders must be signed server-side. If you need browser clients to read live data, mint a short-lived feed token with POST /v1/feed_tokens — it is read-only, scoped to one market, and expires in 15 minutes.

Core concepts

Four objects and one rule. The rule is price-time priority; the objects are markets, accounts, orders and fills.

Markets

A market is a book with a symbol, a tick size, and a kind. binary markets trade contracts between 0.00 and 1.00 that settle at one or zero — prediction markets, essentially probability. scalar markets settle to a number inside a declared range. pair markets never resolve at all; they trade one asset against another indefinitely, like a classic exchange listing.

Markets move through a lifecycle, and each state decides what the engine accepts:

StatusMeaningAccepts orders
draftCreated but not open. Configuration is still editable.No
openTrading normally.Yes
haltedPaused by you or by a circuit breaker. The book is preserved.Cancels only
closedTrading has ended; awaiting an outcome.No
resolvedAn outcome was declared; settlement is running.No
settledEvery position paid out. Terminal state.No

Accounts and the ledger

Each of your end users maps to a Clobber account holding balances per currency. The ledger is double-entry: every credit has a matching debit, and the engine refuses any operation that would leave it unbalanced. This is why there is no "reconciliation" endpoint — the invariant holds at write time, not after a nightly job.

When an order rests on the book, the collateral it could consume is placed on hold: available falls, total does not. When it fills, the hold converts to a transfer. When it cancels, the hold is released. A buy holds price × qty of the settlement currency; a sell of a binary contract holds the contracts themselves, or (1 − price) × qty if the account is going short.

Matching

Strict price-time priority. Best price wins; at equal price, whoever arrived first is filled first. An incoming order sweeps the opposing side until it is filled, hits its limit, or exhausts its time-in-force, and whatever remains rests on the book. Matching is deterministic — replay the same command log and you get the same fills, in the same order, byte for byte. That property is what makes failover a replay rather than a reconciliation exercise.

Self-trading is prevented: if an incoming order would cross the same account's resting order, the resting order is cancelled first by default. Set self_trade_prevention to cancel_taker or decrement to change that.

Sequence numbers

Every event the engine emits for a market carries a monotonically increasing seq, with no gaps. Book deltas, trades and your own fills all share one sequence per market, so if you receive seq: 48211 after seq: 48209, you have missed exactly one message and know to resync.

Markets

Create, list, inspect, halt and resolve. Market administration requires an admin key.

POST/v1/markets

Creates a market. Returns the market object with status open unless you pass "status": "draft".

FieldTypeDescription
symbolstringrequiredUnique per environment. Uppercase letters, digits and hyphens, 3–48 characters.
kindenumrequiredbinary, scalar or pair.
tick_sizedecimalrequiredMinimum price increment. Prices not on the tick grid are rejected.
settlement_currencystringrequiredCurrency positions settle in. Any code you have configured, including play-money units.
lot_sizedecimaloptionalMinimum quantity increment. Defaults to "1".
min_pricedecimaloptionalFloor. Defaults to "0" for binary markets.
max_pricedecimaloptionalCeiling. Defaults to "1" for binary markets; required for scalar.
maker_fee_bpsintegeroptionalBasis points charged to the resting side. May be negative to pay a rebate. Default 0.
taker_fee_bpsintegeroptionalBasis points charged to the aggressing side. Default 0.
closes_attimestampoptionalWhen trading stops automatically. The market moves to closed.
metadataobjectoptionalUp to 16 key/value pairs of your own. Returned on every market object, never interpreted.
Response 201
{
  "id": "mkt_8Kd2nQr5vXw9",
  "symbol": "WILL-IT-RAIN-BA",
  "kind": "binary",
  "status": "open",
  "tick_size": "0.01",
  "lot_size": "1",
  "min_price": "0.00",
  "max_price": "1.00",
  "settlement_currency": "USDC",
  "maker_fee_bps": 0,
  "taker_fee_bps": 20,
  "closes_at": "2026-08-01T03:00:00Z",
  "created_at": "2026-07-29T14:22:07Z",
  "metadata": {}
}
GET/v1/markets

Lists markets, newest first. Filter with status, kind or symbol_prefix; page with cursors.

GET/v1/markets/{symbol}

Fetches one market by symbol or by id. Includes last_price, best_bid, best_ask and rolling volume_24h.

GET/v1/markets/{symbol}/book

A point-in-time snapshot of the book, aggregated by price level. depth caps levels per side (default 20, max 500). The seq in the response is the snapshot's position in the market's event stream — the anchor you use when joining the live feed.

Response 200
{
  "symbol": "WILL-IT-RAIN-BA",
  "seq": 48210,
  "bids": [ ["0.61", "250"], ["0.60", "812"] ],
  "asks": [ ["0.62", "140"], ["0.63", "930"] ],
  "as_of": "2026-07-29T14:31:55.204Z"
}
PATCH/v1/markets/{symbol}

Updates a market. Only status, closes_at, the fee fields and metadata are mutable once a market is open. Set status to halted to pause trading while preserving the book, then back to open to resume.

POST/v1/markets/{symbol}/resolve

Declares the outcome and settles every position atomically. Covered in settlement.

Accounts

One account per end user. Accounts hold balances, carry positions, and are the entity that owns orders.

POST/v1/accounts
FieldTypeDescription
referencestringrequiredYour own identifier for the user. Unique per environment; we index it so you can look accounts up by it.
display_namestringoptionalShown in any Clobber-rendered surface. Never used for matching.
metadataobjectoptionalUp to 16 key/value pairs of your own.
GET/v1/accounts/{id}/balances

Returns one entry per currency held. total is everything the account owns; held is collateral locked behind resting orders; available is what a new order can spend.

Response 200
{
  "account": "acc_2fRk8Wq4vNc6",
  "balances": [
    { "currency": "USDC",
      "total": "1000.00",
      "held": "152.50",
      "available": "847.50" }
  ]
}
GET/v1/accounts/{id}/positions

Open positions per market, with qty (negative when short), avg_price, and unrealized_pnl marked against the last trade.

POST/v1/accounts/{id}/credits

Moves value into or out of an account: positive amount credits, negative debits. This is the seam between Clobber and your funding system — call it when a deposit clears on your side, and again when a withdrawal is confirmed.

Credits are not payments

Clobber never touches money rails. A credit is a ledger entry asserting that value exists; making that true — collecting the card payment, confirming the on-chain transfer — is your system's job. Always credit after funds are final, never before.

GET/v1/accounts/{id}/ledger

The full double-entry history for an account: credits, holds, fills, fees and settlements, each with the seq and object that caused it. This is the endpoint to reconcile against if you keep your own books.

Orders

The hot path. Acks land in under a millisecond at p99, and every order is idempotent if you give it a key.

POST/v1/orders

Submits an order. Requires the Clobber-Account header. Returns once the engine has processed it, so the response already reflects any fills that happened on entry.

FieldTypeDescription
marketstringrequiredSymbol or market id.
sideenumrequiredbuy or sell.
typeenumrequiredlimit or market.
qtydecimalrequiredQuantity in contracts. Must be a multiple of the market's lot_size.
pricedecimalconditionalRequired for limit orders, forbidden for market. Must sit on the tick grid.
time_in_forceenumoptionalgtc (default), ioc, fok or day. See order types.
post_onlybooleanoptionalReject rather than take liquidity. Guarantees the maker fee. Default false.
reduce_onlybooleanoptionalOnly shrink an existing position; never open or flip one. Default false.
client_order_idstringoptionalYour identifier, echoed on every event about this order. Unique per account.
expires_attimestampoptionalCancels automatically at this time. Only valid with gtc.
self_trade_preventionenumoptionalcancel_maker (default), cancel_taker or decrement.
Response 201 — partially filled
{
  "id": "ord_5Tn7cWy2gLp8",
  "client_order_id": "alice-bid-0001",
  "account": "acc_2fRk8Wq4vNc6",
  "market": "WILL-IT-RAIN-BA",
  "side": "buy",
  "type": "limit",
  "price": "0.61",
  "qty": "250",
  "filled_qty": "140",
  "remaining_qty": "110",
  "avg_fill_price": "0.6086",
  "status": "partially_filled",
  "time_in_force": "gtc",
  "fees_paid": "0.17",
  "seq": 48211,
  "created_at": "2026-07-29T14:31:55.201Z",
  "fills": [
    { "id": "fil_9Qw3", "price": "0.61", "qty": "100", "liquidity": "taker" },
    { "id": "fil_9Qw4", "price": "0.60", "qty": "40",  "liquidity": "taker" }
  ]
}

Order status

StatusMeaning
openResting on the book, unfilled.
partially_filledSome quantity traded; the remainder is still resting.
filledFully executed. Terminal.
cancelledCancelled by you, by expiry, or by self-trade prevention. Terminal.
rejectedNever reached the book — failed a validation or risk check. Terminal, with a reject_reason.
expiredAn ioc/fok/day order that ran out of time. Terminal.
GET/v1/orders

Lists orders for the account in the Clobber-Account header, newest first. Filter by market, status or client_order_id. Omit the header with an admin key to query across all accounts.

GET/v1/orders/{id}

Fetches one order and its fills. Accepts either a Clobber order id or your client_order_id prefixed with cid:.

DELETE/v1/orders/{id}

Cancels a resting order. Idempotent: cancelling an already-terminal order returns 200 with the order unchanged rather than an error, so retries are always safe.

POST/v1/orders/cancel_all

Cancels every resting order for an account, optionally scoped to one market. Returns the list of cancelled ids. This is the panic button — wire it to your own kill switch.

POST/v1/orders/batch

Submits up to 100 orders in one request. Each is processed independently and in array order; the response array mirrors the input positionally, with a per-entry error where one failed. A rejected entry never aborts the rest.

Order types & time in force

Two types, four lifetimes, two modifiers. Everything else is a combination of these.

SettingBehaviourTypical use
limit + gtcRests until filled or cancelled. The default.Market making, resting interest.
limit + iocFills what it can immediately at the limit or better; cancels the rest.Taking displayed liquidity without leaving a footprint.
limit + fokFills entirely and immediately, or not at all.All-or-nothing execution where a partial is worse than none.
limit + dayRests until filled, cancelled, or the market closes.Session-scoped interest in markets with a closes_at.
marketSweeps the book until qty is filled. Implicitly ioc; any unfillable remainder is cancelled.Immediate execution when price matters less than certainty.
post_onlyIf the order would take liquidity on entry, it is rejected with would_take_liquidity instead.Guaranteeing maker status and maker fees.
reduce_onlyQuantity is trimmed so the order can only shrink the existing position, never flip it.Safe unwinding, stop-loss logic.
Market orders in thin books

A market order sweeps every level until it is filled, and a binary book can be very thin near its edges. Set max_slippage_bps to cap how far from the top of book you are willing to sweep; the unfilled remainder cancels instead of paying through.

Fills & trades

A trade is the public event; a fill is one side's private view of it. Every trade produces exactly two fills.

GET/v1/markets/{symbol}/trades

Public tape for a market, newest first: price, quantity, aggressing side and seq. No account information is exposed.

GET/v1/fills

Your side of the tape: one row per execution for the account in the header, including liquidity (maker or taker), the fee charged, and the order and trade it belongs to. Filter by market, order, or a since timestamp.

Response 200
{
  "data": [
    { "id": "fil_9Qw3mZx1",
      "trade": "trd_4Bv8kNs2",
      "order": "ord_5Tn7cWy2gLp8",
      "market": "WILL-IT-RAIN-BA",
      "side": "buy",
      "price": "0.61",
      "qty": "100",
      "liquidity": "taker",
      "fee": "0.12",
      "seq": 48211,
      "created_at": "2026-07-29T14:31:55.201Z" }
  ],
  "next_cursor": "cur_MTQ4MjEx"
}

Settlement

Declare an outcome; every position pays out in one atomic ledger transaction. There is no partial settlement state to clean up.

POST/v1/markets/{symbol}/resolve
FieldTypeDescription
outcomestringrequiredFor binary: yes, no or void. For scalar: a decimal inside the market's range.
evidence_urlstringoptionalA link to your source of truth. Stored on the market and echoed to every participant.
cancel_restingbooleanoptionalCancel any orders still on the book first. Default true; a market cannot resolve with live orders.

What the engine does

  1. Moves the market to closed and cancels resting orders, releasing their holds.
  2. Computes each position's payout: for a binary market, 1.00 per contract on the winning side and 0.00 on the losing side.
  3. Writes every payout as one double-entry ledger transaction. Either all of it commits or none of it does.
  4. Moves the market to settled and emits a market.settled event on the feed and to your webhook.

A void outcome refunds instead of paying out: each account gets back exactly what it paid, positions close at cost, and fees already charged are reversed. Use it when the underlying event cannot be adjudicated.

Resolution is irreversible

Once a market reaches settled, the payouts are final and there is no un-resolve endpoint — reversing one would mean debiting users who have already withdrawn. Resolve from an admin key held by your backend, never from a client, and consider a two-person approval step in your own system before the call goes out.

WebSocket feed

One connection, many subscriptions. Book deltas, the public tape, and your own private order and balance events, all sequenced.

WSwss://feed.clobber.dev

1. Connect and authenticate

Public channels work unauthenticated. To receive private events, send an auth frame as the first message. Anything else before it is rejected.

Client → server
{ "op": "auth",
  "key": "ck_live_...",
  "account": "acc_2fRk8Wq4vNc6" }

2. Subscribe

Client → server
{ "op": "subscribe",
  "channels": ["book", "trades", "orders"],
  "markets": ["WILL-IT-RAIN-BA"] }

The server replies with a subscribed confirmation, then a full snapshot for each book you joined, then deltas from that snapshot's seq forward. Apply the snapshot first and the deltas after, and your local book is exact.

3. Handle frames

Server → client
// book delta — replace the level, do not add to it
{ "channel": "book", "market": "WILL-IT-RAIN-BA", "seq": 48212,
  "bids": [["0.61", "110"]], "asks": [] }

// public trade
{ "channel": "trades", "market": "WILL-IT-RAIN-BA", "seq": 48211,
  "price": "0.61", "qty": "100", "taker_side": "buy",
  "ts": "2026-07-29T14:31:55.201Z" }

// private fill on your order
{ "channel": "orders", "event": "fill", "seq": 48211,
  "order": "ord_5Tn7cWy2gLp8", "price": "0.61", "qty": "100",
  "remaining_qty": "150", "liquidity": "taker" }
A delta of zero means gone

Book deltas carry the new absolute size at a price level, not a difference. A level quoted as ["0.61", "0"] has been removed entirely. Treat every delta as an assignment and your book can never drift.

Channels

ChannelAuthEmits
bookpublicA snapshot on subscribe, then aggregated level deltas on every change.
tradespublicEvery execution in the market: price, quantity, aggressing side.
tickerpublicTop of book, last price and 24h volume, throttled to once per second.
ordersprivateLifecycle for your account's orders: accepted, fill, cancelled, rejected, expired.
balancesprivateBalance and hold changes as they happen, including settlement payouts.
marketspublicStatus transitions: opened, halted, closed, resolved, settled.

Subscribe to markets without a markets array to receive status changes for every market on your platform — useful for a single control-plane connection that watches everything.

Sequencing & resync

The feed guarantees no gaps and no reordering. If you see a gap, you dropped it — and recovery is three steps.

Track the last seq you applied per market. When a frame arrives with seq greater than last_seq + 1, do not try to patch around it:

  1. Discard your local book for that market. It is now untrustworthy.
  2. Fetch GET /v1/markets/{symbol}/book and read its seq.
  3. Apply buffered deltas with a higher seq than the snapshot, discard the rest, and resume.

Buffer incoming frames while the snapshot request is in flight rather than pausing your reader — otherwise backpressure builds and the server disconnects you.

Heartbeats and reconnection

The server sends a WebSocket ping every 15 seconds. Reply with a pong — most client libraries do this automatically. Three missed pongs and the connection is closed with code 4008. On reconnect, resubscribe and treat every book as cold: take a fresh snapshot rather than assuming your old state survived.

Close codeMeaningWhat to do
4001Authentication failed or key revoked.Do not retry blindly. Check the key.
4003Subscribed to a private channel without auth.Send the auth frame first.
4008Heartbeat timeout.Reconnect with backoff, then resync.
4009Client too slow; the send buffer overflowed.Read faster or subscribe to fewer markets.
4029Too many connections for this key.Multiplex markets over one connection.

Webhooks

For events your backend must not miss even when no socket is open — settlements above all.

Register an endpoint with POST /v1/webhooks and pick the events you want: market.opened, market.halted, market.resolved, market.settled, order.filled, account.balance_changed. Deliveries are at-least-once and retried on any non-2xx with exponential backoff for 24 hours, so make your handler idempotent on event.id.

Verifying signatures

Every delivery carries a Clobber-Signature header: t=<unix_ts>,v1=<hex_hmac>. The HMAC is SHA-256 over "{t}.{raw_body}" keyed with your webhook secret. Compare in constant time, and reject anything with a timestamp older than five minutes to shut down replays.

Node.js
import crypto from 'node:crypto';

function verify(rawBody, header, secret) {
  const [t, v1] = header.split(',').map(p => p.split('=')[1]);
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;

  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${t}.${rawBody}`)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(expected), Buffer.from(v1)
  );
}
Verify against the raw body

Compute the HMAC over the exact bytes you received, before any JSON parsing. Re-serialising the parsed object changes key order and whitespace, and the signature will never match.

Idempotency

Retry any write safely. Send an Idempotency-Key and a repeat request returns the original result instead of doing the work twice.

Supply a unique key — a UUID is ideal — on every POST. If the same key arrives again within 24 hours, the stored response is replayed verbatim, including the original status code, and no second order is placed.

Request header
Idempotency-Key: 7f3c1a90-4e2b-4c11-9a7d-1f2e3d4c5b6a

Replayed responses carry Clobber-Idempotent-Replay: true so you can tell a fresh execution from a deduplicated one. Reusing a key with a different body is an error — 409 idempotency_key_reused — rather than a silent overwrite, which catches key-generation bugs before they cost you a duplicate fill.

Network timeouts

A timeout tells you nothing about whether the order reached the engine. With an idempotency key, the correct response is simply to retry the identical request: you either get the original result back or the order is placed once. Without one, you must query by client_order_id before retrying.

Pagination

Cursor-based, stable under writes. Offsets would skip or duplicate rows in a book that changes ten times a second.

List endpoints take limit (default 50, max 200) and return next_cursor when more rows exist. Pass it back as cursor. A response without next_cursor is the last page.

curl
curl "https://api.clobber.dev/v1/fills?market=WILL-IT-RAIN-BA&limit=100" \
  -H "Authorization: Bearer $CLOBBER_KEY" \
  -H "Clobber-Account: acc_2fRk8Wq4vNc6"

# then, for the next page:
curl "https://api.clobber.dev/v1/fills?cursor=cur_MTQ4MjEx&limit=100" ...

Errors

Conventional HTTP status codes, plus a stable machine-readable code you can branch on. Never parse the message.

Response 422
{
  "error": {
    "code": "insufficient_balance",
    "message": "Account has 12.40 USDC available, order requires 152.50.",
    "param": "qty",
    "request_id": "req_3Hj7pLm9"
  }
}

Every response carries a request_id, in the body on errors and in the Clobber-Request-Id header always. Quote it when you contact support and we can pull the exact engine trace.

Status codes

StatusMeaning
200 / 201Success.
400Malformed request — bad JSON, unknown field, wrong type.
401Missing, malformed or revoked API key.
403The key is valid but lacks the role for this operation.
404No such market, order or account in this environment.
409Conflict — duplicate symbol, reused idempotency key, illegal state transition.
422Well-formed but unacceptable: failed a balance, tick, lot or risk check.
429Rate limited. Back off and retry.
500 / 503Our fault. Safe to retry with an idempotency key.

Common error codes

CodeStatusCause and fix
account_required400An order endpoint was called without the Clobber-Account header.
invalid_tick422Price is not a multiple of the market's tick_size. Round to the grid before sending.
invalid_lot422Quantity is not a multiple of lot_size.
price_out_of_bounds422Price sits outside min_pricemax_price.
insufficient_balance422Available balance will not cover the required hold. Check available, not total.
market_halted422The market is halted or closed. Only cancels are accepted.
would_take_liquidity422A post_only order would have crossed. Re-price behind the touch.
self_trade_blocked422The order would have matched your own resting order under cancel_taker.
order_not_cancellable409The order already reached a terminal state. Usually benign on a retry.
idempotency_key_reused409The same key was sent with a different body. Generate a fresh key per logical request.
market_not_resolvable409Resolution was attempted from a state other than open or closed.

Rate limits

Per API key, token-bucket, with the order path deliberately given the most room.

PlanOrders /secOther REST /secWS connections
Sandbox50205
Production50010050
DedicatedUnmetered1,000500

Every response includes Clobber-RateLimit-Limit, Clobber-RateLimit-Remaining and Clobber-RateLimit-Reset. A 429 also carries Retry-After in seconds — honour it, and add jitter so a fleet of workers does not synchronise into a thundering herd.

Buckets refill continuously rather than at a fixed boundary, so short bursts above the steady rate are fine as long as the average holds.

SDKs

Thin, typed wrappers over the same REST and WebSocket surface. Everything here is doable with an HTTP client if you would rather not add a dependency.

Install
# Node.js / TypeScript
npm install @clobber/sdk

# Python 3.9+
pip install clobber

# Go 1.21+
go get github.com/clobberdev/clobber-go
TypeScript
import { Clobber } from '@clobber/sdk';

const clobber = new Clobber({ apiKey: process.env.CLOBBER_KEY });

const order = await clobber.orders.create(
  { market: 'WILL-IT-RAIN-BA', side: 'buy',
    type: 'limit', price: '0.61', qty: '250' },
  { account: 'acc_2fRk8Wq4vNc6', idempotencyKey: crypto.randomUUID() }
);

const feed = clobber.feed.connect();
feed.subscribe({ channels: ['book', 'orders'], markets: ['WILL-IT-RAIN-BA'] });
feed.on('fill', (f) => console.log(`filled ${f.qty} @ ${f.price}`));

The SDKs handle retries with idempotency keys, reconnection with automatic book resync, and decimal handling that never routes a price through a float.

Changelog

Additive changes ship continuously and are listed here. Breaking changes get a new API version.

DateChange
2026-07-14Added max_slippage_bps to market orders, capping how far a sweep may pay through the book.
2026-06-30POST /v1/orders/batch now accepts 100 orders per request, up from 25.
2026-06-02New ticker feed channel: throttled top-of-book for dashboards that do not need full depth.
2026-05-19Added reduce_only orders and the account.balance_changed webhook event.
2026-04-28Scalar markets left beta. min_price/max_price are now required for kind: "scalar".
2026-03-11Idempotency window extended from 1 hour to 24 hours.

Still have questions?

The fastest route to a human is the contact form on the main site — tell us what you are building and we will get back to you. Dedicated customers get a shared Slack channel with the founder.