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

# Errors

> HTTP status codes, error response formats, and troubleshooting guidance for TheRundown API.

TheRundown API uses standard HTTP status codes to indicate whether a request succeeded or failed. This page covers every status code you may encounter, the structure of error responses, and how to handle them.

## HTTP Status Codes

| Status Code | Meaning               | Description                                                                         |
| ----------- | --------------------- | ----------------------------------------------------------------------------------- |
| `200`       | OK                    | The request was successful. The response body contains the requested data.          |
| `400`       | Bad Request           | The request contains invalid parameters or is malformed.                            |
| `401`       | Unauthorized          | Authentication failed. The API key is missing, invalid, or expired.                 |
| `404`       | Not Found             | The requested resource does not exist.                                              |
| `429`       | Too Many Requests     | You exceeded a burst throttle or a data-point cap. See [Rate Limits](/rate-limits). |
| `500`       | Internal Server Error | An unexpected error occurred on the server.                                         |

## Error Response Format

When an error occurs, the API returns a JSON object with a message describing the problem.

```json theme={null}
{
  "message": "A human-readable description of the problem."
}
```

## Detailed Status Code Reference

### 200 OK

The request succeeded. For list endpoints, the response is a JSON array. For single-resource endpoints, the response is a JSON object.

```json theme={null}
{
  "event_id": "abc123",
  "sport_id": 2,
  "teams": { ... },
  "score": { ... }
}
```

### 400 Bad Request

The request was rejected because one or more parameters are invalid. Check the `message` field for specifics.

**Common causes:**

* An invalid `sport_id` value
* A malformed date format (expected `YYYY-MM-DD`)
* An unrecognized query parameter value

```json theme={null}
{
  "message": "Invalid sport_id: 999. See /v2/sports for valid sport IDs."
}
```

**How to fix:** Review the request parameters against the API reference documentation. Ensure all required parameters are present and correctly formatted.

### 401 Unauthorized

Authentication failed. The API could not verify your identity.

**Common causes:**

* No API key was provided
* The API key is invalid or has been revoked
* The `X-TheRundown-Key` header is missing or malformed

```json theme={null}
{
  "message": "unauthorized"
}
```

**How to fix:** Verify that your API key is correct and included in the request. See the [Authentication](/authentication) guide for supported methods.

```bash theme={null}
# Verify your key is being sent correctly
curl -v -H "X-TheRundown-Key: YOUR_API_KEY" \
  "https://therundown.io/api/v2/sports/2/events/2026-02-12"
```

### 404 Not Found

The requested resource does not exist. This typically means the event ID, sport ID, or other identifier in the URL path does not match any record.

**Common causes:**

* An event ID that does not exist or has been archived
* A URL path that is misspelled or references a deprecated endpoint

```json theme={null}
{
  "message": "Event not found: abc123"
}
```

**How to fix:** Confirm the resource identifier is correct. Use the appropriate list endpoint to discover valid IDs before requesting a specific resource.

### 429 Too Many Requests

TheRundown uses `429` for more than one condition. Read the response body and headers before deciding whether to retry immediately.

```json theme={null}
{
  "error": "Rate limit exceeded",
  "limit": 2
}
```

You may also see:

```json theme={null}
{
  "error": "Daily data point limit reached",
  "limit": 20000,
  "used": 20000
}
```

or:

```json theme={null}
{
  "error": "Monthly data point limit reached",
  "limit": 10000000,
  "used": 10000000,
  "period": "monthly"
}
```

**How to fix:**

* Read `Retry-After` first.
* Inspect `X-Datapoints-Used`, `X-Datapoints-Remaining`, `X-Datapoints-Reset`, `X-Tier`, and `X-Rate-Limit`.
* If the body says `Rate limit exceeded`, retry after a short backoff.
* If the body says `Daily data point limit reached` or `Monthly data point limit reached`, this is a usage-window issue, not a one-second throttle. Retrying immediately will not help.

### 500 Internal Server Error

An unexpected error occurred on the server side. This is not caused by your request.

```json theme={null}
{
  "message": "An unexpected error occurred. Please try again later."
}
```

**How to fix:** Retry the request after a brief delay. If the error persists, contact support at **[support@therundown.io](mailto:support@therundown.io)** and include the full request URL and timestamp.

## The 0.0001 Sentinel Value

<Info>
  The value `0.0001` appearing in odds or line fields is **not** an error. It is a sentinel value indicating that a line is currently unavailable or has not yet been posted by the sportsbook.
</Info>

When a sportsbook has not released a line, or when a previously available line has been taken down, the API returns `0.0001` rather than `null` or omitting the field. This ensures a consistent numeric type across all responses and makes it straightforward to filter in your code.

```json theme={null}
{
  "affiliate_id": 1,
  "spread": {
    "point_spread_home": 0.0001,
    "point_spread_away": 0.0001,
    "point_spread_home_money": 0.0001,
    "point_spread_away_money": 0.0001
  }
}
```

### How to Handle 0.0001

