ARCNM

Building blocks

Rate limits

Per-key and per-caller limits, the 429 envelope, and how to back off safely.

ARCNM enforces per-key and per-caller rate limits; exceed one and you get 429, and every 429 costs €0. The independent limits:

Limit Applies to Default ceiling
Per API key A single key's request rate 1,200 req/min
Per-caller rate-limit bucket Each caller (key / user / org), all routes 600 req/min (sliding)
Auth routes Login / signup / password-reset, per IP Tight per-route limits

When a limit is hit you get 429. All 429s cost €0.

These are current defaults and may change. Plan-tier entitlements (calculations/month, storage) are a separate axis — see Pricing.


The 429 envelope

There are two 429 codes depending on which limiter fired:

  • The per-caller rate-limit bucket returns rate_limited with the limit and window in details:

    {
      "error": {
        "code": "rate_limited",
        "message": "Submission rate limit reached (600 weighted calls per 60s) — retry in 12s.",
        "details": { "limit": 600, "window_seconds": 60 }
      },
      "request_id": "req_…"
    }
    
  • The auth-route limiter (login / signup / password-reset) returns too_many_requests.


Budget headers

Every authenticated REST response carries the per-caller rate-limit bucket's budget so you can pace yourself before hitting 429:

Header Meaning
X-RateLimit-Limit The per-caller ceiling for the current minute window.
X-RateLimit-Remaining Budget left in the window. Weighted — a heavy op (a calculation run, an upload, the cost pipeline) consumes your plan's heavy-operation weight (25, for example) while a read consumes 1 — so this is true headroom, not a raw request count.
X-RateLimit-Reset UTC epoch second the window rolls over and the budget resets.
RateLimit The same budget in the IETF structured-field form (draft-ietf-httpapi-ratelimit-headers): "default";r=<remaining>;t=<seconds-to-reset>.
RateLimit-Policy The active policy: "default";q=<budget>;w=60 (budget q per 60-second window w).

The legacy X-RateLimit-* triplet and the standards-track RateLimit / RateLimit-Policy pair carry the same numbers — read whichever your HTTP client supports.

On a per-caller 429 you also get Retry-After (seconds until reset). Honour it when present; otherwise fall back to the backoff below. The auth-route limiter and the per-key ceiling don't emit these headers — they're a coarse floor, not a budget you meter against.


Retry strategy

Exponential backoff with full jitter. Always pair retries with an Idempotency-Key so a retry can't double-charge.

import random, time, requests

def with_retry(fn, *, max_attempts=5, cap_s=30):
    for attempt in range(max_attempts):
        resp = fn()
        if resp.status_code != 429:
            return resp
        wait = random.uniform(0, min(cap_s, (2 ** attempt)))
        time.sleep(wait)
    raise RuntimeError(f"giving up after {max_attempts} attempts")
async function withRetry(fn: () => Promise<Response>, maxAttempts = 5, capMs = 30_000) {
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    const res = await fn()
    if (res.status !== 429) return res
    const wait = Math.random() * Math.min(capMs, 2 ** attempt * 1_000)
    await new Promise((r) => setTimeout(r, wait))
  }
  throw new Error(`giving up after ${maxAttempts} attempts`)
}
for attempt in 0 1 2 3 4; do
  resp=$(curl -sw '\n%{http_code}' "$URL" -H "X-API-Key: $ARCNM_API_KEY")
  code=$(printf '%s' "$resp" | tail -n1)
  body=$(printf '%s' "$resp" | sed '$d')
  if [ "$code" = "200" ] || [ "$code" = "201" ] || [ "$code" = "202" ]; then echo "$body"; exit 0; fi
  if [ "$code" != "429" ] && [ "$code" -lt 500 ]; then echo "$body" >&2; exit 1; fi
  sleep $(awk -v n=$((2 ** attempt)) 'BEGIN{srand(); print n*rand()}')
done

What counts toward the limit

  • Every authenticated REST call counts toward the per-key and per-caller limits.
  • POST /calculations/batch is a heavy call no matter how many cells it carries — one request can enqueue up to 500 calculations, so it is priced like the other engine-run surfaces. Note the asymmetry: the batch costs one heavy-operation weight for your plan against the per-minute rate budget but one plan-quota unit per (revision × environment) cell. For a big grid, one batch call is always cheaper against the rate limit than N single quotes.
  • Pre-auth calls (login, signup, password reset) hit the auth-route limiter.
  • MCP traffic is handled by the MCP server's own verification and is not subject to the REST per-request rate-limit bucket.
  • Webhook deliveries we send to you don't count — those are our egress.

Need more headroom?

If a key is hitting the ceiling, email [email protected] with your use case and we'll size it to your forecast.


See also