> For the complete documentation index, see [llms.txt](https://docs.tryterra.co/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.tryterra.co/reference/health-and-fitness-api/rate-limits.md).

# Rate limits

Learn the two rate limits on data-read endpoints, the headers that report them, and how to handle a 429.

Terra applies rate limits to data-read endpoints (for example `/activity`) to stop runaway loops and keep response times consistent.

The limits apply per `user_id`: each user you've connected has its own budget, separate from your other users.

## Limits

There are two rules. A request must satisfy both to be accepted.

| Rule                      | Limit                | Window                              |
| ------------------------- | -------------------- | ----------------------------------- |
| Per-request window size   | 1,825 days (5 years) | Per request                         |
| Cumulative requested days | 6,000 days           | Rolling 1-hour bucket per `user_id` |

The "requested days" cost of a single call is `end_date − start_date`. Calls without `start_date` and `end_date` count as 1 day. Requests of exactly 1,825 days are allowed: only more than 1,825 is rejected.

{% hint style="info" %}
Most requests use small windows (30 days or less) and never come close to either limit. The per-request cap stops calls that are clearly broken, such as requesting 20 years of data at once. The hourly cumulative cap stops loops that request multi-year windows for the same user over and over.
{% endhint %}

## Detect throttling

When Terra rejects a request, you receive an `HTTP 429 Too Many Requests` response with these headers:

| Header                   | Meaning                                                                                                            |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------ |
| `X-Terra-RateLimit-Rule` | Which rule fired: `r1` (per-request) or `r2` (cumulative)                                                          |
| `Retry-After`            | For `r2`: seconds until the hourly bucket resets. For `r1`: not set, because retrying the same request won't help. |

Response body:

```json
{
  "detail": "rate limit exceeded"
}
```

## Read your remaining budget

When Terra accepts a request and evaluates the cumulative rule (`r2`), the response includes three headers so you can pace your requests:

| Header                          | Meaning                                        |
| ------------------------------- | ---------------------------------------------- |
| `X-Terra-RateLimit-Limit`       | Your hourly limit in days (for example `6000`) |
| `X-Terra-RateLimit-Remaining`   | Days left in the current hour bucket           |
| `X-Terra-RateLimit-Reset-After` | Seconds until the bucket resets                |

Example response:

```http
HTTP/1.1 200 OK
Content-Type: application/json
X-Terra-RateLimit-Limit: 6000
X-Terra-RateLimit-Remaining: 5910
X-Terra-RateLimit-Reset-After: 1843
...
```

If these headers are missing, Terra tracked no budget for the request (for example an unauthenticated path), and you don't need to act on them.

## Handle 429 responses

* `X-Terra-RateLimit-Rule: r1`: your `start_date` to `end_date` window is wider than 5 years. Split it into shorter windows and retry. Don't retry the same request, because it will fail again.
* `X-Terra-RateLimit-Rule: r2`: your requests for this user add up to more than 6,000 days in the current hour. Wait `Retry-After` seconds, then resume. Smaller windows won't help until the bucket resets.

A simple back-off:

```python
import time, requests

while True:
    r = requests.get(url, headers=headers)
    if r.status_code != 429:
        break
    if r.headers.get("X-Terra-RateLimit-Rule") == "r1":
        raise ValueError("window > 5 years; chunk it")
    time.sleep(int(r.headers.get("Retry-After", 60)))
```

## Best practices

* Cache what you've already fetched. The cumulative cap counts every request in the hour, including repeats of data you already have.
* Chunk backfills. A 5-year backfill in 30-day chunks is 60 calls. Spread them across a few hours and you won't see a 429.
* Watch the `X-Terra-RateLimit-Remaining` header in your production logs. A value dropping toward zero tells you throttling is coming.
* Send one request per user at a time. Parallel calls for the same user share a budget, so concurrency does not increase your allowance.

{% hint style="warning" %}
Connections without a user (for example before authentication completes) aren't rate-limited. The budget starts once a connection ID exists.
{% endhint %}

## FAQ

**Does this apply to webhook deliveries?** No. Rate limits only apply to API requests you send to the data-read endpoints.

**Does the budget reset at the top of the hour, or an hour after my first request?** Buckets are fixed and aligned to UTC (for example 14:00 to 15:00 UTC). The `Retry-After` header tells you when the current bucket resets.

**Can I get a higher limit?** Ask your Terra account contact. Limits can be raised per `dev_id` for legitimate use cases.

**What counts as one day of cost?** Whatever you pass as `end_date − start_date`. A request with `?start_date=2024-01-01&end_date=2024-01-31` costs 30 days, however much data the user has in that range.
