Fixtures (browsing markets)

Before you can submit an order, you need a contractId. The three endpoints in this doc walk you down the fixtures hierarchy until you have one.

Auth: ISV-only JWT (see Authentication). These aren't user-scoped, so no sub or subsig needed.


1. The hierarchy

Tournament  (e.g. "NBA")   — also carries its sport in tournament.sport
   └─ Sport event  (e.g. "Detroit Pistons at Cleveland Cavaliers")
       └─ Market   (e.g. "Moneyline")
           └─ Selection (= outcome) — each has a contractId

A contract is the unique combination of (event, market, outcome, strike). Its contractId is the hash you'll pass to the order endpoints. See Market Orders (single-contract trades) and Parlays (2–12 leg trades).

The usual drill-down: pick a tournament, list its events, list the markets on an event, pick a selection, grab the contractId.

A few mapping notes if you're coming from another platform's vocabulary:

  • There is no GET /sports endpoint. Tournaments are the top of the API hierarchy. Sport is an attribute on the tournament (tournament.sport: { id, name }), not a separate resource. If you want a "browse by sport" view, build the sport list client-side by deduplicating tournament.sport.id across the /tournaments response.
  • Mapping to a Plaee-style schema: ProphetX tournament.sport ≈ Plaee's category; ProphetX tournament ≈ Plaee's sub-category. ProphetX sport event ≈ Plaee's event. Markets and outcomes line up directly.
  • Each outcome is its own contract. For binary markets, the "yes" side and the "no" side have different contractIds. You pick which side the user is taking when you submit the order; there's no side flag on the order body.

2. GET /private/v1/tournaments

QueryDefaultEffect
hasActiveEventsunsetSet to true to filter to tournaments that have at least one currently-active event.

Response (TournamentsResponse): the response is { "tournaments": [...] }. A trimmed staging example showing the first three entries from a hasActiveEvents=true query (the full list returned ten):

{
  "tournaments": [
    {
      "id": 31,
      "name": "NFL",
      "sport": { "id": 16, "name": "American Football" },
      "category": { "id": 0, "name": "N/A", "countryCode": "N/A" },
      "updatedAt": "2024-07-12T02:00:27.859Z"
    },
    {
      "id": 53,
      "name": "FIFA World Cup",
      "sport": { "id": 1, "name": "Soccer" },
      "category": { "id": 0, "name": "N/A", "countryCode": "N/A" },
      "updatedAt": "2026-05-11T15:15:19.506Z"
    },
    {
      "id": 109,
      "name": "MLB",
      "sport": { "id": 2, "name": "Basketball" },
      "category": { "id": 0, "name": "N/A", "countryCode": "N/A" },
      "updatedAt": "2024-07-12T02:00:27.296Z"
    }
  ]
}

Staging frequently returns category as the N/A placeholder; production data is more populated. Don't rely on category for routing — use sport.name and name.


3. GET /private/v1/sport-events

QueryRequiredEffect
tournamentIdone of theseFilter to a single tournament.
eventIdsone of theseComma-separated list of event IDs.

You typically call this after picking a tournament. The full response is { "sportEvents": [...] }. Per-event shape (SportEvent) — a real staging row for an NBA matchup from ?tournamentId=132:

{
  "eventId": 20023797,
  "name": "Detroit Pistons at Cleveland Cavaliers",
  "displayName": "Detroit Pistons at Cleveland Cavaliers",
  "liveDisabled": false,
  "status": "not_started",
  "type": "",
  "subType": "",
  "scheduled": "2026-05-15T23:00:00Z",
  "updatedAt": "2026-05-14T03:29:13.952Z",
  "sportName": "Basketball",
  "tournamentId": 132,
  "tournamentName": "NBA",
  "competitors": [
    { "id": 20000012, "name": "Cleveland Cavaliers", "displayName": "Cleveland Cavaliers", "abbreviation": "CLE", "country": "", "side": "home" },
    { "id": 20000014, "name": "Detroit Pistons", "displayName": "Detroit Pistons", "abbreviation": "DET", "country": "", "side": "away" }
  ]
}

Things to notice from real data: status is one of not_started, live, etc. (not scheduled); type and subType are often empty strings rather than null; country may be empty.


4. GET /private/v1/markets

QueryRequiredEffect
eventIdsyesComma-separated event IDs.
typesnoComma-separated market types, e.g. moneyline,total.
subTypesnoComma-separated market sub-types.

Response (MarketsResponse): the response is { "markets": [{ "eventId": ..., "markets": [...] }, ...] } — one entry per event you queried, each carrying its own markets array. Below is a real moneyline market returned from a live ATP Monte Carlo singles match, showing the typical shape of a market with partial liquidity (one side has a resting price, the other side is empty):

