Getting started
Environments
Sandbox is a property of the tenant, not of a request or a host name. Read simulated on every response rather than inferring the environment from the URL you happen to be pointed at.
What each layer does
| Layer | State | What that means for you |
|---|---|---|
| Market catalogue | Live | Real questions, real outcomes, normalised and de-duplicated across sources. |
| Prices | Live | Streamed or polled from upstream books. Age is published on every quote. |
| Quotes | Live pricing, real expiry | Priced from a real book where one exists. The 30-second expiry is enforced against the stored timestamp, and is 5 seconds on a five-minute crypto contract. Read expiresAt rather than assuming either. |
| Orders | Live | A durable Parity order id, real idempotency, a real state machine and real collateral. The row is written before anything is sent, and the reserve handshake is what makes an order executable. |
| Fills and positions | Real rows | A fill opens or extends a position, with real weighted averages. A SANDBOX tenant, or any tenant sending `executionIntent: "simulate"`, gets a locally produced fill, written to the same tables in the same shape. A live order produces no fill until it is transmitted, which needs venue execution enabled for the deployment and that order armed. Read `transmissionState`. |
| Settlement | Real | Positions settle against their contract’s resolution and the credit is computed once, on our side. |
| Money movement | Yours | Parity returns exact debit, release and credit instructions. Your ledger applies them. |
| Webhook delivery | Per deployment | Events are durable rows and the signed sender exists. HTTP delivery is behind `WEBHOOK_DELIVERY_ENABLED`. Poll `/api/v1/reconciliation` rather than depending on it. |
Who holds the money
This is the fact that decides how your integration is shaped, and it does not change between sandbox and live: the operator owns its customers' cash ledger. Parity never holds player funds. What Parity returns is an exact instruction against your own ledger: authorizationAmount to reserve, actualDebit to take, releaseAmount to give back, sellProceeds to credit on an exit, settlementCredit or voidCredit when a position resolves.
Those amounts are exact decimal strings on the wire ("25.00"), never JSON numbers. The float fields beside them (stake, notional, platformFee, averagePrice) are for display.
Some endpoints are sandbox-only
Sandbox has simulated money, because somebody learning the flow needs funds to spend and somewhere to watch them move. Test funds arrive as a SANDBOX_CREDIT movement from house equity, and the balance and movement reads report it back.
| Endpoint group | In live mode |
|---|---|
GET /api/v1/users/{id}/balance | 403 sandbox_only |
GET /api/v1/users/{id}/ledger | 403 sandbox_only |
/api/v1/funding/* | 403 sandbox_only |
/api/v1/sandbox/* | 403 sandbox_only. The scenario and resolution test controls |
A Parity-held balance is not the production integration path
These endpoints model a per-user cash position derived from Parity's own ledger. That is real and useful in the sandbox and it is the wrong model for a live operator: a partner reading a balance from here would be treating Parity as a custodian of its players' funds, which Parity is not, and would eventually reconcile its own books against a number that describes something else.
So they refuse in live mode rather than returning a figure that invites the misreading. Build against the instructions on quotes, orders and settlements, and your own wallet stays the authority on what a player may stake.
The simulated flag
Every quote, order and settlement payload carries simulated. It is present on every response and never omitted when inconvenient, because a simulated fill that looks identical to a real one is exactly what an integrator should not have to read the docs to discover.
"simulated": true // this fill was produced locally; no order reached a venueRead this
simulated: true is the answer wherever a fill was produced locally: every sandbox response, and every response to a caller that asked for executionIntent: "simulate". Read it as “was this fill real”, and read transmissionState for “did this order reach a venue”. They are different questions and the API answers them separately.
A live order that has not been transmitted has no fill at all, so neither flag describes it. That order reads status: pending_venue_execution, transmissionState: not_transmitted and settlementState: pending. Hold the authorization and do not treat the absence of simulated: true as evidence a venue took it.
It is a fact about that order, not about the deployment
One Parity deployment carries sandbox and live tenants at the same time, and they are not in the same execution mode. So simulated is derived per order from the venue that answered it, and two orders created seconds apart on the same deployment can disagree. Read the field on the order in front of you.
Do not infer it from the host name you are pointed at, from the prefix on your key, or from an earlier order. Those describe your credential and our infrastructure; this field describes one fill.
Routing, stated precisely
Parity groups questions across sources by exact match. That grouping is a display fact: it says two listings ask the same question, not that the contracts are interchangeable. Cutoff times, resolution sources and edge cases in the rules all differ.
- An order is bound to the contract that was quoted. The quote reports
routing.consideredVenues, how many sources could have competed, androuting.routable, whether any actually may. - The alternatives themselves are never listed, because naming them is naming the venues. No partner-facing payload identifies a source anywhere.
routableisfalseuntil a human has verified a group as equivalent.- Parity does not claim best execution across sources.
Sandbox and production hosts
There is one deployment. A key does not encode a host, and there is no separate sandbox domain to point at: your integration host is issued to you directly. Configure it as a variable rather than hard-coding it, so a different host can be swapped in without a code change.
PARITY_BASE=https://<issued-to-you>
PARITY_KEY=pk_test_... # sandbox tenant. Live keys are sk_live_...Sandbox is a property of the tenant
Not of a request, not of a header, not of the URL. The operator record carries the mode and every request inherits it, so there is no parameter a caller could set to reach the other environment and no way to place a live order with a test key by accident.
The sandbox is not a lesser environment
It runs the production pipeline: live prices, real quote expiry, real idempotency, real settlement, the same tables and the same code. What differs is that the fill is produced locally instead of at a venue, and that the money endpoints above are open. Everything you prove there about idempotency, recovery and settlement transfers unchanged.
Health and status
GET /api/health is unauthenticated and reports whether the app can serve, how fresh each price feed is, and how much of the catalogue is streamed rather than polled.
{
"status": "ok", // ok | degraded | starting | unhealthy
"database": "up",
"markets": { "total": 22936, "open": 22936, "resolved": 0 },
"stream": { "running": true, "subscriptions": 1500, "openContracts": 22936,
"streamPricedContracts": 1489, "restPricedContracts": 21447 },
"pricing": { "houseEdgeBps": 0, "maxUsableSpread": 0.2 },
"timestamp": "2026-08-20T04:43:02.980Z"
}degradedis a 200. A single upstream feed being unhealthy is an operating state, not an outage, and your monitoring should be able to tell the two apart.- Only a dead database returns
503. - A feed that has not synced in several reconcile intervals reports
staleeven if its last sync succeeded: liveness, not last-known-status.

