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

# Efficient Polling

> Optimize data-point usage with delta endpoints, tight filters, smart polling intervals, and caching strategies.

Polling the full events endpoint every few seconds is expensive, burns data points, and can trip your per-second throttle if you scale it across many sports or books. This guide covers how to keep data fresh without paying for or requesting far more than you need.

## Why Polling Efficiency Matters

TheRundown usage is not just about request count. The cost of a polling loop depends on:

* how many sportsbooks you request
* how many markets you include
* whether you pull full event payloads or only deltas
* how often you refresh

A naive loop that fetches full event snapshots every few seconds can waste both data points and burst budget. Delta endpoints solve this by returning **only what changed** since your last request.

## Shrink Every Response First

Before you tune polling intervals, make each response smaller.

```bash theme={null}
curl -H "X-TheRundown-Key: YOUR_API_KEY" \
  "https://therundown.io/api/v2/sports/4/events/2026-02-26?market_ids=1,2,3&affiliate_ids=19,23&main_line=true&offset=300"
```

Use these levers aggressively:

* `market_ids`: biggest control for response size
* `affiliate_ids`: only request the books you actually surface
* `main_line=true`: skip alternate lines if your product only shows the primary market
* event-specific endpoints: if you only care about a handful of games, do not fetch the full slate
* `hide_closed_markets=1`: useful during market discovery to avoid off-board clutter

## Delta Endpoint Bootstrap Flow

The pattern for using delta endpoints has three stages:

### 1. Fetch the full snapshot

Start by loading the complete event data for the sport and date you need. This gives you the initial state and the first delta cursor.

```bash theme={null}
curl "https://therundown.io/api/v2/sports/4/events/2026-02-26?key=YOUR_API_KEY&market_ids=1,2,3&affiliate_ids=19,23&main_line=true&offset=300"
```

The response includes `meta.delta_last_id` — save this value. It is an **integer** cursor, and it is the bootstrap cursor for the **`/api/v2/markets/delta`** (price-change) feed.

### 2. Poll the market delta endpoint

On each subsequent poll, pass your saved cursor to `/api/v2/markets/delta`. It returns only the prices that changed since that cursor — the most efficient way to keep odds fresh.

```bash theme={null}
curl "https://therundown.io/api/v2/markets/delta?key=YOUR_API_KEY&last_id=PREVIOUS_DELTA_LAST_ID&sport_id=4&market_ids=1,2,3"
```

Each response includes its own `meta.delta_last_id` (also an integer) — use that as `last_id` on the next poll. Do **not** bootstrap with `last_id=0`: a cursor that has fallen too far behind the current head is rejected, so always seed from a fresh events snapshot.

<Warning>
  The two delta endpoints take **different, non-interchangeable cursors**:

  * **`/api/v2/markets/delta`** (price changes) uses an **integer** cursor. Bootstrap it from the events snapshot's integer `meta.delta_last_id`, then follow the integer cursor in each markets-delta response. This is the endpoint for odds polling.
  * **`/api/v2/delta`** (full event-object changes: status, scores, the whole event) uses an **ordered UUID** cursor, e.g. `11f1-23b3-f4d42784-8057-a3a997572248`. That cursor is only returned by `/api/v2/delta`'s own responses — the events snapshot does **not** provide it.

  Passing the integer events cursor to `/api/v2/delta` returns a `400`. For price and odds polling, use `/api/v2/markets/delta`.
</Warning>

### 3. Merge updates into local state

Each delta response contains the full updated object — replace (do not partially merge) the corresponding entry in your local cache. Update your cursor to the new `delta_last_id` from the response.

## Choosing Between Event and Market Delta

| Endpoint                    | Returns                                      | Cursor type  | Bootstrap source                                 | Best for                                                |
| --------------------------- | -------------------------------------------- | ------------ | ------------------------------------------------ | ------------------------------------------------------- |
| `GET /api/v2/markets/delta` | Individual price changes only                | Integer      | Events snapshot `meta.delta_last_id`             | Odds-focused apps (recommended for price polling)       |
| `GET /api/v2/delta`         | Full event objects (scores, status, markets) | Ordered UUID | The endpoint's own response `meta.delta_last_id` | Apps that need full event-object changes alongside odds |

The market delta is usually the cheapest option because it returns only the specific prices that changed, rather than the entire event object. The cursors are not interchangeable — the integer from the events snapshot bootstraps `markets/delta` only, and `/api/v2/delta` rejects it with a `400`.

## Recommended Polling Intervals

Match your polling frequency to your use case. Faster polling uses more data points and increases the chance of hitting your burst limit if you parallelize heavily.

| Use Case                  | Interval  | Endpoint        | Notes                                      |
| ------------------------- | --------- | --------------- | ------------------------------------------ |
| Live odds screen          | 5–10s     | Market delta    | Fastest practical interval for REST        |
| Pre-match odds monitoring | 30–60s    | Market delta    | Lines move slowly before game time         |
| Live scores               | 15–30s    | Event delta     | Score updates come in bursts during play   |
| Pre-match schedules       | 5 min     | Full events     | Schedules rarely change close to game time |
| Historical/closing lines  | On demand | Openers/closing | Fetch once after the event starts or ends  |