{
  "id": 186,
  "name": "Moneyline",
  "type": "moneyline",
  "subType": "moneyline",
  "status": "active",
  "favorite": false,
  "categoryName": "Game Lines",
  "groupName": "",
  "strike": 0,
  "totalQuantity": 10,
  "totalFilled": 0,
  "playerId": 0,
  "updatedAt": "2026-04-03T19:32:34.503Z",
  "selections": [
    [
      {
        "outcomeId": 4,
        "contractId": "2d179d1578e669e97a2e57735f0cbef8",
        "name": "Alexander Shevchenko",
        "displayName": "Alexander Shevchenko",
        "displayPrice": "",
        "displayStrike": "",
        "price": 0,
        "quantity": 0,
        "strike": 0,
        "marketId": 0,
        "competitorId": 1700000826,
        "value": 0,
        "updatedAt": "1970-01-01T00:00:00Z"
      }
    ],
    [
      {
        "outcomeId": 5,
        "contractId": "05d1b7df6b4a41016d545c3944e754e0",
        "name": "Andrea Pellegrino",
        "displayName": "Andrea Pellegrino +100",
        "displayPrice": "+100",
        "displayStrike": "",
        "price": 100,
        "quantity": 10,
        "strike": 0,
        "marketId": 186,
        "competitorId": 1700000749,
        "value": 10,
        "updatedAt": "2026-04-29T07:39:08.945Z"
      }
    ]
  ]
}

Fields worth knowing:

  • selections is a list of lists — each inner array holds the variants for one outcome. Most simple markets give you one entry per outcome, but you should iterate the outer list and then the inner list rather than assuming a flat array.
  • contractId is the thing you trade — pass it to /market-orders or /parlays.
  • displayPrice and displayStrike are strings (e.g. "+100", "-110", or "" when no price exists). price is a number for the same value.
  • price is raw American price; the response also carries an adjustedPrice field after the ISV fee on production-priced markets. Show the adjusted one to your user where present.
  • quantity is current liquidity at the displayed price. 0 means no resting orders on that side yet.
  • strike is the handicap/spread value (0 for moneyline).
  • An unpriced selection shows displayPrice: "", price: 0, quantity: 0, marketId: 0, and updatedAt: "1970-01-01T00:00:00Z" — i.e. the contract exists but has no resting liquidity. Don't show 0 as -0/+0 prices to your user; treat the empty displayPrice as "no price yet".
  • Pre-game events (e.g. an NBA game scheduled for tomorrow) often return the full markets catalog with every selection in this unpriced state. As the event approaches start time, sides get populated.

For markets composed of sub-markets, expect a sibling marketLines array on the parent market carrying nested Market objects with the same shape.


5. A typical drill-down

BASE="https://isv-api.staging.prophetx.dev/private/v1"

# 1. Find live tournaments (staging returned ~10 with hasActiveEvents=true)
curl "$BASE/tournaments?hasActiveEvents=true" -H "Authorization: Bearer $JWT_ISV"

# 2. Enumerate events in one of them — e.g. NBA (tournamentId=132)
curl "$BASE/sport-events?tournamentId=132" -H "Authorization: Bearer $JWT_ISV"

# 3. Pull the markets for an event — e.g. Detroit Pistons @ Cleveland Cavaliers
curl "$BASE/markets?eventIds=20023797&types=moneyline,total" \
  -H "Authorization: Bearer $JWT_ISV"
# Pick a selections[].contractId — now you're ready to submit an order.

6. Pagination

GET /tournaments, GET /sport-events, and GET /markets do not paginate — each call returns the full filtered set in a single response. Filter aggressively (hasActiveEvents=true on tournaments, tournamentId / eventIds on the others, types / subTypes on markets) to keep payloads bounded.


7. Keeping data fresh

These endpoints return snapshots. Don't poll them in a tight loop — the recommended pattern is:

  • Hydrate from the fixtures API at service start, and resync every 5–10 minutes as a safety net.
  • Apply real-time changes from the webhooks: SportEventEvent, TournamentEvent, MarketEvent, and MarketSelectionEvent (the liquidity feed — the orderbook, in other words). See Webhooks (push events).

Use the fixtures API for discovery and the periodic backstop, and the webhook for live updates. ProphetX prefers ISVs lean on webhooks instead of polling.


8. Sandbox vs production

The staging environment has trading bots posting liquidity, so you can exercise the full order/parlay lifecycle without needing real users on both sides of the book. The catalog itself (sports, tournaments, market types) is similar to production — fixtures you can browse and trade against in sandbox will look very close to what you'll see live.