OverviewCore concepts
Sports odds screen
The sports screen is a price comparison grid: game → bet type → period → line → selection → one price per venue. It groups quotes for the same selection and exact line. A venue without that line has an empty cell; it never supplies its nearest line instead.
Screen and catalog status
Section titled “Screen and catalog status”The screen and catalog routes in the table below are live and metered. Fixture price
history remains dark by default, as described below. A deployment can still have
the screen and catalog routes switched off; their calls then return 404 ROUTE_NOT_FOUND
and a dark call is not charged. The screen has a
second switch: the other sports routes can answer while /v1/sports/screen still returns
ROUTE_NOT_FOUND.
Check error.code, not just HTTP status. SPORTS_FIXTURE_NOT_FOUND means the fixture is
absent from the current mirror. SPORTS_BLOCK_NOT_FOUND means that fixture has no block
for the requested bet type, period or team slot. Neither means the surface is dark.
503 SPORTS_SCREEN_UNAVAILABLE is retryable when the mirror is loading or the fixture
changed during the read.
Clients never retry a sports 404, replace it with an empty list, probe availability at
startup or cache a dark verdict. TypeScript and Python preserve the typed NotFoundError
and its code. MCP and the CLI’s human output add a deployment hint; CLI --json preserves
the raw error envelope. A subsequent call can succeed once the operator enables the route.
Operations and clients
Section titled “Operations and clients”All routes below use GET. Use fixture ids returned by the API for {fixtureId}.
| REST route | TypeScript SDK | Python SDK | MCP | CLI |
|---|---|---|---|---|
/v1/sports/facets | fetchSportsFacets | fetch_sports_facets | get_sports_facets with kind=cascade | predictefy sports facets |
/v1/sports/screen | fetchSportsScreen | fetch_sports_screen | get_sports_screen | predictefy sports screen |
/v1/sports/competitions | fetchSportsCompetitions | fetch_sports_competitions | get_sports_facets with kind=competitions | predictefy sports competitions |
/v1/sports/teams | fetchSportsTeams | fetch_sports_teams | get_sports_facets with kind=teams | predictefy sports teams [query] |
/v1/sports/fixtures | fetchSportsFixtures | fetch_sports_fixtures | get_fixtures | predictefy sports fixtures |
/v1/sports/fixtures/{fixtureId} | fetchSportsFixture | fetch_sports_fixture | get_fixture | predictefy sports fixture <fixtureId> |
/v1/sports/fixtures/{fixtureId}/compare | compareFixturePrices | compare_fixture_prices | compare_fixture_prices | predictefy sports compare <fixtureId> |
Start with competitions, then request facets for a competition to discover bet types,
periods and lines. Facets include inactive and non-screenable types. MCP’s cascade requires
competition; use kind=competitions to find an id first. Facets accept sport and
competition, with no from or to window. Team search uses q in REST and the SDKs,
query in MCP, and the CLI’s positional query.
The screen supports moneyline, match_result, spread, total, team_total, btts
and halftime_result. Select a marketType, period and, for team_total, a lineTeam
of team1 or team2. The screen’s line selects an exact line. Compare returns all
available lines in ascending order; its clients expose no line filter.
Fixture listings return identity and block availability in markets, with market: null.
Fixture detail returns identity and full market blocks. Compare returns market blocks;
the screen returns fixture rows with the selected market or market: null.
TypeScript returns data with .meta attached, including on arrays. Python returns lists
as PageList with .meta; facets and fixture detail keep the full response envelope.
Screen and fixture lists also carry paging metadata. Read the
TypeScript, Python, MCP and
CLI guides for client setup.
How to read a cell
Section titled “How to read a cell”Each cell identifies its venue, marketId, outcomeId and selection. Prices carry
probability, american and decimal representations computed by the server.
| Field | Meaning |
|---|---|
gross | Ask price before fees, or null. grossReason explains a missing price: no_asks, book_unavailable, synthetic_book or market_not_open. |
net | Price including the venue’s known fees, or null when it cannot be supplied. |
feeUnknown | When true, net is always null. Never show a net price in this case. A displayed gross price must be labelled as gross with unknown fees. |
feeBps | Fee adjustment in basis points, or null when unknown. |
sizeAtPrice | Available size at the quoted price, or null when unavailable. It does not promise depth for a larger order. |
asOf | The source quote clock, or null. It is not a fresh confirmation just because the response arrived now. |
stale | Whether the quote is stale under the fixture’s freshness tier. A stale cell cannot win best. |
source | live or catalog. A catalog cell cannot win best. |
An empty cell is absent, never zero. Preserve missing prices as null or an absent
display value. Do not turn them into a zero probability, zero odds or zero available size.
Use the returned prices and calculations; clients do not convert odds or compute their
own replacement calculations.
Predictefy’s own definitions
Section titled “Predictefy’s own definitions”best, avg, fair, edge and hold below are Predictefy’s own definitions.
They describe this price comparison and do not guarantee execution or a return.
| Field | Predictefy’s own definition |
|---|---|
best | The lowest eligible live, non-stale ask probability including known fees. Catalog and stale cells never win. An unknown-fee cell ranks on gross, keeps net: null and sets edgeOnGross: true. This does not promise a verified all-in execution cost. |
avg | Weighted mean of gross implied probabilities across eligible venues. By default, all live, non-stale venues have equal weight, including the best venue. Vig is not removed. avgVenues selects the averaging venues; avgWeights supplies relative integer weights from 0 to 10, with 0 excluding a venue. |
fair | Predictefy’s own no-vig probability: normalize the averages of every selection in the block to sum to 1. If any selection lacks an average, all fair prices in the block are null. |
edge | Predictefy’s own ratio: fair probability divided by the best comparable probability, minus 1. It may be negative. It is null when fair or best is unavailable. The denominator uses net when known, or gross with edgeOnGross: true when fees are unknown. |
hold | Predictefy’s own sum of selection probabilities minus 1. average uses the selection averages; byVenue uses each venue’s eligible gross prices. An incomplete selection set has no computed hold: the average is null or the venue entry is absent. |
These definitions are computed by the server. Preserve edgeOnGross when displaying
Predictefy’s own edge so an unknown-fee comparison cannot look fee-adjusted.
Freshness and degraded responses
Section titled “Freshness and degraded responses”Use meta.freshness on screen, fixture-list, fixture-detail and compare responses. Its
thresholds are milliseconds:
| Tier | Threshold | Applied when |
|---|---|---|
live | 30 seconds (30000) | Exact kickoff has passed or is within two hours. |
day | 2 minutes (120000) | Exact kickoff is within 24 hours, outside the live tier. |
week | 20 minutes (1200000) | Exact kickoff is further away. |
A date-only fixture uses the week tier before its event date, then the day tier. Inspect
each cell’s asOf and stale alongside these thresholds.
meta.degraded: true means the response is degraded and some cells may come from an older
snapshot. Show that state and meta.degradedReason; do not imply every cell is fresh.
A screen row with degradedReason: 'snapshot_unavailable' has an unavailable block,
which remains market: null. meta.unplaced records reasons some selections could not
be placed in the grid. Missing facets can return sports: [] with facets_unavailable
in metadata; that is different from a dark-route error.
Paging and filters
Section titled “Paging and filters”Screen and fixture lists use keyset pagination. Pass the returned nextCursor as cursor
on the next call, retaining the same filters. The wire sends nextCursor: null at the end.
TypeScript omits the top-level cursor at the end while preserving page.nextCursor: null;
Python exposes .next_cursor as None. Stop when the cursor is null or absent.
The default window runs from 12 hours before now to 7 days after now. from and to
can narrow it; the maximum span is 31 days. Use YYYY-MM-DD or an ISO timestamp with
seconds and a timezone offset. Python calls the lower bound from_ and accepts a date
or timezone-aware datetime too. Epoch numbers and offset-less timestamps are refused.
REST pages default to 50 rows and accept limit from 1 to 200. Each venue list is capped
at 12 venues. Use venues for screen and compare display columns, avgVenues and
avgWeights for averaging. Fixture lists instead take a single venue; in the CLI this
is the global --venue option. Screen and compare use --venues.
MCP defaults to 5 screen rows, capped at 25, and 20 fixture rows, capped at 100. If its
payload is truncated, lower limit or request fewer venues. A cursorWithheld note means
the payload budget dropped rows and the cursor was withheld to avoid skipping them.
Repeat with a smaller request; do not treat that payload as a complete page.
Fixture identity and status
Section titled “Fixture identity and status”Catalog identity is Predictefy’s grouping of venue markets. home, away and kickoffAt
are null when no source states them. Do not infer home/away from team order or invent
a kickoff time from eventDate; kickoffPrecision: 'date' preserves that distinction.
final means our keyed markets closed. A fixture listing is not a settlement claim.
It does not independently verify an official outcome. postponed and cancelled are
reported only when a source states them.
The sports screen is a price comparison. The executable-gated arbitrage finder is a different route, described in Cross-venue data; a screen row does not establish that route’s live-ask, open-market, depth, fee/gas and resolution-equivalence checks.
Fixture price history (dark by default)
Section titled “Fixture price history (dark by default)”fetchFixturePriceHistory (GET /v1/sports/fixtures/{fixtureId}/history) returns
recorded price series for each venue, selection and line, with opening and closing
marks and main-line movement. It remains dark by default behind its own switch,
READS_ENABLE_SPORTS_HISTORY, inside the sports family. Public access requires both
that switch and READS_ENABLE_SPORTS to be the literal true, with sports Redis and
the History database configured. A disabled history switch leaves the route unmounted
with 404 ROUTE_NOT_FOUND. Enabling the family alone does not enable history.
History query
Section titled “History query”Use a fixture id returned by the API. All query parameters are optional:
| Parameter | Meaning and default |
|---|---|
marketType | moneyline by default; also accepts match_result, spread, total, team_total, btts and halftime_result. |
period | Canonical period, default full. |
lineTeam | Only allowed for team_total: team1 by default or team2. team1 is home when both home and away are stated, otherwise teamA. Spread lines always address team1 without this parameter. |
line | Finite numeric line filtering retained samples. Omission selects the latest recorded main line; line-less markets retain null. |
venues | Comma-separated venues, with at most 12 distinct venues after normalization. Omission uses all available venues. |
from | Defaults to the resolved to minus 7 days. Must be strictly before to. |
to | Defaults to the earlier of now and exact kickoff plus 12 hours; defaults to now when kickoff is not exact. Explicit future values are clamped to now. |
interval | 1m, 5m, 15m, 1h, 6h or 1d. Omission picks the finest supported interval that fits the 1,000-point cap: ceil((to - from) / interval) <= 1000. |
from and to accept ISO-8601 instants with seconds and a timezone, or YYYY-MM-DD
as UTC midnight. The resolved window must span at most 31 days. An explicit
interval too fine for that window returns 400 VALIDATION_ERROR. Each bucket retains
its last recorded state; an earlier retained sample may anchor the first point at from.
The future-to clamp prevents manufactured future points. Read the resolved from,
to and interval from meta.
line and venues filter sample-backed data. Permanent opening and closing marks
can still contribute series for other lines and venues. data.lines lists retained
sample lines, so a line represented only by marks may be absent from that list.
Reading recorded prices
Section titled “Reading recorded prices”These are best-ask observations, with meta.priceBasis: best_ask. The read uses
stored history and makes no live venue calls. Samples are retained for 90 days;
opening and closing marks are kept forever once taken. The sample read horizon
starts at the resolved to minus 32 days. Sample-backed fields such as lines,
the series[].open fallback, current and movement.changes only consider samples
newer than that horizon, within retained history.
Each series entry has open, close, current and time-ordered points. open
uses the permanent opening mark, falling back to the earliest priced sample within
the read horizon. close is the recorded kickoff mark. current is the latest
retained state and can fall outside the requested window. movement describes
recorded main-line changes, including its opening, current and closing line.
The closing block is the recorded state at kickoff. Closing marks are taken
after an exact kickoff with a default delay of 120 seconds. This is neither a
current quote nor an official result. A missing block stays null; closingUnavailable
explains it with kickoff_not_exact, before_kickoff or no_samples.
For each point, at is the recorded point time (or from for an anchor); asOf
is source freshness. gross is the recorded best ask or null with a reason.
net is non-null only when feeUnknown is false and a net price is available.
Unknown fees keep net: null. Preserve stale and source when displaying prices.
History health and errors
Section titled “History health and errors”meta.degraded: true preserves retained prices while reporting a recorder-health
problem in meta.degradedReason:
| Reason | Meaning |
|---|---|
recorder_stale | The recorder heartbeat is missing or stale. |
recorder_not_writing | The recorder heartbeat is fresh, but the recorder is not writing prices. |
redis_unavailable | Redis could not be read to check recorder health. |
Healthy responses omit degradedReason. meta.truncated: true means the bucketed
sample result exceeded the repository row cap; do not treat it as complete. This
route has no pagination.
| HTTP status and code | Meaning |
|---|---|
400 VALIDATION_ERROR | Invalid fixture id, query, window or interval. |
404 SPORTS_FIXTURE_NOT_FOUND | The fixture could not be found in the mirror or Core identity fallback. |
404 ROUTE_NOT_FOUND | History is unmounted or the caller does not have access to the dark sports family. |
503 HISTORY_UNAVAILABLE | The History read failed, exceeded its budget, or hit the concurrent-read capacity limit. Database timeouts and budget exhaustion are not retryable; capacity overflow is retryable with Retry-After: 1. |
503 SPORTS_SCREEN_UNAVAILABLE | The cold fixture index could not load. |
503 CATALOG_UNAVAILABLE | The Core fixture-identity fallback timed out. |
503 PLATFORM_UNAVAILABLE | Authentication or metering storage is unavailable. |
When enabled, history uses the history weight, 5 credits today, through its
independently tunable sports_history weight key. Dark calls are not charged.
Credits
Section titled “Credits”Dark calls are not charged. Once enabled, sports reads are metered. See Pricing, credits & billing for the current action costs and billing rules.