# Rate Limits

Lathe Studio enforces rate limits to ensure fair usage and API stability.

---

## [Tier Limits](/content/docs/api/rate-limits#tier-limits/index.html)

| Tier       | Monthly Limit         | Notes                                   |
|------------|-----------------------|-----------------------------------------|
| Basic      | No API access         | API requires Pro or Enterprise          |
| Pro        | 1,000 calls/month     | Resets on billing cycle anniversary      |
| Enterprise | 10,000 calls/month    | Custom limits available — contact sales |

Monthly limits reset at the start of each billing cycle (the anniversary of your subscription start date).

---

## [Per-Minute Limit](/content/docs/api/rate-limits#per-minute-limit/index.html)

In addition to the monthly limit, a **per-minute in-memory limit** applies to prevent burst abuse. This limit is not published and is generous enough that well-behaved CI pipelines will never hit it.

If you encounter 429 errors from the per-minute limit (rather than the monthly limit), the `Retry-After` header tells you how many seconds to wait.

---

## [Rate Limit Headers](/content/docs/api/rate-limits#rate-limit-headers/index.html)

Every API response includes these headers:

| Header                      | Description                                          |
|-----------------------------|-----------------------------------------------------|
| `X-RateLimit-Remaining`     | Calls remaining in the current billing period       |
| `X-RateLimit-Reset`        | Unix timestamp (seconds) when the monthly limit resets |

Monitor `X-RateLimit-Remaining` in your integrations to avoid unexpected 429 errors.

---

## [429 Too Many Requests](/content/docs/api/rate-limits#429-too-many-requests/index.html)

When you exceed the rate limit, the API returns a `429` response:

```json
{
  "error": "rate_limit_exceeded",
  "message": "You have exceeded your monthly API call limit. Upgrade to Enterprise for 10,000 calls/month.",
  "details": {
    "limit": 1000,
    "reset_at": "2026-04-01T00:00:00Z"
  }
}
```

---

## [Retry Guidance](/content/docs/api/rate-limits#retry-guidance/index.html)

Use **exponential backoff with jitter** when retrying after a 429:

```
wait = min(base * 2^attempt + random_jitter, max_wait)
```

Recommended values:

| Parameter      | Value         |
|----------------|---------------|
| Base wait      | 1 second      |
| Max wait       | 60 seconds    |
| Jitter         | 0–500ms random|
| Max attempts    | 5            |

Example in Python:

```python
import time
import random
import httpx

def create_build_with_retry(payload, api_key, max_attempts=5):
    for attempt in range(max_attempts):
        response = httpx.post(
            "https://app.lathe.studio/api/v1/builds",
            headers={"Authorization": f"Bearer {api_key}"},
            json=payload,
        )
        if response.status_code == 429:
            wait = min(2 ** attempt + random.uniform(0, 0.5), 60)
            time.sleep(wait)
            continue
        response.raise_for_status()
        return response.json()
    raise RuntimeError("Max retry attempts exceeded")
```

---

## [Reducing API Usage](/content/docs/api/rate-limits#reducing-api-usage/index.html)

If you're approaching your monthly limit:

1. **Cache read responses** — `GET /builds` and `GET /projects` responses are stable. Cache them for 60 seconds.
2. **Batch operations** — The `POST /testruns/{id}/results` endpoint accepts an array of results in a single call.
3. **Use webhooks instead of polling** — Subscribe to `testrun.completed.v1` instead of polling `GET /testruns/{id}` in a loop.
4. **Upgrade your plan** — Move to Enterprise for 10,000 calls/month.
