> ## Documentation Index
> Fetch the complete documentation index at: https://docs.oddspapi.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Core Concepts - IDs, Data Models & Best Practices

> Understand OddsPapi data models: fixture IDs, market IDs, odds identifiers, entity relationships, timestamps, and implementation best practices for arbitrage detection.

This page explains the core concepts used throughout the Odds API: how IDs are constructed, how entities relate to each other, how timestamps are used, and how to design reliable client-side storage.

***

## Language prefix

All endpoints are prefixed with a language code:

* `/en/...`
* `/de/...`
* `/fr/...`

Translated fields (for example names) follow the prefix language when available.
Identifiers (`sportId`, `fixtureId`, etc.) are language-independent.

***

## Sports, tournaments, seasons

### sportId

* Integer.
* The **first two digits** embedded into several downstream IDs.
* Discover via `/sports`.

### tournamentId

* Integer.
* Belongs to exactly one sport.
* Discover via `/tournaments`.

### seasonId

* Integer.
* Belongs to exactly one tournament.
* Discover via `/seasons`.

> See the full [Sports Coverage](/coverage) table for every `sportId` and whether it's available as fixtures, futures, or both.

***

## Markets and outcomes

<Note>
  **TL;DR** — Every selection follows a fixed hierarchy: `marketType → period → handicap → side`, all baked into the `outcomeId` — so the same bet always resolves to the same `marketId` / `outcomeId`, addressed by coordinates rather than bookmaker name strings. The one part *not* in `outcomeId` is the player, carried by `playerId`. All outcomes of a market share its `marketId`, which equals the market's first `outcomeId`.
</Note>

### How markets decompose (the deterministic hierarchy)

Every `outcomeId` comes from a single, deterministic decomposition — OddsPapi splits each market into a fixed hierarchy:

```
marketType → period → handicap (line value / specifier) → side (outcome)   [+ optional player]
```

So "total goals over 2.5, full time" always lands on the same `marketId` / `outcomeId` across bookmakers and fixtures.

| Level | Field         | Meaning                                                                                                                                      |
| ----- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| 1     | `marketType`  | The kind of market (`1x2`, `totals`, `spreads`, `spreads-european`, `bothteamsscore`, `drawnobet`, `oddeven`, …).                            |
| 2     | `period`      | The segment the market applies to (`fulltime`, `p1`, `p2`).                                                                                  |
| 3     | `handicap`    | The line value / specifier (e.g. `2.5`, `-0.25`). **Each distinct line value is its own `marketId`.** `0.0` for lineless markets like `1x2`. |
| 4     | `outcomeName` | The **side** of the market (`Over` / `Under`, `1` / `X` / `2`, `Yes` / `No`, …).                                                             |

> **Key rule:** because every line value gets its **own** `marketId`, "Over 2.5" and "Over 3.5" are *different* markets — not two outcomes of one market. Within a single `marketId`, the outcomes are only the **sides** of that exact line (e.g. `Over 2.5` + `Under 2.5`).

