Skip to content

Rate Limiting

Unauthenticated requests are capped per IP.

The cap:

RateBurst
1000 / hour500

Reads and writes both count. Authenticate to remove the per-IP cap; authenticated writes are governed by the limits below.

The 500-token burst means you can fire 500 requests back-to-back when the bucket is full; after that you refill at ~17 requests per minute.

  • Steady polling under ~16/minute works forever.
  • A burst of 500 requests works once, then you wait ~30 minutes for the bucket to refill.
  • Anonymous polling at 50/second drains the burst in 10 seconds, then 429s until the bucket refills.
  • Many users sharing one IP (corporate networks, VPNs) — everyone shares the same budget. Authenticate to escape the shared cap.

When the budget is exhausted, the API returns 429 Too Many Requests with a Retry-After header and this body:

{
"error": {
"code": "RATE_LIMITED",
"message": "Anonymous request limit reached. Authenticate at https://docs.warmhub.ai/auth/getting-access for higher limits.",
"hint": "Authenticate at https://docs.warmhub.ai/auth/getting-access for higher limits.",
"retryAfter": 4
},
"scope": "ip",
"authenticated": false
}
  • error.retryAfter matches the Retry-After header (seconds). After fully draining the bucket this is typically 3 to 4 seconds.
  • scope is always "ip" for anonymous 429s.
  • authenticated is always false — authenticated requests bypass this cap and never produce a 429 here.

Authenticate via the getting-access flow and use a personal access token (PAT) or session token. If you have a legitimate scraping or monitoring use case that needs to remain anonymous and run above 1000/hour from a single IP, contact support — IP-level whitelisting is available case-by-case.

Some authenticated write operations are capped per user and per organization — the same limits apply whichever WarmHub client you use to issue the write. The table under Rate-limited operations below has the full list. Other authenticated writes and all authenticated reads aren’t currently capped.

ScopeKeyDescription
Per-userUser IDProtects against individual abuse. On the free tier, this applies to repo-scoped writes. On paid tiers, the org limit governs instead for shared repo write budgets such as commits. Standalone operations such as org creation and PAT creation, plus destructive repo delete operations, are always per-user regardless of tier.
Per-orgOrg IDA shared budget across all org members. Higher tiers receive higher limits.

Only resource-creating and high-cost write operations are rate limited. Many operations on existing resources — such as pause, revoke, and rename — are not rate limited, because they are self-limiting: you can only act on what already exists.

However, some write operations on existing resources are still rate limited. Revising or removing shapes, for example, draws from the same write permission budget as creation. Repository delete is also rate limited separately because it is irreversible and high impact.

OperationScopeStrategy
Writes to things and assertions — including commits and content updates (README, AGENTS, and generated content)Per-user + per-orgToken bucket
Shape creationPer-orgToken bucket
Shape revise / removePer-orgToken bucket
Subscription creationPer-orgToken bucket
Credential set creationPer-orgFixed window
Repository creationPer-orgFixed window
Repository deletePer-userFixed window
Organization creationPer-userFixed window
PAT (Personal Access Token) creationPer-userFixed window

All organizations start on the free tier. Limits increase with higher tiers:

TierCommits/minRepos/hrRepo deletes/hrShapes/minSubscriptions/minCredentials/hrOrgs/hrPATs/hr
Free120 (user) / 600 (org)2020 (user)4020201020
Pro1,000 (org)5020 (user)6030501020
Enterprise5,000 (org)20020 (user)2001002001020

On paid tiers, shared repo write limits are generally enforced at the org level. Organization creation, PAT creation, and repository delete remain per-user on all tiers.

When a per-user or per-org limit is exceeded, the API returns HTTP 429 with a RATE_LIMITED error:

HTTP/1.1 429 Too Many Requests
Retry-After: 12
Content-Type: application/json
{
"error": {
"code": "RATE_LIMITED",
"message": "Rate limit exceeded",
"retryAfter": 12
}
}

The Retry-After response header carries the number of seconds to wait before retrying. The same value is serialized into error.retryAfter so clients can read it without parsing headers.

When you receive a 429:

  1. Read the Retry-After header — wait at least that many seconds before retrying.
  2. Use exponential backoff as a fallback if the header is missing.
  3. Don’t retry immediately — rapid retries consume tokens and extend the wait.
  • Authenticated read operationshead, query, and about lookups; action-run reads.
  • Simple operations on existing resources — pause, resume, rename, revoke.