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 /sportsendpoint. 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 deduplicatingtournament.sport.idacross the/tournamentsresponse. - Mapping to a Plaee-style schema: ProphetX
tournament.sport≈ Plaee'scategory; ProphetXtournament≈ Plaee'ssub-category. ProphetXsport event≈ Plaee'sevent. 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 nosideflag on the order body.
2. GET /private/v1/tournaments
GET /private/v1/tournaments| Query | Default | Effect |
|---|---|---|
hasActiveEvents | unset | Set 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
categoryas theN/Aplaceholder; production data is more populated. Don't rely oncategoryfor routing — usesport.nameandname.
3. GET /private/v1/sport-events
GET /private/v1/sport-events| Query | Required | Effect |
|---|---|---|
tournamentId | one of these | Filter to a single tournament. |
eventIds | one of these | Comma-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
GET /private/v1/markets| Query | Required | Effect |
|---|---|---|
eventIds | yes | Comma-separated event IDs. |
types | no | Comma-separated market types, e.g. moneyline,total. |
subTypes | no | Comma-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:
selectionsis 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.contractIdis the thing you trade — pass it to/market-ordersor/parlays.displayPriceanddisplayStrikeare strings (e.g."+100","-110", or""when no price exists).priceis a number for the same value.priceis raw American price; the response also carries anadjustedPricefield after the ISV fee on production-priced markets. Show the adjusted one to your user where present.quantityis current liquidity at the displayed price.0means no resting orders on that side yet.strikeis the handicap/spread value (0for moneyline).- An unpriced selection shows
displayPrice: "",price: 0,quantity: 0,marketId: 0, andupdatedAt: "1970-01-01T00:00:00Z"— i.e. the contract exists but has no resting liquidity. Don't show0as-0/+0prices to your user; treat the emptydisplayPriceas "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, andMarketSelectionEvent(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.
