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 is real in this build
| 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, and the 30-second expiry is enforced against the stored timestamp. |
| Orders, fills, positions | Recorded | Real idempotency, real state machine, a locally produced fill. Written to the same tables in the same shape a live venue will write them. |
| Settlement | Real | Positions settle against their contract’s resolution and the credit is computed once, on our side. |
| Money movement | Yours | Predicta returns exact debit, release and credit instructions. Your ledger applies them. |
| Webhook delivery | Queued, not sent | Events are durable rows. The sender is switched off: poll instead. |
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. Predicta never holds player funds. What Predicta 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, predictaFee, averagePrice — are for display.
Three endpoint groups 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 |
A Predicta-held balance is not the production integration path
These endpoints model a per-user cash position derived from Predicta'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 Predicta as a custodian of its players' funds, which Predicta 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 // the fill was produced locally; no order reached a venueRead this
simulated: true is the answer on every response in this build. If your code treats a simulated fill and a live fill the same way, it will keep working when execution is switched on — and if it does not, you find out here rather than in production.
Routing, stated precisely
Predicta 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.
routableisfalseunless a human has verified a group as equivalent, which is the answer for every group today.- Predicta does not claim best execution across sources, and will not until that verification exists.
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.
PREDICTA_BASE=https://<issued-to-you>
PREDICTA_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.

