TokenPlay CLOB Market Maker API
Reference and interactive tester for market makers. OpenAPI JSON: openapi.json. Amounts are decimal strings when they may exceed JavaScript's safe integer range.
Start Here
There are two public surfaces. The matching engine holds resting limit orders. The settlement bridge handles market orders, chain preparation and Solana transaction builders.
| Need | Use | Important detail |
|---|---|---|
| Read book, place/cancel limit orders | CLOB_API_BASE / /clob/v1 | API and UI use the same book when the market label is the same. |
| Market orders and funding helpers | CLOB_BRIDGE_BASE / /clob-engine | Market orders fill against the AMM and never rest in the engine book. |
| EVM integration | examples/market-maker/mm-client.mjs | Orders use EIP-712. Market label is evm:<chainId>:<clobMarketId>. |
| Solana integration | examples/market-maker/solana-mm-client.mjs | Orders use base58 ed25519 over a canonical string. Market label is the plain numeric CLOB market id. |
| Deposit, withdraw, claim, LP actions | Wallet-signed chain tx | Solana uses bridge /tx/*. EVM calls ClobExchange directly or via mm-client.mjs. |
First-time test order: choose a preset below, click GET /markets, inspect /book, generate a fresh signature, then POST /orders. A copied old signature will be rejected.
Live Market
BSC Mainnet68c1a4f0e2b7d93a5f6e8b21evm:56:1042The selected network updates the Market field, the wallet, request URLs and both signature scripts. Switching to Solana swaps the signing scheme entirely — see Signature Guide.
Market Maker API List
| Method | Endpoint | Use | Auth / Signature |
|---|---|---|---|
| GET | /markets | List live CLOB books known to this engine. | None |
| GET | /markets/{market} | Summary: best bid/ask levels, open orders, session volume. | None |
| GET | /markets/{market}/ticker | Current top-of-book price/spread/session stats. | None |
| GET | /markets/{market}/book | Aggregated order book depth. | None |
| GET | /markets/{market}/orders | All current resting limit orders. | None |
| GET | /users/{user}/orders?market={market} | One wallet's current resting orders. | None |
| GET | /users/{user}/orders/closed?market={market} | One wallet's cancelled, expired, or rejected orders. | None |
| GET | /markets/{market}/trades | Recent matched fills observed by this engine. | None |
| GET | /orders/{orderId} | Status for one order id. | None |
| POST | /orders | Place a limit/market-maker order. | EVM: EIP-712 signature. Solana: ed25519 over the canonical string. No API key either way |
| DELETE | /orders/{orderId} | Cancel a resting order. | Wallet-signed cancel message |
Every endpoint above is on this engine and takes no API key — the wallet signature is the authentication. Market orders are not here: they fill against the AMM through the settlement bridge, see Market Orders. Money movement is chain-specific: Solana uses bridge /tx/* transaction builders, while EVM users send ClobExchange contract transactions from their own wallet/RPC or with mm-client.mjs. See Money & Collateral.
Do not give market makers privileged close/settlement/resolve endpoints. Those are operator-only.
Recent Requests
| Time | Status | Request |
|---|---|---|
| Make a request to see history. | ||
Suggested Test Flow
| Step | Action | Expected Result |
|---|---|---|
| 1 | Click GET /markets or choose a preset. | You have the correct engine market label. Do not use the app/Mongo market id in /orders. |
| 2 | Click book, orders, ticker. | You see current book depth, resting orders, and current top-of-book stats. |
| 3 | Fund the wallet on chain, then place a signed limit order. | The order is accepted only if the signature matches the exact payload. |
| 4 | Click GET /orders/{orderId}. | You see the exact order status: open, partial, filled, cancelled, or expired. |
| 5 | Cancel from UI, or use signed DELETE /orders/{orderId}. | The order disappears from book/orders. No explorer tx is expected for engine cancel. |
| 6 | Check /users/{user}/orders/closed. | Cancelled, expired, or rejected orders are visible separately from the live book. |
| 7 | Withdraw idle balance from the exchange. | The wallet receives tokens and this withdrawal appears on the explorer. |
| 8 | Use a second wallet to cross the book with a taker order. | Book size changes and app trade history records the fill. If settlement fails, the engine rolls the fill back. |
Market Data Endpoints
Live Markets
Lists live CLOB books known to this engine process. Use this first when you need the exact market label for API calls.
market | Engine label used by /orders, /book, /ticker, and /trades. |
count | How many books are currently live in this engine instance. |
200 | Live market summaries returned. |
Click Try It
Market Summary
Returns top-level book state for one CLOB market: best prices, open order count, depth levels, and current engine-session volume.
market | Engine label, for example evm:97:314. |
200 | Summary returned. |
429 | Rate limit exceeded. |
Click Try It
Ticker
Returns current best bid, best ask, spread, last trade and session stats for the selected market.
bestBid | Highest bid in ticks, if present. |
bestAsk | Lowest ask in ticks, if present. |
spread | bestAsk - bestBid in ticks, if both sides exist. |
Click Try It
Price Depth
Returns aggregated bid and ask levels. This is the same shape the order-book widget reads.
bids[] | Price levels where users want to buy. |
asks[] | Price levels where users want to sell. |
size | Raw base units as a decimal string. |
Click Try It
Current Orders
Returns current resting/open limit orders. Use the optional user filter to inspect one maker wallet.
user | Optional wallet query param. |
signature | Never returned from this public endpoint. |
Click Try It
Trades
Returns recent matched fills observed by this engine process.
limit | Optional, default 50, max 500. |
createdAt | ISO timestamp for the fill. |
Click Try It
User Orders
Returns one wallet's resting orders, optionally scoped to the Market field.
user | Wallet address in the path. |
market | Optional engine market query param. |
Click Try It
User Closed Orders
Returns this wallet's orders that ended without trading: cancelled, expired, or rejected. Filled orders appear in trades/history instead.
user | Wallet address in the path. |
market | Optional engine market query param. |
limit | Optional, default 50, max 500. |
Click Try It
Order Status
Returns status for one order id. Use an id returned from POST /orders or copied from the current orders response.
orderId | Global engine order id, for example o104. |
404 | Order id not found. |
Set Order id, then click Try It
Try POST /orders
Prerequisites For API-Only Trading
PRIVATE_KEY | The market maker's own wallet key. The API never creates signatures. |
RPC_URL | RPC for the chain where the CLOB market lives, for funding calls. |
ENGINE_MARKET | Engine label, for example evm:97:314. |
CLOB_MARKET_ID | Numeric on-chain market id, for example 314. |
CLOB_EXCHANGE | ClobExchange contract address. This is the EIP-712 verifying contract. |
PAYMENT_TOKEN | Collateral token used by this market, usually USDC for EVM markets. |
Three Raw API Requests
This is the exact API flow a market maker bot performs after it has found the engine market label and generated a fresh order signature locally.
GET https://app-staging.tokenplay.ai/clob/v1/markets
POST https://app-staging.tokenplay.ai/clob/v1/orders
content-type: application/json
{
"user": "0x87c088C6F5550516b8Ee80a9608149e140779850",
"market": "evm:97:314",
"action": "buy",
"outcome": "YES",
"price": 660,
"size": "18181818",
"type": "GTC",
"salt": 1787155825206943,
"expiry": 0,
"signature": "0x<fresh_eip712_signature>"
}
GET https://app-staging.tokenplay.ai/clob/v1/markets/evm%3A97%3A314/book
GET https://app-staging.tokenplay.ai/clob/v1/markets/evm%3A97%3A314/orders
The docs button below cannot invent signature. Use the example client or an MM bot to sign first, then send the POST body.
Signature Generation Script — BSC Mainnet
Market makers generate a new signature locally for each unique order payload. The private key stays in their bot or vault; TokenPlay only receives the resulting signature.
EVM orders are EIP-712 typed data signed against the ClobExchange contract for that chain.
import { Wallet } from "ethers";
const wallet = new Wallet(process.env.PRIVATE_KEY);
const chainId = 97;
const clobMarketId = 314n;
const clobExchange = process.env.CLOB_EXCHANGE;
const domain = {
name: "tokenplay-clob-order-v1",
version: "1",
chainId,
verifyingContract: clobExchange,
};
const types = {
Order: [
{ name: "user", type: "address" },
{ name: "market", type: "uint256" },
{ name: "action", type: "uint8" },
{ name: "outcome", type: "uint8" },
{ name: "price", type: "uint256" },
{ name: "size", type: "uint256" },
{ name: "salt", type: "uint256" },
{ name: "expiry", type: "uint256" },
],
};
const message = {
user: wallet.address,
market: clobMarketId,
action: 0, // 0 = buy, 1 = sell
outcome: 0, // 0 = YES, 1 = NO
price: 660n, // 66.0c
size: 18181818n, // 18.181818 shares
salt: BigInt(Date.now()) * 1000n,
expiry: 0n,
};
const signature = await wallet.signTypedData(domain, types, message);
console.log(JSON.stringify({
user: wallet.address,
market: "evm:97:314",
action: "buy",
outcome: "YES",
price: Number(message.price),
size: message.size.toString(),
type: "GTC",
salt: Number(message.salt),
expiry: Number(message.expiry),
signature,
}, null, 2));
POST /orders
This must contain a fresh wallet signature for this exact payload. EVM orders are EIP-712 typed data signed against ClobExchange. The API does not take payment by itself; collateral must already be funded/deposited on-chain by the trading flow.
signature | Generated by the market maker script with wallet.signTypedData(domain, types, message). Do not copy a signature from an old GET /orders/{orderId} response. |
market | API book label, for example evm:97:314. |
message.market | Numeric on-chain market id inside the EIP-712 signed payload, for example 314. |
price | Ticks from 1 to 999. 660 means 66.0c. |
size | Raw share units. With 6 decimals, 18181818 means 18.181818 shares. |
salt | Unique per order. A new salt means a new signature. |
Click Send signed order
Try DELETE /orders/{orderId}
DELETE /orders/{orderId}
Cancel signature must be a wallet signature over: tokenplay-clob-cancel-v1|<user>|<orderId>.
This is an engine cancel, so there is no explorer transaction. Funds become idle/withdrawable in the CLOB exchange; use contract withdraw or npm run mm -- withdraw --amount <amount> to return them to the wallet.
Click Send cancel
Market Orders
This engine holds limit orders only. A market order does not rest in the book — it fills straight against the AMM pool, and that happens in the settlement bridge, a different service on a different base URL: https://app-staging.tokenplay.ai/clob-engine.
1. Quote the pool
GET /clob-engine/quote?market=373 ← Solana
GET /clob-engine/quote?chain=97&market=314 ← EVM
{ "market": 373, "yes": 655, "no": 345 }
Prices in ticks, same scale as an order: 1000 = $1.00. Use the side you are buying as your signed price limit, or a worse one to allow slippage — the fill is bounded by what you signed.
2. Send the signed order
POST /clob-engine/market-order content-type: application/json
The order is signed exactly as a limit order — same message, same scheme. What differs is where you send it and one extra field.
| EVM | The signed order plus chainId. market is the bare on-chain id. |
| Solana | market, user, action, outcome, price, size, salt, expiry, signature, and optionally appMarketId. No chainId. |
EVM, end to end
// The message is signed exactly as for POST /orders — same domain, same struct.
// market inside the SIGNATURE is always the bare id; it is a uint256 on chain.
const signature = await wallet.signTypedData(
{ name: "tokenplay-clob-order-v1", version: "1", chainId: 97, verifyingContract: EXCHANGE },
{ Order: [
{ name: "user", type: "address" }, { name: "market", type: "uint256" },
{ name: "action", type: "uint8" }, { name: "outcome", type: "uint8" },
{ name: "price", type: "uint256" }, { name: "size", type: "uint256" },
{ name: "salt", type: "uint256" }, { name: "expiry", type: "uint256" } ] },
{ user: wallet.address, market: 314n, action: 0, outcome: 0,
price: 700n, size: 10000000n, salt: 1725440000000n, expiry: 0n }
);
await fetch("https://app-staging.tokenplay.ai/clob-engine/market-order", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
chainId: 97, // picks the EVM adapter — omit it and the request goes to Solana
market: 314, // the ON-CHAIN id, NOT the engine label "evm:97:314"
user: wallet.address,
action: "buy", outcome: "YES",
price: 700, // a LIMIT, not a target: quote first, then sign some slippage room
size: "10000000",
salt: 1725440000000, expiry: 0,
signature,
}),
});
Or, with the bundled client: npm run mm -- quote then npm run mm -- market-order --action buy --outcome YES --price 700 --size 10000000.
The mistake worth naming. Every other endpoint on the engine speaks the label evm:97:314, so a body built by copying a working POST /orders call carries it here too — and the EVM adapter reads it as a number, gets NaN, and answers 422 bad market id. There is no marketId field on this route; the field is market. Both spellings are accepted now, but the bare id is the one to write.
The routing rule is the whole trick. The bridge serves both chain families on one set of paths and decides which adapter answers by asking one question: does the body have a chainId field? If yes it goes to EVM, if no it goes to Solana. A Solana order that carries chainId is handed to the EVM adapter and comes back as an unknown market; an EVM order without it goes to Solana and fails on a malformed pubkey. Neither error mentions routing.
Returns 200 with the fill, or 422 when the pool cannot fill it at your signed limit. Unlike POST /orders, nothing is left resting afterwards.
Money: Deposit, Withdraw, Liquidity, Claim
These move funds rather than read them, so the wallet must sign something. The flow differs by chain. Solana: the bridge builds an unsigned transaction through /tx/*, your wallet signs it, and you submit it. EVM: there is currently no /tx/* builder; call ClobExchange directly from the user's wallet/RPC, or use examples/market-maker/mm-client.mjs for testing.
First, make sure the market exists on chain
On EVM, this is the public preparation call the UI shows as Preparing market.... Run it before the first deposit/order on a fresh market; otherwise deposit() can revert MarketMissing. On Solana, the same route is operator-only because it spends authority SOL to create accounts.
POST https://app-staging.tokenplay.ai/clob-engine/ensure-market
content-type: application/json
{"chainId": 97, "market": 314, "closeTime": 0} ← EVM, no token
POST https://app-staging.tokenplay.ai/clob-engine/ensure-market
x-clob-resolve-token: <operator token>
content-type: application/json
{"marketId": 880101} ← Solana operator pre-provision
Solana: build → sign → submit
// 1. ask the bridge to assemble the Solana transaction
const res = await fetch("https://app-staging.tokenplay.ai/clob-engine/tx/deposit", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ marketId: 880101, user: wallet.publicKey.toBase58(), amount: "100000000" }),
}).then(r => r.json());
// { ok: true, transaction: "<base64>", blockhash, lastValidBlockHeight, createsTokenAccount }
// 2. sign it yourself — the only step that touches your key
const tx = Transaction.from(Buffer.from(res.transaction, "base64"));
tx.sign(wallet);
// 3. send it, here or straight to your own RPC
await fetch("https://app-staging.tokenplay.ai/clob-engine/tx/submit", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ signedTransaction: tx.serialize().toString("base64") }),
});
// { ok: true, signature: "…", confirmed: true }
The /tx/* routes are Solana-only. Do not add chainId to these calls; a body with chainId is routed to the EVM adapter, which does not serve /tx/*. /tx/submit waits for confirmation before it answers. Reading an account straight after a bare send is how a working instruction looks like it did nothing.
| Solana endpoint | Body | Builds the instruction |
|---|---|---|
POST /tx/deposit | marketId, user, amount | deposit() |
POST /tx/withdraw | marketId, user, amount | withdraw() |
POST /tx/add-liquidity | marketId, user, amount | addLiquidity() |
POST /tx/remove-liquidity | marketId, user | removeLiquidity() |
POST /tx/claim | marketId, user | claim() |
POST /tx/claim-lp-fees | marketId, user | claimLpFees() |
POST /tx/submit | signedTransaction | broadcasting it yourself |
For Solana /tx/*, amount is raw units in the payment mint's own decimals — USDC has 6, so 100000000 is $100. Same scale an order's size uses; no floats anywhere. A wallet that has never held the mint gets createsTokenAccount: true and the token account is opened inside the same transaction.
Claim is not withdraw
claim turns winning shares into free balance inside the exchange. withdraw is a second transaction that moves tokens to the wallet. Stopping after the first is the most common “where is my money”. remove-liquidity additionally needs the market resolved and the AMM pool to have claimed its own shares, or it reverts AmmNotSettled.
The same thing from a terminal
For trying it by hand before writing a client. EVM uses direct contract transactions through mm-client.mjs; Solana uses unsigned transaction builders or the local helper below.
# EVM, in prediction-backend npm run mm -- ensure-market npm run mm -- balance npm run mm -- deposit --amount 25 npm run mm -- add-liquidity --amount 50 npm run mm -- claim-lp-fees npm run mm -- remove-liquidity npm run mm -- withdraw --amount <clobBalance> # Solana, in tokenplay-clob-bridge node scripts/solana-collateral.mjs balance 880101 node scripts/solana-collateral.mjs deposit 880101 100 --execute node scripts/solana-collateral.mjs add-liquidity 880101 100 --execute node scripts/solana-collateral.mjs claim 880101 --execute node scripts/solana-collateral.mjs withdraw 880101 25 --execute
For EVM, claim is winnings only. LP exit is remove-liquidity, then withdraw. For Solana, solana-collateral.mjs is dry-run unless you pass --execute.
Solana Complete Run
Market 880101 on devnet, created and closed through the Solana /tx/* calls. Check your own run against the arithmetic: when a step is silently doing nothing, the totals stop adding up long before anything throws.
| Step | Call | Wallet | Exchange balance | Shares |
|---|---|---|---|---|
| start | — | $500.00 | — | — |
| create the book | POST /ensure-market | $500.00 | — | — |
| deposit | POST /tx/deposit | $400.00 | $100.00 | — |
| rest an order | POST /orders | $400.00 | $100.00 | — |
| cancel it | DELETE /orders/{id} | $400.00 | $100.00 | — |
| buy 10 YES at 50¢ | POST /market-order | $400.00 | $94.95 | 10 YES |
| resolve YES | POST /resolve | $400.00 | $94.95 | 10 YES |
| claim | POST /tx/claim | $400.00 | $104.95 | 0 |
| withdraw | POST /tx/withdraw | $504.95 | $0.00 | 0 |
Net $4.95 = a $10 payout on 10 winning shares, less the $5 they cost at 50¢, less $0.05 of fee. The fee is 1% of the trade.
Solana Walkthrough
The same Solana run as above, as commands. Paste them in order against a market of your own. The build steps never touch your private key; the signing pipe is the wallet side.
Setup
export ENGINE=https://app-staging.tokenplay.ai/clob/v1 export BRIDGE=https://app-staging.tokenplay.ai/clob-engine export USER_PUB=<your base58 pubkey> export M=880201 # the numeric market id
Amounts below are raw units: USDC has 6 decimals, so 50000000 is $50. Prices are ticks: 1000 = $1.00.
1 — Bring the market up, and look at it
curl -s -X POST $BRIDGE/ensure-market -H "x-clob-resolve-token: $TOKEN" -H 'content-type: application/json' -d "{"marketId":$M}"
# {"ok":true,"market":880201}
curl -s "$BRIDGE/quote?market=$M"
# {"market":880201,"yes":500,"no":500}
curl -s $ENGINE/markets/$M
curl -s $ENGINE/markets/$M/book
curl -s $ENGINE/markets/$M/ticker
The first call is slow: it creates the accounts and funds the AMM. It is idempotent, so run it again freely.
You almost certainly do not need this step, or the token. x-clob-resolve-token is an operator secret — the same one that gates /propose and /resolve, which decide who gets paid — and it is not issued to integrators. A market published through the app is normally brought up on chain before you get to it; reading /quote for it will tell you. If a market really has no book, ask an operator to run this one call for it. Everything else below needs nothing but your own wallet.
2 — Deposit signs
# build — see what comes back before piping it anywhere
curl -s -X POST $BRIDGE/tx/deposit -H 'content-type: application/json' -d "{"marketId":$M,"user":"$USER_PUB","amount":"50000000"}"
# {"ok":true,"transaction":"<base64>","blockhash":"…","createsTokenAccount":false}
# build → sign → submit, in one pipe
curl -s -X POST $BRIDGE/tx/deposit -H 'content-type: application/json' -d "{"marketId":$M,"user":"$USER_PUB","amount":"50000000"}" | jq -r .transaction | node sign.mjs | curl -s -X POST $BRIDGE/tx/submit -H 'content-type: application/json' --data-binary @-
# {"ok":true,"signature":"…","confirmed":true}
sign.mjs is the whole of your side of this. It ships as scripts/sign.mjs in tokenplay-clob-bridge, and it is short enough to paste:
import { Transaction, Keypair } from "@solana/web3.js";
import bs58 from "bs58";
const wallet = Keypair.fromSecretKey(bs58.decode(process.env.USER_KEY));
let b64 = "";
process.stdin.setEncoding("utf8");
for await (const chunk of process.stdin) b64 += chunk;
b64 = b64.trim();
if (!b64 || b64 === "null") {
// jq prints "null" when the build step returned an error instead.
console.error("no transaction on stdin — run the /tx/... call alone and read its error");
process.exit(1);
}
const tx = Transaction.from(Buffer.from(b64, "base64"));
tx.sign(wallet);
process.stdout.write(JSON.stringify({ signedTransaction: tx.serialize().toString("base64") }));
Run it as USER_KEY=<base58 secret key> node sign.mjs. This is a testing convenience: a real integration signs with the user's wallet — Phantom, a Ledger, a custodial signer — and the code is the same three lines, Transaction.from, sign, serialize. Nothing about the transaction is secret; only the signing is.
3 — Rest an order, then cancel it signs
npm run mm:solana -- place --action buy --outcome YES --price 400 --size 5000000
# HTTP 201 {"accepted":true,"order":{"id":"o2","status":"open",…}}
curl -s $ENGINE/markets/$M/book
curl -s "$ENGINE/users/$USER_PUB/orders?market=$M"
export OID=$(curl -s $ENGINE/markets/$M/orders | jq -r '.orders[0].id')
curl -s $ENGINE/orders/$OID
# {"order":{"id":"o2","status":"open","remaining":"5000000"}} ← no salt, no signature
npm run mm:solana -- cancel --id $OID
curl -s "$ENGINE/users/$USER_PUB/orders/closed?market=$M"
Swap place for message to print the signed string and the signature without sending anything — the fastest way to see why a signature is being rejected.
4 — Prove the signature is really checked
curl -s -X POST $ENGINE/orders -H 'content-type: application/json' -d "{
"user":"$USER_PUB","market":"$M","action":"buy","outcome":"YES",
"price":500,"size":"1000000","type":"GTC","salt":42,"expiry":0,
"signature":"<any other valid base58 signature>"}"
# 422 {"accepted":false,"reason":"invalid signature"}
5 — Trade against the AMM signs
curl -s -X POST $BRIDGE/market-order -H 'content-type: application/json' -d '{"market":880201,"user":"<pubkey>","action":"buy","outcome":"yes",
"price":700,"size":5000000,"salt":123,"expiry":0,"signature":"<base58>"}'
# {"ok":true,"sig":"…","execPrice":500}
curl -s "$BRIDGE/quote?market=$M"
# {"yes":667,"no":333} ← the AMM sold YES, so YES got dearer
Your signed price is a limit, not a request: 700 here means “fill me at 70¢ or better”, and it filled at 50¢. A 422 means the pool could not do it within your limit.
6 — Liquidity, resolution, payout signs
curl -s -X POST $BRIDGE/tx/add-liquidity -H 'content-type: application/json' -d "{"marketId":$M,"user":"$USER_PUB","amount":"25000000"}" | jq -r .transaction | node sign.mjs | curl -s -X POST $BRIDGE/tx/submit -H 'content-type: application/json' --data-binary @-
curl -s -X POST $BRIDGE/resolve -H "x-clob-resolve-token: $TOKEN" -H 'content-type: application/json' -d "{"marketId":$M,"outcome":"YES"}"
# {"ok":true,"txHash":"…","ammTx":"…"} ← ammTx null means the pool held no shares
for k in claim claim-lp-fees remove-liquidity; do
curl -s -X POST $BRIDGE/tx/$k -H 'content-type: application/json' -d "{"marketId":$M,"user":"$USER_PUB"}" | jq -r .transaction | node sign.mjs | curl -s -X POST $BRIDGE/tx/submit -H 'content-type: application/json' --data-binary @-
done
7 — Withdraw signs
# the balance after a claim is never a round number — read it, do not guess
curl -s -X POST $BRIDGE/tx/withdraw -H 'content-type: application/json' -d "{"marketId":$M,"user":"$USER_PUB","amount":"76975000"}" | jq -r .transaction | node sign.mjs | curl -s -X POST $BRIDGE/tx/submit -H 'content-type: application/json' --data-binary @-
Leaving a placeholder in amount makes the build return an error, jq print null, and the signer choke on nothing. If a pipe dies oddly, run the /tx/ call alone and read its error.
Check the arithmetic, not just the status codes
Deposit $50, buy 5000000 at an exec price of 500, and the position should read $47.475: the trade cost $2.50 of notional and $0.025 of fee. After YES resolves, claim adds $5.00 — the shares are worth par — for $52.475. Removing $25 of liquidity returns $24.50, not $25: the pool sold shares for $2.50 that settled at $5.00, and your 25 of its 125 shares carry 20% of that $2.50 loss. A run where every call returns 200 and the totals do not close is a run that has quietly failed.
Rate Limits
API Rate Limits
Defaults are intentionally generous and configurable per environment.
CLOB_API_RATE_LIMIT_PUBLIC | Default 600 requests per window for reads and docs-backed API calls. |
CLOB_API_RATE_LIMIT_ORDERS | Default 120 order write requests per window. |
CLOB_API_RATE_LIMIT_WINDOW_MS | Default 60000. |