Rate Limits

Rate Limits

Lathe Studio rate-limits the API to keep the platform stable and available for everyone.

Limits are not a packaging device. There is one plan — $25 per user per month, everything included — and the full API is part of it. Limits exist for two reasons:

  1. To absorb attacks and runaway clients so a burst of traffic cannot degrade the service for other customers.
  2. To keep the API a companion to your Lathe Studio usage, rather than a free backend for unrelated software.

Well-behaved integrations — CI pipelines posting results, dashboards polling on a sane interval, webhook consumers — are not the target and should not run into these limits in normal use.


How limiting works#

Each API key carries its own limit, so you can issue a tightly-limited key to a noisy pipeline without affecting your other integrations. Limits are applied per key and per organization, and we tune them as we learn what real usage looks like.

We deliberately do not publish specific thresholds. They are set to catch abuse rather than to ration normal use, and they will change as the platform matures. Write your client to react to a 429 (see below) rather than to a number documented here.

Responses may carry X-RateLimit-* and X-Monthly-Quota-* headers indicating remaining allowance. Treat them as advisory: use them to back off early when present, but do not rely on them as an exact budget.


429 Too Many Requests#

When you exceed either limit, the API returns 429 in the standard error shape:

{
  "error": {
    "code": "RATE_LIMITED",
    "message": "Rate limit exceeded",
    "details": {
      "reset_at": 1775001600
    }
  }
}

details.reset_at is a Unix timestamp (seconds) for when you may retry.

If you are hitting limits during legitimate use, email support — we would rather adjust your limits than have you work around them.


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#

Good practice regardless of where the limits sit:

  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. Issue per-integration keys — Separate keys make it obvious which client is doing the work.