Filter out the sentinel value when displaying or processing lines. Treat any field equal to `0.0001` as "not available."

<CodeGroup>
  ```python Python theme={null}
  SENTINEL = 0.0001

  def is_line_available(value):
      """Return True if the line value is real, not the sentinel."""
      return value is not None and value != SENTINEL

  spread = event["spread"]["point_spread_home"]
  if is_line_available(spread):
      print(f"Home spread: {spread}")
  else:
      print("Home spread: not available")
  ```

  ```javascript JavaScript theme={null}
  const SENTINEL = 0.0001;

  function isLineAvailable(value) {
    return value !== null && value !== undefined && value !== SENTINEL;
  }

  const spread = event.spread.point_spread_home;
  if (isLineAvailable(spread)) {
    console.log(`Home spread: ${spread}`);
  } else {
    console.log("Home spread: not available");
  }
  ```
</CodeGroup>

## Troubleshooting Checklist

If you are encountering errors, work through this checklist:

1. **Check your API key.** Is it present in the request? Is it valid? Try the key against a public endpoint like `/v2/sports`.
2. **Inspect the full response.** Read the `message` field in the error response for specific guidance.
3. **Review the request URL.** Ensure the path, query parameters, and date formats are correct.
4. **Check your billing and throttle headers.** If you are getting `429` responses, inspect `Retry-After`, `X-Datapoints-Remaining`, `X-Datapoints-Reset`, and `X-Rate-Limit`.
5. **Retry with backoff for 5xx errors.** Server errors are usually transient. Retry after a short delay.
6. **Contact support.** If the issue persists, email **[support@therundown.io](mailto:support@therundown.io)** with the request URL, response body, and timestamp.

## Retryable vs Non-Retryable Errors

Not all errors should be retried. Retrying a `401` won't fix a bad API key, but a `502` may resolve on the next attempt.

| Status |   Retryable?   | Action                                                       |
| ------ | :------------: | ------------------------------------------------------------ |
| `400`  |       No       | Fix the request parameters                                   |
| `401`  |       No       | Check your API key                                           |
| `404`  |       No       | Verify the resource ID or URL path                           |
| `429`  | Yes, sometimes | Respect `Retry-After`; do not blindly retry cap-based `429`s |
| `500`  |       Yes      | Retry with exponential backoff                               |
| `502`  |       Yes      | Retry with exponential backoff                               |
| `503`  |       Yes      | Retry with exponential backoff                               |

## Retry Strategy

For retryable errors, use **exponential backoff with jitter** to avoid thundering-herd problems when the server recovers.

The pattern: wait `base * 2^attempt` seconds, add a random jitter, and cap the maximum delay. Three retries is usually sufficient — if the error persists after that, log it and move on.

<CodeGroup>
  ```python Python theme={null}
  import requests
  import time
  import random

  API_KEY = "YOUR_API_KEY"
  BASE = "https://therundown.io/api/v2"
  RETRYABLE = {429, 500, 502, 503}

  def fetch_with_retry(url, params=None, max_retries=3):
      """Fetch a URL with exponential backoff for transient errors."""
      for attempt in range(max_retries + 1):
          resp = requests.get(
              url,
              headers={"X-TheRundown-Key": API_KEY},
              params=params,
          )

          if resp.status_code == 200:
              return resp.json()

          if resp.status_code not in RETRYABLE or attempt == max_retries:
              resp.raise_for_status()

          if resp.status_code == 429:
              retry_after = resp.headers.get("Retry-After")
              wait = max(int(retry_after), 1) if retry_after else 60
          else:
              wait = min(2 ** attempt + random.uniform(0, 1), 30)

          print(f"Retrying in {wait:.1f}s (attempt {attempt + 1}/{max_retries})")
          time.sleep(wait)

  # Usage
  events = fetch_with_retry(f"{BASE}/sports/4/events/2026-02-26")
  ```

  ```javascript JavaScript theme={null}
  const API_KEY = "YOUR_API_KEY";
  const BASE = "https://therundown.io/api/v2";
  const RETRYABLE = new Set([429, 500, 502, 503]);

  async function fetchWithRetry(url, maxRetries = 3) {
    for (let attempt = 0; attempt <= maxRetries; attempt++) {
      const resp = await fetch(url, {
        headers: { "X-TheRundown-Key": API_KEY },
      });

      if (resp.ok) return resp.json();

      if (!RETRYABLE.has(resp.status) || attempt === maxRetries) {
        throw new Error(`API error ${resp.status}: ${await resp.text()}`);
      }

      let wait;
      if (resp.status === 429) {
        const retryAfter = resp.headers.get("Retry-After");
        wait = retryAfter ? Math.max(Number(retryAfter), 1) : 60;
      } else {
        wait = Math.min(2 ** attempt + Math.random(), 30);
      }

      console.log(`Retrying in ${wait.toFixed(1)}s (attempt ${attempt + 1}/${maxRetries})`);
      await new Promise((r) => setTimeout(r, wait * 1000));
    }
  }

  // Usage
  const events = await fetchWithRetry(
    `${BASE}/sports/4/events/2026-02-26?key=${API_KEY}`
  );
  ```
