# Rate limits

> Two requests a second with small bursts on the default plan, Retry-After on 429, monthly caps, and the non-JSON 403 edge throttle with a 90-second back-off.

Updated 2026-10-24 · HTML: https://layercrew.com/developers/rate-limits · Markdown: https://layercrew.com/developers/rate-limits.md · OpenAPI: https://layercrew.com/openapi.json

Two limits apply to your keys: a pace per key, and monthly caps per plan. Both answer `429` with a JSON envelope. The network edge in front of the API adds a third, coarser one that answers without JSON.

## Pace

The default plan allows 2 requests a second per key with bursts up to 10; your plan may allow more, and `GET /v1/ping` reports your `rate_limit`. Over the limit you get `429 RATE_LIMITED` with `Retry-After` in seconds, `X-RateLimit-Limit` and `X-RateLimit-Remaining`. Throttled calls are logged like any other and counted in your usage.

> **X-RateLimit-Remaining is per server.** The number is the bucket of the server that answered. The API runs on more than one, so it is a guide, never a contract: pace your client by `Retry-After` and by your own clock.

## Monthly caps

Your plan may cap requests, live orders and test orders per calendar month (UTC). `GET /v1/account` shows the caps under `limits` — `monthly_request_cap`, `monthly_order_cap`, `monthly_test_order_cap`, where `0` means no cap — and this month's `usage`. At 80 percent of a cap you get `usage.warning` once; at the cap, `429 QUOTA_EXCEEDED` until the first of next month. The request cap is checked against a count refreshed every 30 seconds, so a handful of calls may pass over it. A quote cap per day (`quote_daily_cap`) works the same way.

## The edge throttle

In front of the API sits a network edge that protects every LayerCrew service. Above roughly 5 requests a second from one address it answers `403` with an HTML or empty body — no JSON envelope, no `X-Request-ID`.

- Treat a `403` without JSON as the edge: **back off 90 seconds**, then resume at two requests a second.
- Keep to two requests a second per source address, with small bursts, even when your plan's key limit is higher.
- **Never retry a create blindly.** For an order, send the same `Idempotency-Key` again or look it up by `reference`; for a file, list your files; for a webhook endpoint, list your endpoints.

## Designing for the limits

- Use webhooks for state changes and the events feed to reconcile; polling each order is the pattern that hits the limits first.
- Batch your reads: lists take `per_page` up to 100, the feed `limit` up to 200.
- One client, one queue: a shared token bucket in your integration keeps bursts in bounds.
- Back off on `429` and on `5xx` with jitter; quote `X-Request-ID` if a pattern repeats.

_A fetch wrapper that honours Retry-After, backs off the edge and never retries a create without its key_

```node
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

export async function layercrew(path, init = {}, attempt = 0) {
  const res = await fetch(`https://engine.layercrew.com/v1${path}`, {
    ...init,
    headers: { Authorization: `Bearer ${process.env.LAYERCREW_API_KEY}`, ...(init.headers ?? {}) },
  });
  const isJson = (res.headers.get("content-type") ?? "").includes("application/json");
  if (res.status === 403 && !isJson) {
    await sleep(90_000);                       // the edge: back off 90 seconds
    return layercrew(path, init, attempt + 1); // same init, same Idempotency-Key
  }
  if ((res.status === 429 || res.status >= 500) && attempt < 5) {
    const retryAfter = Number(res.headers.get("retry-after")) || 2 ** attempt;
    await sleep(retryAfter * 1000 + Math.random() * 500);
    return layercrew(path, init, attempt + 1);
  }
  return res;
}
```

## Body size

Request bodies are limited to 256 KiB — `413 PAYLOAD_TOO_LARGE` beyond it. File bytes never travel in JSON; they go to the signed upload URL described under [Files](https://layercrew.com/developers/files#upload).
