Markets
Events and outcomes
An event is a question. An outcome is a contract you can actually buy, addressed by an opaque ctr_ id. Quotes are always against an outcome, never against an event.
The partner catalogue
/api/v1/eventsAPI keymarkets:read./api/v1/events/{id}API key{id} is the event id or its slug. markets:read./api/v1/categoriesAPI key?category= accepts, with a live event count for each. markets:read./api/v1/images/{id}No auth{id} is the event id. No credentials: it is an <img> in a browser./api/events and /api/markets are the public surface, not your contract
Those routes power Predicta's own site. They page by offset, they are not narrowed to what you are offered, and the contract feed carries upstream identifiers and source metadata in its payload. Build against /api/v1: it is keyset-paged, scoped to your operator, and every contract is named by an opaque ctr_… id that identifies no venue.
The model
| Object | What it is | Example |
|---|---|---|
| Event | One question, grouped across sources. What a player browses. | “Fed decision in September?” |
| Outcome | One binary contract belonging to that event, addressed by a ctr_ id. What is traded. | “No change” |
| Side | YES or NO on one outcome. Both sides of every outcome are tradeable. | YES on “No change” |
A binary event has one outcome and the question is answered by which side you take. A multi-answer race has one outcome per answer, each with its own YES and NO. Every contract settles at exactly $1.00 or exactly $0.00: a market that resolved 99% certain did not resolve.
Event kinds
| kind | Meaning | How to render it |
|---|---|---|
| binary | One outcome. | YES / NO on the question itself. |
| mutually_exclusive | A field of answers, exactly one of which is true. | The leaders, with a “+N more”. Probabilities across the field are comparable. |
| cumulative_ladder | Nested thresholds where one rung implies the next. | Never two rungs side by side. The two likeliest rungs of a nested set are the two loosest ones — 86.5% and 76.5%, nearly the same statement — and printing them together reads as a two-way choice when one implies the other. Show impliedLevel instead, and order the rungs by threshold. |
| ambiguous | The shape could not be established. | Present the leaders, never as a complete picture. |
Listing events
| Parameter | Type | Notes |
|---|---|---|
| category | string | One canonical category. An unknown value is 400 invalid_category, not a silently unfiltered catalogue. Sports is never returned. |
| search | string | Free text over the question. |
| sort | string | trending (default), volume, closing-soon, new |
| dir | string | asc or desc. |
| limit | integer | Default 25, 1–100. |
| tradable | boolean | true restricts to events with at least one executable side. |
| closesAfter / closesBefore | ISO-8601 | Window on the close time. |
| cursor | string | Opaque. Echo nextCursor from the previous page. |
curl -s "$PREDICTA_BASE/api/v1/events?category=economy&sort=volume&limit=10" \
-H "Authorization: Bearer $PREDICTA_KEY"{
"data": [
{
"id": "evt_…",
"slug": "fed-decision-september",
"title": "Fed decision in September?",
"subtitle": null,
"category": "economy",
"imageUrl": "/api/v1/images/evt_…", // a Predicta path, not an upstream CDN
"kind": "mutually_exclusive",
"outcomeCount": 5,
"impliedLevel": null,
"impliedLevelLabel": null,
"closesAt": "2026-09-16T00:00:00.000Z",
"volume": 8102934.11,
"volume24h": 415244.02,
"status": "open",
"outcomes": [ /* see below */ ]
}
],
"pagination": { "limit": 25, "total": 812, "nextCursor": "…", "hasMore": true }
}The cursor contract
- Paging is keyset, not offset. Events arrive while you page, and an offset would let one that appeared between two requests push an older event across the boundary where you never see it — a missing row, reported as a successful page.
- A cursor is checked against the ordering it was minted under. Replaying a
sort=volumetoken againstsort=newis400 invalid_cursorrather than a confidently wrong slice. Restart from the first page. - A full page issues a cursor, so the last page is whichever comes back short. You may spend one empty request discovering the end.
pagination.totalis the size of the whole filtered set, not what remains after the cursor.
// The SDK hides the cursor loop entirely.
for await (const event of predicta.iterateEvents({ category: 'economy', tradable: true })) {
upsert(event);
}The outcome object
{
"id": "ctr_9f31c2…", // QUOTE AGAINST THIS. Opaque; names no venue.
"label": "No change",
"probability": 0.715, // reference, 0–1
"yesCents": 71.5, // reference, in cents
"noCents": 28.5,
"executableYesCents": 72, // what a BUY costs. null = no usable side
"executableNoCents": 29,
"tradable": true, // executableYesCents !== null || executableNoCents !== null
"spreadCents": 1, // ask − bid. Large means the reference is a poor guide
"priceBasis": "midpoint", // midpoint | last | source | none
"priceUpdatedAt": "2026-08-20T04:43:08.322Z",
"priceFeed": "websocket", // websocket | rest
"status": "open",
"threshold": null, // parsed strike, for ordering a ladder
"volume24h": 415244.02,
"closesAt": "2026-09-16T00:00:00.000Z"
}The display rule
yesCents is the probability. executableYesCents is the price. A buy takes the ask and a sell takes the bid; buying NO costs 1 − bid, which is why both executable fields are published rather than one.
A null executable price means the book publishes no usable side: show “no price” and disable the control. Never fall back to the reference. Falling back is the exact bug these two fields exist to prevent — the quote endpoint would refuse the trade, and the player would already have seen a number.
- Filter a “what can I offer” list on
tradable, never onyesCents !== null. The second is true on almost every contract and is the reference, not a price. - A crossed book — ask at or below bid — is bad data that looks like a gift, so both sides are discarded and the outcome reads as untradable rather than cheap.
- Prices of exactly 0 and exactly 1 are settled, not tradeable, and are never published as a price.
Contract ids
A contract is addressed only by its opaque ctr_… id. It is the id /api/v1/events publishes, the id you post to /api/v1/quotes, and the id you read back on an order, a position and a settlement. One id space, end to end, and no second identifier to store or to get wrong.
- It names no venue and carries no upstream identifier. The mapping is one-way and derived, so nothing about Predicta's sourcing crosses the boundary.
- An event id (
evt_…) and a contract id are different things. You browse by event and you trade by contract. imageUrlis a Predicta path,/api/v1/images/{eventId}, and the bytes are proxied rather than redirected. Prefix it with your issued host. The SDK haspredicta.imageUrl(event).
One event, every outcome
The list caps outcomes per event for card-sized payloads. The detail route returns the whole set, which is what rendering a ladder or a nine-candidate field needs. It accepts either the evt_… id or the slug — the same identity wearing different clothes, and forcing a caller to know which one they are holding buys nothing.
curl -s "$PREDICTA_BASE/api/v1/events/fed-decision-september" \
-H "Authorization: Bearer $PREDICTA_KEY"
# → { "data": { …, "outcomes": [ … ] } }An event your operator is not offered reads as 404 unknown_event, not as a separate refusal: this route answers for things in your catalogue, and your catalogue does not contain it.
Categories
{
"data": [
{ "category": "politics", "label": "Politics", "eventCount": 412 },
{ "category": "economy", "label": "Economy", "eventCount": 188 }
],
"totalEvents": 600
}- Counted over events, under your own offering policy, by the same filter the list endpoint applies — so a tab that says 188 returns 188. The public
/api/categoriescounts contracts, which is the right number for the feed it serves and the wrong number next to an event list. - Only categories with something in them are returned. An integrator building a nav should not discover which of sixteen tabs are empty by opening all sixteen.
- Ordered canonically rather than by count, so a nav does not reshuffle itself every time volume moves.
- Sports markets are excluded from the catalogue entirely and are never persisted.
Language
Titles and outcome labels are returned in the provider's own translation when one was published, and in the original language otherwise. Predicta does not machine-translate a question and present it as the published wording.

