Rate limits
Limits are Laravel rate limiters (fixed window of one minute, counters in the cache). A throttled
request answers 429 with a Retry-After header (seconds). Counters are per tenant host: each
tenant has its own cache prefix (Tenancy).
| Where | Limit | Counted per | Tune with |
|---|---|---|---|
POST /api/auth/device/code |
10 / min | IP | fixed |
POST /api/auth/device/token (CLI polling) |
60 / min | IP | fixed |
| `GET | POST /api/auth/device/requests/{user_code}…` (show, approve, deny) | 5 / min | signed-in user |
/api/auth/tokens*, /api/admin/tokens*, /api/admin/agent-audit |
60 / min | user (IP if anonymous) | fixed |
GET /api/meta |
60 / min | user or IP | fixed |
POST /api/auth/email/resend |
6 / min | user | fixed |
POST /api/demo/login |
60 / min | user or IP | fixed |
/api/platform/* (platform API) |
60 / min | user | fixed |
Any request with a scoped token that has rate_limit_per_minute |
1 to 6000 / min, set when the token is created | token | rate_limit_per_minute on POST /api/auth/tokens |
POST /api/living-course/webhooks/{id} |
60 / min; also 48 automatic checks per connection and day | webhook id | living_course.poll.checks_per_connection_per_day |
POST /api/jitsi/recorded-video |
60 / min | IP | fixed |
POST /api/lti/tool/exchange |
30 / min | IP | fixed |
POST /api/liascript/progress/{topic} |
120 / min | user or IP | fixed |
POST /api/liascript/{id}/preview, POST /api/cmi5/fetch, POST /api/csp-report |
60 / min | IP | fixed |
| Image renderer `GET | POST /api/img` | 10 000 / min overall and 1 000 / min per IP. GET only when the limiter is enabled, POST always |
host, IP |
Routes in api/routes/api.php (/api/events, /api/health, /api/healthcheck, /api/name) |
60 / min | IP | fixed |
The per-token limit answers differently from the others, in the API’s own error shape:
{ "success": false, "message": "This token exceeded its rate limit.", "error": "rate_limited" }with status 429 and Retry-After. It is checked after the scope check, so a call refused with 403
does not use it up. See Scoped API tokens.
Stripe’s webhook route has no ulams throttle either; it is protected by its signature (Webhooks).
Handling a 429
Section titled “Handling a 429”Wait for Retry-After seconds and retry. For writes, send an Idempotency-Key so a retry cannot
duplicate the change (Scoped API tokens).
The CLI maps a 429 to the error RATE_LIMITED with details.retryAfter (CLI).