Setting Up
Rate Limits & Errors
Per-key limits, the headers that expose them, and how to back off.
Limits#
Each key has its own limit, default 600 requests per minute, configurable from 10 to 6 000 when the key is created or edited. The window is a fixed 60-second bucket.
| Header | Meaning |
|---|---|
X-RateLimit-Limit | Requests allowed per minute for this key |
X-RateLimit-Remaining | Requests left in the current window |
X-RateLimit-Reset | Unix time (seconds) when the window resets |
Retry-After | Only on 429/503: seconds to wait |
When the limit is exceeded you get 429 rate_limited. The limiter fails closed: if its backing store is unreachable you get 503 rate_limiter_unavailable with Retry-After: 5 rather than an unlimited pass.
Backing off#
async function call(url, init, attempt = 0) {
const res = await fetch(url, init);
if ((res.status === 429 || res.status === 503) && attempt < 5) {
const wait = Number(res.headers.get('Retry-After') || 2 ** attempt);
await new Promise((r) => setTimeout(r, wait * 1000));
return call(url, init, attempt + 1);
}
return res;
}Bulk work#
- Use
POST /students/bulk(≤ 200 per call) instead of loopingPOST /students. - Page through lists with
limit=200. - Prefer webhooks to polling; if you must poll, use
GET /events?since=once a minute rather than re-listing resources.
Idempotency#
Writes that carry a ref are idempotent on it: merit credits, Alpha point awards, webhook event replays. Provisioning calls are not idempotent — store the returned login_email and check before re-creating. A dedicated Idempotency-Key header is on the roadmap.
Error reference#
| Status | Codes |
|---|---|
| 400 | institution_required (platform admins only) |
| 401 | unauthorized, invalid_api_key, LAUNCH_TOKEN_INVALID (exchange) |
| 403 | insufficient_scope, forbidden, institution_not_approved |
| 404 | not_found, user_not_found, no_secret |
| 409 | conflict, already_added, already_verified, user_inactive, insufficient_allowance, endpoint_inactive, void_failed |
| 410 | event_pruned |
| 422 | validation_error, invalid_faculty, invalid_scopes, no_owner, no_valid_students, https_required, private_host, events_required, endpoint_limit, key_limit, file_too_large |
| 429 | rate_limited |
| 500 | internal_error |
| 501 | not_implemented |
| 503 | rate_limiter_unavailable |