<Info>
  For sub-second updates, use the [WebSocket endpoint](/api-reference/v2/websocket) on a WebSocket-enabled tier instead of polling. WebSocket traffic does not increment the HTTP request counter, but pushed messages are still metered as data points.
</Info>

## Cache TTL Recommendations

Not all data changes at the same rate. Cache aggressively for reference data and use shorter TTLs for live data.

| Data Type          | Recommended TTL | Endpoint                                           |
| ------------------ | --------------- | -------------------------------------------------- |
| Sports list        | 24 hours        | `GET /api/v2/sports`                               |
| Affiliates list    | 24 hours        | `GET /api/v2/affiliates`                           |
| Teams              | 6 hours         | `GET /api/v2/sports/{id}/teams`                    |
| Market definitions | 6 hours         | `GET /api/v2/markets`                              |
| Events (pre-match) | 5 minutes       | `GET /api/v2/sports/{id}/events/{date}`            |
| Events (live)      | Use delta       | `GET /api/v2/delta` or `GET /api/v2/markets/delta` |
| Market prices      | Real-time       | Delta endpoint or WebSocket                        |

## Staleness Guards

Your delta cursor can become stale if you stop polling for an extended period. When this happens, the delta endpoint may return an error or skip events that changed while you were away.

**How to detect stale data:**

* Track the `updated_at` timestamp on your cached events. If the newest update is more than 5 minutes old during a live game window, your data may be stale.
* If a delta response returns an empty result but you know games are in progress, your cursor may have expired.
* If you receive an error response from the delta endpoint, re-bootstrap from a full snapshot.

**How to recover:**

1. Discard your current delta cursor
2. Fetch a fresh full snapshot from the events endpoint
3. Extract the new `delta_last_id` and resume polling

## Watch Your Usage Headers

Every production poller should log and monitor:

* `X-Datapoints`
* `X-Datapoints-Used`
* `X-Datapoints-Remaining`
* `X-Datapoints-Reset`
* `X-Rate-Limit`

That gives you the feedback loop to tune filters and polling intervals before users start hitting limits.

## Code Example: Python Polling Loop

This example fetches a full snapshot, then polls the market delta endpoint with automatic fallback to a full refresh when the cursor goes stale.

```python theme={null}
import requests
import time

API_KEY = "YOUR_API_KEY"
BASE = "https://therundown.io/api/v2"
SPORT_ID = 4
MARKET_IDS = "1,2,3"
AFFILIATE_IDS = "19,23"
POLL_INTERVAL = 5  # seconds

# Local cache: event_id -> event data
events = {}

def fetch_full_snapshot():
    """Load the full event list and return the delta cursor."""
    resp = requests.get(
        f"{BASE}/sports/{SPORT_ID}/events/2026-02-26",
        headers={"X-TheRundown-Key": API_KEY},
        params={
            "market_ids": MARKET_IDS,
            "affiliate_ids": AFFILIATE_IDS,
            "main_line": "true",
            "offset": "300",
        }
    )
    data = resp.json()

    for event in data.get("events", []):
        events[event["event_id"]] = event

    cursor = data.get("meta", {}).get("delta_last_id", "0")
    print(f"Loaded {len(events)} events, cursor={cursor}")
    return cursor

def poll_market_delta(last_id):
    """Fetch price changes since last_id. Returns new cursor."""
    resp = requests.get(
        f"{BASE}/markets/delta",
        headers={"X-TheRundown-Key": API_KEY},
        params={
            "last_id": last_id,
            "sport_id": SPORT_ID,
            "market_ids": MARKET_IDS,
        }
    )

    if resp.status_code != 200:
        print(f"Delta error {resp.status_code}, re-bootstrapping...")
        return None  # Signal to re-bootstrap

    data = resp.json()
    changes = data.get("deltas", [])

    for change in changes:
        eid = change.get("event_id")
        print(
            f"Price change: {eid} {change.get('market_name')} "
            f"{change.get('participant_name')} -> {change.get('price')}"
        )

    new_cursor = data.get("meta", {}).get("delta_last_id", last_id)
    return new_cursor

# Bootstrap
cursor = fetch_full_snapshot()

# Poll loop
while True:
    time.sleep(POLL_INTERVAL)
    result = poll_market_delta(cursor)

    if result is None:
        # Cursor went stale, re-bootstrap
        cursor = fetch_full_snapshot()
    else:
        cursor = result
```

## WebSocket vs. Polling Decision Guide

| Factor                    | REST Polling                                            | WebSocket                                                            |
| ------------------------- | ------------------------------------------------------- | -------------------------------------------------------------------- |
| Latency                   | 5–60s depending on interval                             | Sub-second                                                           |
| Usage impact              | Consumes data points and counts toward HTTP burst limit | Consumes data points but does not increment the HTTP request counter |
| Implementation complexity | Simple HTTP requests                                    | Requires connection management, reconnection logic                   |
| Data freshness            | As fresh as your poll interval                          | Real-time                                                            |
| Reliability               | Each request is independent                             | Must handle disconnects and reconnections                            |
| Best for                  | Pre-match monitoring, low-frequency updates             | Live odds screens, real-time dashboards                              |

<Note>
  Many production applications use **both**: WebSocket for live windows, with delta polling as a fallback when the socket disconnects or for lower-tier keys that do not have WebSocket access. See the [Building an Odds Screen](/guides/building-odds-screen) guide for this pattern in practice.
</Note>
