> ## Documentation index
> Fetch the complete documentation index at: https://docs.predictefy.com/llms.txt
> Use it to discover every available page before exploring further.

# Sports odds screen

> Cross-venue sports price comparison, live since 2026-09-29, cell definitions and freshness.

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

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

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](/guides/sdk/), [Python](/guides/python-sdk/), [MCP](/guides/mcp/) and
[CLI](/guides/cli/) guides for client setup.

## 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

`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

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

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

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](/guides/cross-venue/); 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)

`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

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

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

`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

Dark calls are not charged. Once enabled, sports reads are metered. See
[Pricing, credits & billing](/guides/credits/) for the current action costs and billing
rules.
