Rate Limits

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


Tier Limits

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

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

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

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

{
  "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

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:

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

If you're approaching your monthly limit:

  1. Cache read responsesGET /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.