Appearance
Rate Limiting
API requests are rate limited per route. Limits apply per IP (for unauthenticated auth routes) or per user (for authenticated routes). The window is 1 minute; over-limit requests receive 429 Too Many Requests.
How it works
- Key by IP: Used for
GET /m/:key(image serve),POST /auth/register, andPOST /auth/login. Same IP is limited across all users. - Key by user: Used for all other limited routes. The identifier is the authenticated user (JWT or API key). Each user has their own counter per route.
- Window: Fixed 1-minute window. Counters reset at the start of each new window.
- No limit:
/ping,/d/:key,/reference/*, and webhooks are not rate limited.
Endpoint limits
| Endpoint | Type | Limit (req/min) |
|---|---|---|
| GET /ping | — | — |
| GET /m/:key | IP | 400 |
| GET /d/:key | — | — |
| GET /reference/* | — | — |
| POST /auth/register | IP | 10 |
| POST /auth/login | IP | 15 |
| POST /auth/api-keys | userId | 10 |
| GET /auth/api-keys | userId | 30 |
| DELETE /auth/api-keys/:id | userId | 10 |
| POST /files | userId | 30 |
| POST /files/bulk-delete | userId | 20 |
| GET /files | userId | 60 |
| GET /files/:id | userId | 120 |
| DELETE /files/:id | userId | 60 |
| GET /settings | userId | 60 |
| PATCH /settings | userId | 20 |
| GET /profile | userId | 60 |
| GET /usage | userId | 60 |
| GET /logs | userId | 60 |
Type: IP = limit by client IP; userId = limit by authenticated user; — = not limited.
429 response
When the limit is exceeded:
- Status: 429 Too Many Requests
- Body:
{ "error": "Too many requests" } - Header:
Retry-After: <seconds>(seconds until the current window ends, typically 60)
Handling: Back off and retry after the given seconds.
javascript
const retryAfter = res.headers.get('Retry-After')
await new Promise(r => setTimeout(r, parseInt(retryAfter || '60') * 1000))
// Retry requestImage resize concurrency (503)
GET /m/:key requests that need resize or format (query params width, height, or format) are limited by concurrent processing capacity. At most 10 such operations run at once.
- When no slot is available: Response is 503 with
Retry-After: 5and body{ "error": "Server busy; try again shortly" } - Passthrough requests (no resize/format) do not use a slot and are not affected
Retry after the Retry-After seconds when you receive 503.