What’s fundamentally different
Five changes underlie the rest of this guide:- Capital moves to a proxy wallet. You deploy and fund a secure proxy. A new prerequisite with no V2 equivalent.
- One endpoint to make and take. Maker and taker both POST to
/orders-v3.timeInForce(GTCrests;IOC/FOKexecute immediately) determines which you are. - API keys are required on every authenticated request — and the header is renamed: V2
X-Api-Key→ V3x-sx-api-key. Search-and-replace it everywhere. - Refund and capital efficiency events are in-lined. No longer separate events or endpoints
- Order signing process has changed
pendingFillsfields have been removed.
What has not changed
percentageOddsis still implied probability × 10^20;totalBetSizeis still your stake in base-token units (USDC = 6 decimals). See Unit conversions.- The odds ladder is still enforced (default step 0.125%). Read
oddsLadderStepSizefromGET /metadata/obv3. See Odds rounding. - Resting orders are still quoted in the maker frame; taker implied probability = 1 − maker probability. (
showTakerPerspective=trueon the orderbook snapshot does the inversion for you.) - You still sign with your EOA private key, and maker is still your EOA address.
- Market discovery is unchanged:
GET /markets/active,GET /markets/find, and the sports / leagues / fixtures endpoints and shapes. - The
{ "status": ..., "data": ... }response envelope is unchanged. - The realtime endpoint and protocol are unchanged (
wss://realtime.sx.bet/connection/websocket, Centrifugo), and thefixtures:*,markets:global,main_line:globalandparlay_markets:globalchannels are unchanged. - Testnet hosts are unchanged:
https://api.toronto.sx.betandwss://realtime.toronto.sx.bet/connection/websocket.
Step-by-step guide
1
Set up the account: deploy and fund a proxy wallet
Deploy a proxy your account owns and move USDC into it — see Accounts. Easiest way is just login to sx.bet and go through the wizard.Prefer to stay in code? The whole flow is API-first too (all with the Funding the proxy replaces V2’s “enable betting” step. There is no longer an ERC-20 approval of the
x-sx-api-key header):TokenTransferProxy (POST /orders/approve is gone)Already live on V2? Your trading capital currently sits in your EOA under V2’s model; to trade on V3 it must be inside the proxy. Login to your SX Bet account and follow the pop-up wizard to migrate.2
3
Use POST /orders-v3 for posting orders and fills
-
There is no longer a separate endpoint for fills. The endpoint is
POST /orders-v3timeInForceis new and required and the signature has changed.timeInForceimplies if your order will rest or not. -
If you’re a market maker, use
GTC. If you’re a taker, useIOC. See Time in force. - The signing of the order has also changed. See the example below.
-
The old
apiExpiryfield was eliminated and merged into just a singleexpiryfield. In V3expiryis a real unix-seconds timestamp inside the signature (0 = never). Because it is signed, changing an order’s TTL means re-signing. V2’s constant2209006800is gone — do not send it. -
The V2 slippage vocabulary is also gone — there are no
desiredOddsoroddsSlippagefields, and noODDS_STALEerror.percentageOddsis the worst price you will accept; the engine matches at that price or better. See Odds. -
This endpoint, by default, is now asynchronous, meaning that the outcome of your order (filled / partially filled / cancelled) is not known immediately. Use
waitForOutcometo keep the old synchronous behaviour. This applies to both orders that will match, clear, or rest.
POST /orders/new and POST /orders/fill/v2) to V3 (POST /orders-v3):marketHash, maker (your EOA, never the proxy), baseToken, totalBetSize, percentageOdds, salt, and isMakerBettingOutcomeOne are unchanged. The EIP-712 domain version is now "1", not "1.0".status (PENDING/FAILED) is the accept/reject of your submission and is independent of outcome.state. outcome is present only because waitForOutcome was set; without it the call returns as soon as the order is accepted and you track the result on the account:orders_v3 channel instead.Worked example — the two frames: you post GTC with percentageOdds = "52500000000000000000" (52.5% — your implied probability on your own outcome). A taker who wants the other outcome sees your level as 47.5% under showTakerPerspective=true and submits their own order with percentageOdds = "47500000000000000000" as their worst acceptable price.Full new signing guide: EIP-712 order signing.4
Taking liquidity
As mentioned above, the fill endpoint is gone. Taking is just submitting an order with An
timeInForce: "IOC" / "FOK". percentageOdds is the worst price you will accept.IOC order fills what it can and cancels the rest, so a partial fill returns outcome.state: "PARTIAL_FILL_DONE" with the unmatched remainingAmount cancelled (a FOK order instead fills completely or cancels whole). Drop waitForOutcome and the call returns as soon as the order is accepted (status: "PENDING", no outcome), and you track fills on the account:fills_v3 channel instead.5
Adjust cancel actions
Cancels no longer have an extra signature. In V3 all three are just
Cancel-by-id waits for the engine and reports per order; cancel-by-event and cancel-all are
asynchronous and only confirm the job was published. See Cancelling orders.
DELETEs authenticated by your API key. CancelOrderV2SportX / CancelOrderEventsSportX / CancelAllOrdersSportX EIP-712 domains are now gone.6
Re-arm your heartbeat (dead-man switch)
Endpoint renamed: V2 Semantics are unchanged: miss the window and every open order is cancelled, arriving on the
POST /heartbeat → V3 POST /heartbeat/v3. V2’s requestor field is gone (the timer is keyed to your API key’s account):account:orders_v3 channel as inactiveReason: "HEARTBEAT_TIMEOUT". Refresh well inside the window (e.g. every 20s on a 60s timeout).To disarm, call the same endpoint with timeoutSeconds: 0 — that clears an armed timer. There is no separate cancel route; V2’s POST /heartbeat/cancel has no V3 successor. See Heartbeat.7
Realtime changes
The token route: point it at Full reference: Real-time data.
GET /user/realtime-token-v3/api-key (V2 used GET /user/realtime-token/api-key). Adjust
channel names and a few payload keys.The addresses in the channel keys are your EOA / user address checksummed and NOT your proxy wallet address.
fixtures:*, markets:global and main_line:global are unchanged.The order book channels are more than a rename — they no longer stream individual orders. See the next step, Re-model the order book feed, for the new anonymous, aggregated shape.Delivery is at-least-once, so a message can be replayed. De-duplicate on the client using the
messageId in ctx.tags — drop any publication whose messageId you have already seen. See
Realtime reliability.The orderbook channels (orderbook_v3 and
orderbook_v3_event) are the exception: use their
version field to order and de-duplicate updates within a market — apply a publication only when its
version is strictly greater than the one you hold. See Book versioning.8
Re-model the order book feed
In V2, Subscribe first, then seed from REST inside the Full reference: Order book updates and Event order book updates.
order_book:market_{marketHash} published a stream of individual order rows/deltas you merged into a local book. In V3, orderbook_v3:{marketHash} (and orderbook_v3_event:{eventId}) publish the entire resting book on every update, aggregated by price into anonymous levels.- No per-order identity. Levels carry only
percentageOddsand aggregatesize. Your own orders still arrive individually onaccount:orders_v3_#{address}. - Replace, don’t merge. V2 required upsert/remove by
orderHash; every V3 publication is the complete book for that market. - Version-gate instead of
updateTime. Each publication carries aversion(a single monotonic string). Apply a message only when itsversionis strictly greater than the one you hold for that market. See Book versioning. - Sides are explicit. Levels are split into
outcomeOne/outcomeTwo(always the maker frame), replacing V2’s per-rowisMakerBettingOutcomeOne. There is noshowTakerPerspectiveon this channel (unlikeGET /orderbook-v3/snapshot); invert yourself withtakerOdds = 1 − percentageOdds / 10^20.
subscribed handler, and run both the snapshot and every publication through the same version rule so ordering never depends on timing. When the channel replays the book from history (recovered: true), skip the seed entirely. Full pattern: Seeding and subscribing to the order book.9
Parlay RFQ market makers
Nothing changes about how you hear requests: subscribe to
parlay_markets:global exactly as in V2.What changes is how you respond. Price the request, then post your quote to POST /orders-v3 with timeInForce: "GTC" against the parlay marketHash, signed the new way (see the posting step above). Use a real expiry no later than the parlay market’s own expiry — expiry: 0 is rejected on parlays. Full walkthrough: Market making parlays.10
Adjust query endpoints
We have renamed the data types to more intuitive names, and adjusted the endpoints.
These endpoints all require an API-key and you can only see your own activity.
See Grain types.Three changes apply across all four:
- Rsults are bound to your API key, so V2’s
bettor=...andmaker=truequery params are gone. - Pagination is now uniform:
perPage+nextKeycursor everywhere. - V2’s
settled=true/falsebecomes thestatusfilter (PENDING/LOCKED/SETTLED/FAILED).
orderHash or fillHash, remap it.11
Read refunds from rows, not a refund endpoint
There is no refund endpoint or refund channel in V3. Capital-efficiency refunds
are republished as fields inline on the trade and fill rows you already read — not as a separate resource or
event. (If you never consumed CE refunds in V2, there is nothing to do here.)
12
Understand the new order and trade state machines
Order status —
PENDING → ACTIVE → INACTIVE (IOC/FOK orders skip ACTIVE and go straight to INACTIVE):FILLED is an inactiveReason, not a status: a fully filled order is status: "INACTIVE" with
inactiveReason: "FILLED". See Order lifecycle.Bet status — PENDING → LOCKED → SETTLED, or PENDING → FAILED:See Bet lifecycle.
Endpoint changes
Rate limits have changed
V2 limited endpoint groups (allPOST /orders/* shared 5,500/min; all GET /trades/* shared 200/min). V3 limits are per-endpoint. Relevant changes:
Full list, response headers, and the global bucket: Rate limits.
Migration checklist
Run it on testnet (api.toronto.sx.bet) first.- Create an API key. Apply the key on every privileged endpoint.
- Deploy and fund the proxy.
- Fetch
GET /metadata/obv3; wiredomain,activeAsset.baseToken,oddsLadderStepSizeandlimits.*into config; delete theexecutorAddresslookup. - Re-implement order signing.
- Post a minimum-size
GTCwithwaitForOutcome: true; then take it with anIOCfrom a second account. - Port cancels to the three
DELETEs; delete the V2 cancel-signing code; test “by id”, “by event”, and “all”. - Move realtime: new token route, channel renames per the “Realtime changes” step,
messageIddedup, andversion-gating on the orderbook channels. - Point accounting at
/trades-v3//fills-v3//positions-v3; remaporderHash/fillHashids; switch pagination toperPage+nextKey. - Arm
POST /heartbeat/v3and let it lapse once on testnet to watchHEARTBEAT_TIMEOUTcancel your orders.
Improvements
What changed in V3.
Quickstart
The full V3 flow, metadata to placed bet.