Rate Limits

Rate Limits

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


Tier Limits#

TierMonthly LimitNotes
BasicNo API accessAPI requires Pro or Enterprise
Pro1,000 calls/monthResets on billing cycle anniversary
Enterprise10,000 calls/monthCustom 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:

HeaderDescription
X-RateLimit-RemainingCalls remaining in the current billing period
X-RateLimit-ResetUnix 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:

ParameterValue
Base wait1 second
Max wait60 seconds
Jitter0–500ms random
Max attempts5

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.