</CodeGroup>

## Complete Error-Handling Wrapper

This wrapper combines retry logic with sentinel value filtering into a single utility you can use across your integration.

<CodeGroup>
  ```python Python theme={null}
  import requests
  import time
  import random

  API_KEY = "YOUR_API_KEY"
  BASE = "https://therundown.io/api/v2"
  SENTINEL = 0.0001
  RETRYABLE = {429, 500, 502, 503}

  def api_request(path, params=None, max_retries=3):
      """Make an API request with retry logic. Returns parsed JSON."""
      url = f"{BASE}/{path.lstrip('/')}"

      for attempt in range(max_retries + 1):
          resp = requests.get(
              url,
              headers={"X-TheRundown-Key": API_KEY},
              params=params,
          )

          if resp.status_code == 200:
              return resp.json()

          if resp.status_code not in RETRYABLE or attempt == max_retries:
              resp.raise_for_status()

          if resp.status_code == 429:
              retry_after = resp.headers.get("Retry-After")
              wait = max(int(retry_after), 1) if retry_after else 60
          else:
              wait = min(2 ** attempt + random.uniform(0, 1), 30)

          time.sleep(wait)

  def is_available(price_value):
      """Return True if the price is real (not the 0.0001 sentinel)."""
      return price_value is not None and price_value != SENTINEL

  def get_main_line_prices(event, market_id, affiliate_id):
      """Extract the main-line price for each participant in a market."""
      results = []
      for market in event.get("markets", []):
          if market["market_id"] != market_id:
              continue
          for participant in market.get("participants", []):
              for line in participant.get("lines", []):
                  price_obj = line.get("prices", {}).get(str(affiliate_id))
                  if not price_obj or not price_obj.get("is_main_line"):
                      continue
                  price = price_obj["price"]
                  results.append({
                      "participant": participant["name"],
                      "line": line["value"],
                      "price": price if is_available(price) else None,
                      "available": is_available(price),
                  })
      return results

  # Usage
  data = api_request("sports/4/events/2026-02-26", {"market_ids": "1,2,3"})
  for event in data.get("events", []):
      spreads = get_main_line_prices(event, market_id=2, affiliate_id=19)
      for s in spreads:
          if s["available"]:
              print(f"{s['participant']}: {s['line']} ({s['price']})")
          else:
              print(f"{s['participant']}: off the board")
  ```

  ```javascript JavaScript theme={null}
  const API_KEY = "YOUR_API_KEY";
  const BASE = "https://therundown.io/api/v2";
  const SENTINEL = 0.0001;
  const RETRYABLE = new Set([429, 500, 502, 503]);

  async function apiRequest(path, params = {}, maxRetries = 3) {
    const query = new URLSearchParams({ key: API_KEY, ...params });
    const url = `${BASE}/${path.replace(/^\//, "")}?${query}`;

    for (let attempt = 0; attempt <= maxRetries; attempt++) {
      const resp = await fetch(url);

      if (resp.ok) return resp.json();

      if (!RETRYABLE.has(resp.status) || attempt === maxRetries) {
        throw new Error(`API error ${resp.status}: ${await resp.text()}`);
      }

      let wait;
      if (resp.status === 429) {
        const retryAfter = resp.headers.get("Retry-After");
        wait = retryAfter ? Math.max(Number(retryAfter), 1) : 60;
      } else {
        wait = Math.min(2 ** attempt + Math.random(), 30);
      }

      await new Promise((r) => setTimeout(r, wait * 1000));
    }
  }

  function isAvailable(priceValue) {
    return priceValue != null && priceValue !== SENTINEL;
  }

  function getMainLinePrices(event, marketId, affiliateId) {
    const results = [];
    for (const market of event.markets || []) {
      if (market.market_id !== marketId) continue;
      for (const participant of market.participants || []) {
        for (const line of participant.lines || []) {
          const priceObj = line.prices?.[String(affiliateId)];
          if (!priceObj?.is_main_line) continue;
          results.push({
            participant: participant.name,
            line: line.value,
            price: isAvailable(priceObj.price) ? priceObj.price : null,
            available: isAvailable(priceObj.price),
          });
        }
      }
    }
    return results;
  }

  // Usage
  const data = await apiRequest("sports/4/events/2026-02-26", {
    market_ids: "1,2,3",
  });
  for (const event of data.events || []) {
    const spreads = getMainLinePrices(event, 2, 19);
    for (const s of spreads) {
      if (s.available) {
        console.log(`${s.participant}: ${s.line} (${s.price})`);
      } else {
        console.log(`${s.participant}: off the board`);
      }
    }
  }
  ```
</CodeGroup>

<Note>
  For strategies to reduce the number of API calls you make (and the errors you encounter), see the [Efficient Polling guide](/guides/efficient-polling) and [Rate Limits](/rate-limits).
</Note>