<Accordion title="Worked example — soccer (sportId 10)">
  A representative slice of the real soccer market map. Each `marketId` equals its first `outcomeId`, and each line value is its own market:

  | marketId | marketName           | marketType         | period   | handicap | outcomeId | outcomeName |
  | -------: | -------------------- | ------------------ | -------- | -------: | --------: | ----------- |
  |      101 | Full Time Result     | `1x2`              | fulltime |      0.0 |       101 | 1           |
  |      101 | Full Time Result     | `1x2`              | fulltime |      0.0 |       102 | X           |
  |      101 | Full Time Result     | `1x2`              | fulltime |      0.0 |       103 | 2           |
  |      104 | Both Teams To Score  | `bothteamsscore`   | fulltime |      0.0 |       104 | Yes         |
  |      104 | Both Teams To Score  | `bothteamsscore`   | fulltime |      0.0 |       105 | No          |
  |      106 | Over Under Full Time | `totals`           | fulltime |      0.5 |       106 | Over        |
  |      106 | Over Under Full Time | `totals`           | fulltime |      0.5 |       107 | Under       |
  |     1010 | Over Under Full Time | `totals`           | fulltime |      2.5 |      1010 | Over        |
  |     1010 | Over Under Full Time | `totals`           | fulltime |      2.5 |      1011 | Under       |
  |     1068 | Asian Handicap       | `spreads`          | fulltime |     -0.5 |      1068 | 1           |
  |     1068 | Asian Handicap       | `spreads`          | fulltime |     -0.5 |      1069 | 2           |
  |    10122 | European Handicap    | `spreads-european` | fulltime |      6.0 |     10122 | 1           |
  |    10122 | European Handicap    | `spreads-european` | fulltime |      6.0 |     10123 | X           |
  |    10122 | European Handicap    | `spreads-european` | fulltime |      6.0 |     10124 | 2           |
  |    10208 | First Half Result    | `1x2`              | p1       |      0.0 |     10208 | 1           |
  |    10208 | First Half Result    | `1x2`              | p1       |      0.0 |     10209 | X           |
  |    10208 | First Half Result    | `1x2`              | p1       |      0.0 |     10210 | 2           |
  |    10214 | Draw No Bet          | `drawnobet`        | fulltime |      0.0 |     10214 | 1           |
  |    10214 | Draw No Bet          | `drawnobet`        | fulltime |      0.0 |     10215 | 2           |
  |    10222 | Odd Even Full Time   | `oddeven`          | fulltime |      0.0 |     10222 | Odd         |
  |    10222 | Odd Even Full Time   | `oddeven`          | fulltime |      0.0 |     10223 | Even        |
  |    10224 | Over Under Team 1    | `teamtotals-team1` | fulltime |      0.5 |     10224 | Over        |
  |    10224 | Over Under Team 1    | `teamtotals-team1` | fulltime |      0.5 |     10225 | Under       |

  Reading the first row: `marketType=1x2`, `period=fulltime`, `handicap=0.0`, `outcomeName=1` — the home side of the full-time match-result market.
</Accordion>

### Relationship between `marketId` and `outcomeId`

Markets and outcomes are tightly coupled by design:

* A **market** represents a complete betting market (for example moneyline, 1x2, totals).
* A **market contains multiple outcomes**.
* **All outcomes that belong to the same market share the same `marketId`.**
* **The first `outcomeId` of a market is always equal to the `marketId`.**

This makes it possible to determine which outcomes belong to which market **without additional metadata**.

### marketLength

`marketLength` is the **number of outcomes under a `marketId`** — the count of sides that can be bet on, which together form the complete probability space (their implied probabilities sum to ≈ 1 before margin removal). Examples: `moneyline` → 2, `1x2` → 3, `winningmargin` → the number of margin bands offered.

### ID structure

Both `marketId` and `outcomeId` are integers constructed as:

```
{sportId (2 digits)} + {incrementing number}
```

Examples:

* `11xxxx` → basketball market or outcome
* `14xxxx` → American football market or outcome

This design provides:

* Unlimited markets and outcomes per sport
* Fast grouping by sport
* Immediate visibility of market relationships

### Why `marketId` matters (arbitrage & modeling)

All outcomes under a single `marketId` together represent a **complete probability space**.

This makes `marketId` especially useful for:

* Arbitrage detection
* Overround / margin calculations
* Probability normalization
* Market completeness checks

**Recommendation:**
If you perform arbitrage or pricing logic, always group odds by `marketId`.

***

## Participants and players

### participantId

* Integer.
* Represents teams or competitors in fixtures.
* Discover via `/participants`.

### playerId

* Integer.
* Used for player proposition markets.
* `playerId = 0` typically represents a non-player market.
* Discover via `/players`.

***

## Fixture IDs

### fixtureId structure

`fixtureId` is a **string** that encodes multiple pieces of information:

