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

GET/api/v1/eventsAPI key
The catalogue, cursor-paged and narrowed to what your operator is offered. markets:read.
GET/api/v1/events/{id}API key
One event with every outcome. {id} is the event id or its slug. markets:read.
GET/api/v1/categoriesAPI key
The values ?category= accepts, with a live event count for each. markets:read.
GET/api/v1/images/{id}No auth
Event artwork, served from Predicta's origin. {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

ObjectWhat it isExample
EventOne question, grouped across sources. What a player browses.“Fed decision in September?”
OutcomeOne binary contract belonging to that event, addressed by a ctr_ id. What is traded.“No change”
SideYES 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

kindMeaningHow to render it
binaryOne outcome.YES / NO on the question itself.
mutually_exclusiveA field of answers, exactly one of which is true.The leaders, with a “+N more”. Probabilities across the field are comparable.
cumulative_ladderNested 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.
ambiguousThe shape could not be established.Present the leaders, never as a complete picture.

Listing events

ParameterTypeNotes
categorystringOne canonical category. An unknown value is 400 invalid_category, not a silently unfiltered catalogue. Sports is never returned.
searchstringFree text over the question.
sortstringtrending (default), volume, closing-soon, new
dirstringasc or desc.
limitintegerDefault 25, 1–100.
tradablebooleantrue restricts to events with at least one executable side.
closesAfter / closesBeforeISO-8601Window on the close time.
cursorstringOpaque. Echo nextCursor from the previous page.
shell
curl -s "$PREDICTA_BASE/api/v1/events?category=economy&sort=volume&limit=10" \
  -H "Authorization: Bearer $PREDICTA_KEY"
200 application/json (abridged)
{
  "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=volume token against sort=new is 400 invalid_cursor rather 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.total is the size of the whole filtered set, not what remains after the cursor.
typescript
// The SDK hides the cursor loop entirely.
for await (const event of predicta.iterateEvents({ category: 'economy', tradable: true })) {
  upsert(event);
}

The outcome object

json
{
  "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 on yesCents !== 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.
  • imageUrl is a Predicta path, /api/v1/images/{eventId}, and the bytes are proxied rather than redirected. Prefix it with your issued host. The SDK has predicta.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.

shell
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

200 application/json
{
  "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/categories counts 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.