Rate limits
Construction AI rate-limits API calls per key on both a requests-per-minute and daily basis. Defaults, headers, 429 handling, and how to ask for higher limits.
Construction AI rate-limits authenticated API calls per key. Limits are protective rather than billing-oriented — they prevent runaway loops, misconfigured clients, and accidental over-use, while leaving comfortable headroom for normal integration traffic.
Defaults
Every API key is created with two limits:
| Limit | Default | Range at creation |
|---|---|---|
| Requests per minute (RPM) | 60 | 1 – 600 |
| Requests per day | 10,000 | 1 – 100,000 |
You can override both at key creation in the dashboard. A key for a high-frequency integration might be issued at 300 RPM / 50,000 daily; a key for a one-off batch job might be issued at 10 RPM / 1,000 daily to limit blast radius.
What happens when you hit a limit
The API responds with HTTP 429 Too Many Requests:
{
"success": false,
"error": "rate_limited",
"message": "Rate limit exceeded (rpm)"
}The response includes a Retry-After header with the number of seconds to wait before retrying:
HTTP/1.1 429 Too Many Requests
Retry-After: 47
Content-Type: application/json
The message field tells you which limit you hit — rpm or daily. Both have the same response shape; only the retry interval differs (a minute-level retry vs a day-level retry).
How to handle 429s
Two patterns, depending on the integration shape.
Backoff and retry (server-to-server sync)
If you're a vendor bridge or a sync job, the right behaviour is to honour Retry-After and retry once:
async function callApi(url: string, init?: RequestInit): Promise<Response> {
const response = await fetch(url, init);
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get('Retry-After') ?? '60', 10);
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
return fetch(url, init);
}
return response;
}For sustained traffic above your limit, retrying repeatedly is the wrong answer — request a higher limit instead (see below).
Surface to the user (interactive client)
If you're an AI agent or interactive client, surface the limit to the user rather than silently retrying:
"Rate limit reached — please wait 47 seconds and try again."
Silent retries feel like the client is slow; explicit messaging is more honest and avoids burning further quota.
Limit accounting
- RPM is a rolling window. Each request is counted; the window slides every second. Bursts of 60 in one second are fine if the previous 59 seconds had zero.
- Daily is a rolling 24-hour window (not calendar day). Resets continuously.
- Both limits enforced together. Exceeding either returns 429.
- Limits are per key, not per IP or per organisation. Two keys in the same organisation have independent budgets.
Raising your limits
If your integration legitimately needs more than 600 RPM or 100,000 daily, request an increase via the dashboard contact form. The platform is happy to grant higher caps for known integrations — the defaults exist to protect against accidents, not to gate scale.
Things that help the request:
- A description of the integration shape (sync vs interactive, expected traffic profile)
- The key name(s) needing the bump
- Your expected steady-state RPM and burst RPM separately
Things that don't help:
- Asking for "unlimited" — there are always upper bounds, even if administrative ones
- Asking before optimising client-side batching — many "needs more RPM" cases turn out to be N+1 patterns that batch endpoints would solve
Headers we emit
Today the platform emits only Retry-After on 429 responses. We do not currently emit X-RateLimit-Remaining / X-RateLimit-Reset / similar pre-emptive headers — clients must observe their own request counts or rely on the 429 + retry pattern.
This is a deliberate simplification for now; emitting per-request rate-limit headers adds overhead and most clients don't consume them correctly. If you have a specific use case for pre-emptive headers, raise it through the contact form.
What's NOT rate-limited
- The OAuth endpoints (
/oauth/authorize,/oauth/token,/oauth/register) have separate IP-based rate limiting to prevent brute-force attacks. They're independent of per-key limits. - The
/agent-infodiscovery endpoint is unauthenticated and not rate-limited. - Session-authenticated dashboard traffic (your normal browser usage) is not subject to the API key rate limits. They apply only to bearer-token-authenticated calls.
Where to go next
- Authentication — how to mint keys and configure their limits.
- Errors — the full error shape and what to do with each code.