```
{providerSlug}{sportId}{tournamentId}{providerFixtureId}
```

Conceptual example:

```
id1100013262926199
```

Where:

* `id` → provider identifier / short slug
* `11` → sportId (2 digits)
* `000132` → tournamentId (6 digits)
* `62926199` → provider’s native fixture ID

From the `fixtureId` alone you can infer the sport, tournament, and upstream provider — handy for logging and cross-system correlation.

***

## Future IDs

### futureId structure

`futureId` follows the same principle as `fixtureId`:

```
{providerSlug}{sportId}{tournamentId}{seasonId}{marketId}
```

Where:

* `providerSlug` → provider identifier / short slug (e.g. `id`, `pm`, `ks`)
* `sportId` → 2 digits
* `tournamentId` → 6 digits (zero-padded)
* `seasonId` → the season / event instance
* `marketId` → the outright market within that season (e.g. `1` = Winner)

This encodes provider, sport, tournament, season, and market — globally unique and self-describing, like `fixtureId`.

***

## Odds identifiers

<Note>
  **TL;DR** — A single price is uniquely keyed by `{fixtureId}:{bookmaker}:{outcomeId}:{playerId}` (fixtures) or `{futureId}:{bookmaker}:{participantId}` (futures). Use these `oddsId` strings as **primary keys** in your storage for clean dedup, updates, and reconciliation.
</Note>

### Fixture odds keys

For fixtures, a single price is uniquely identified by:

```
{fixtureId}:{bookmaker}:{outcomeId}:{playerId}
```

Example:

```
id1400003160574217:bet365:141:0
```

This combination uniquely defines **one price**.

Drop the `bookmaker` and you have the **selection** itself — `{fixtureId}:{outcomeId}:{playerId}` — the bookmaker-independent key for grading / settlement, since a selection wins or loses the same way everywhere. No `marketId` is needed; the `outcomeId` already encodes the market.

### Future odds IDs

For futures, odds are uniquely identified by:

```
{futureId}:{bookmaker}:{participantId}
```

***

## Timestamps (seconds vs milliseconds)

This API intentionally uses **both epoch seconds and epoch milliseconds**, depending on context. The rule is consistent across fixtures **and** futures:

### Epoch seconds (UTC)

All **start times** are integer epoch seconds:

* `startTime` / `endTime`
* `startTimeFrom`
* `startTimeTo`

### Epoch milliseconds (UTC)

All **odds-update times** are integer epoch milliseconds — for fixtures and futures alike:

* `changedAt` / `bookmakerChangedAt`
* Odds `since` filters (`/fixtures/odds`, `/fixtures/odds/main`, `/futures/odds`)
* WebSocket envelope `ts` and the timestamp part of `entryId`

### Recommendation

* Store timestamps as integers.
* Rule of thumb: **schedule times are seconds, odds-update times are milliseconds.**
* Normalize internally only if needed.

***

## Operating tips

### Server Location Matters

To achieve the lowest latency for realtime updates:

