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

# Errors and limits

> Status codes, error bodies and the rate limit.

Errors use standard HTTP status codes and return a JSON body. Gateway errors carry `statusCode` and
`message`:

```json theme={null}
{ "statusCode": 403, "message": "end is before your data window, which starts at 2026-10-06T22:36:51Z" }
```

Parameter validation errors (`422`) list each failing field in `detail`:

```json theme={null}
{
  "detail": [
    { "type": "enum", "loc": ["query", "frequency"], "msg": "Input should be '1h'", "input": "1d" }
  ]
}
```

## Status codes

| Code | Meaning | What to do |
| - | - | - |
| `200` | Success. | |
| `400` | `start` or `end` is not a valid ISO 8601 timestamp. The message shows the value received. | Send `YYYY-MM-DD` or `YYYY-MM-DDTHH:MM:SSZ`. |
| `401` | The API key is missing or invalid. | Check the `X-API-Key` header. See [Authentication](https://docs.sandmark.com/authentication.md). |
| `403` | `end` is before your [history window](https://docs.sandmark.com/concepts/history-window.md). | Move `end` forward. |
| `404` | Unknown path. | Check the URL against the [reference](https://docs.sandmark.com/api-reference/list-assets.md). |
| `422` | A parameter is missing or not one of its allowed values. | Read `detail[].loc` for the field. |
| `429` | Rate limit exceeded. | Wait `Retry-After` seconds, then retry. |
| `5xx` | Something went wrong on our side. | Retry with backoff. If it continues, contact Sandmark. |

## Rate limit

Each API key may make **60 requests per minute**. Past that, requests get `429` with a `Retry-After` header
until the minute rolls over:

```http theme={null}
HTTP/1.1 429 Too Many Requests
Retry-After: 12
```

```json theme={null}
{ "statusCode": 429, "message": "Rate limit is exceeded. Try again in 12 seconds." }
```

> [!TIP]
>
> Most workloads never come close to the limit. Each timeseries request can return up to 5,000 rows, so it's
> far cheaper to fetch a wide range once and filter locally than to make one request per day or per hour.

## Retrying

Retry `429` and `5xx` responses. Don't retry other `4xx` codes: the request itself needs changing.

```python theme={null}
import time

import requests


def get_with_retry(session: requests.Session, url: str, params: dict, attempts: int = 5):
    for attempt in range(attempts):
        response = session.get(url, params=params, timeout=60)
        if response.status_code == 429:
            time.sleep(int(response.headers.get("Retry-After", "5")))
            continue
        if response.status_code >= 500 and attempt < attempts - 1:
            time.sleep(2 ** attempt)
            continue
        response.raise_for_status()
        return response.json()
    response.raise_for_status()
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.