* The **fastest delivery regions** are Central Europe (recommended) and US East.
* For best performance, deploy your backend **in the same datacenters we use**, such as:
  * [Netcup](https://www.netcup.com/en/?ref=294017)
  * [Hetzner](https://www.hetzner.com/cloud)
* If you're building **prediction markets or models sensitive to latency**, consider:
  * Deploying in **Austria (AT)** via Netcup, or
  * Using **AWS eu-west-1** (Ireland)

> ⚡ Servers colocated near our streaming infrastructure receive updates faster and with fewer hops.

### Snapshot + realtime pattern

A reliable integration pattern:

<Steps>
  <Step title="Fetch HTTP snapshot">
    Pull the current state (fixtures, odds, or futures) over REST.
  </Step>

  <Step title="Subscribe to WebSocket">
    Stream realtime updates on top of the snapshot.
  </Step>

  <Step title="Re-snapshot on signal">
    If the WebSocket signals `snapshot_required`, re-fetch the HTTP snapshot and resume the realtime stream.
  </Step>
</Steps>

### Efficient backfills

* Use `since` parameters where available
* Avoid full historical fetches unless required
* Store odds keyed by their odds identifiers for deduplication

### Rate limits

See [Rate Limits](/api-reference/rate-limits) for headers, per-endpoint limits, and 429 backoff handling.

***

## Historical odds and CLV

The realtime WebSocket `odds` and `oddsFutures` channels deliver **latest state** — optimized for low-latency trading, they coalesce or drop intermediate updates under load and are not a tick ledger.

> Use the live stream to **trade**, and the REST history endpoints to **measure** — CLV models, fill auditing, and backtests.

When you need the **full price movement** of an outcome, or its **opening vs closing line**, use the REST history endpoints. They share the same `oddsId` format (`{fixtureId}:{bookmaker}:{outcomeId}:{playerId}`) as the live `odds` channel, so you can join realtime fills directly to their historical and closing-line records.

| Purpose                           | Fixtures                        | Futures                        |
| --------------------------------- | ------------------------------- | ------------------------------ |
| Full price timeline               | `GET /fixtures/odds/historical` | `GET /futures/odds/historical` |
| Opening vs closing line (OLV/CLV) | `GET /fixtures/odds/clv`        | `GET /futures/odds/clv`        |

**Trading use cases:**

* **CLV modelling** — compare your fill price against the closing line to measure edge.
* **Fill auditing** — reconcile traded prices with the recorded timeline to detect slippage.
* **Backtesting** — replay the full historical timeline to test strategies against real line movement.

See the **API → Reference** section for full request parameters, response schemas, and an interactive playground for each of these endpoints.

***

## Glossary

Quick definitions of the core terms used throughout the API.

**Identifiers & data model**

* **sportId** — Numeric sport identifier; the first two digits of downstream `marketId`, `outcomeId`, and `fixtureId`.
* **fixtureId** — String key for a single match/event, encoding provider, sport, tournament, and provider fixture ID.
* **futureId** — String key for a futures / outright market, encoding provider, sport, tournament, season, and market.
* **marketType** — The kind of market (`1x2`, `totals`, `spreads`, `bothteamsscore`, …).
* **period** — The segment a market applies to (`fulltime`, `p1`, `p2`).
* **handicap** — The line value / specifier of a market; each distinct line value is its own `marketId`.
* **marketId** — Integer grouping all outcomes of one market; equals the market's first `outcomeId`.
* **marketLength** — Number of outcomes under a `marketId`: the sides that can be bet on, together summing to a probability of ≈ 1.
* **outcomeId** — Integer identifying one fully-decomposed selection: `marketType` + `period` + `handicap` (line) + side. It encodes everything except the player — that's what `playerId` adds.
* **playerId** — Ties an outcome to a player for player markets; `playerId = 0` is a non-player market.
* **participantId** — Integer identifying a team or competitor in a fixture.
* **oddsId** — Composite price key. Fixtures: `{fixtureId}:{bookmaker}:{outcomeId}:{playerId}`. Futures: `{futureId}:{bookmaker}:{participantId}`.

**Prices & trading**

* **OLV** — Opening line value: the outcome's first recorded price.
* **CLV** — Closing line value: the outcome's last price before settlement; the reference for grading execution.
* **staleOdds** — Flag indicating a bookmaker's connectivity is degraded, so its odds may not be current.
* **settlement** — Post-event endpoints returning per-outcome results (won / lost), final scores, and margins.

**Streaming & delivery**

* **serverEpoch + entryId** — The per-channel cursor; a change in `serverEpoch` means the client must re-snapshot.
* **resume / replay** — Reconnect and replay missed messages within `resumeWindowMs` (default `60000`).
* **snapshot\_required** — Signal that the cursor is outside the resume window and a fresh REST snapshot is needed.
