Rate Limits
j17 applies rate limits at two layers: per-instance limits to ensure fair sharing between tenants, and plan-based limits to enforce your billing tier. Both return 429 Too Many Requests when exceeded, with headers and response bodies that tell you exactly what happened.
Per-instance limits
Every instance has a burst rate limit determined by its billing tier. This limit applies across all API keys for the instance — creating more keys doesn't increase your throughput.
Write limits
| Tier | Limit | Per second |
|---|---|---|
| Free | 600/min | 10/s |
| Hobby | 6,000/min | 100/s |
| Pro | 12,000/min | 200/s |
| Scale | 60,000/min | 1,000/s |
Read limits
Read limits are 5x write limits. Reads are cheaper but a runaway polling loop can still affect performance for other tenants.
| Tier | Limit | Per second |
|---|---|---|
| Free | 3,000/min | 50/s |
| Hobby | 30,000/min | 500/s |
| Pro | 60,000/min | 1,000/s |
| Scale | 300,000/min | 5,000/s |
Response
When you exceed your instance limit:
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 6000
X-RateLimit-Remaining: 0
X-RateLimit-Scope: instance
X-RateLimit-Tier: hobby
{
"ok": false,
"error": "Rate limit exceeded",
"scope": "instance",
"limit": 6000,
"window": "1m",
"tier": "hobby"
}
Successful responses include X-RateLimit-Remaining so your client can self-throttle before hitting the limit.
What to do
- Batch writes. Use
POST /:type/:idwith an array of events instead of individual requests. One batch counts as one request. - Upgrade your tier. Tier changes take effect within 30 seconds.
- Backoff. When you get a 429, wait before retrying:
const delay = Math.min(1000 * 2 ** attempt, 30000) + Math.random() * 1000;
await new Promise(r => setTimeout(r, delay));
Per-IP safety net
A global per-IP limit of 30,000 requests/minute acts as a safety net. You should never hit this under normal use — if you do, the X-RateLimit-Scope header will say ip instead of instance.
Plan limits
Your plan includes a monthly allocation for events written. As you approach and exceed it:
| Usage | Behavior |
|---|---|
| Under 80% | Normal operation |
| 80-100% | Requests succeed but include a warning header |
| Over 100% | Requests rejected with 429 |
Warning (approaching limit)
HTTP/1.1 200 OK
X-J17-Limit-Warning: Approaching events_written limit: 800/1000
Watch for the X-J17-Limit-Warning header and alert before you hit the hard limit.
Hard limit (exceeded)
HTTP/1.1 429 Too Many Requests
X-J17-Limit-Exceeded: events_written: 1250/1000
{
"ok": false,
"error": "Limit exceeded",
"metric": "events_written",
"usage": 1250,
"limit": 1000,
"message": "You have exceeded your plan's events_written limit. Please upgrade your plan or wait until the next billing period."
}
Plan limits count monthly totals. Rate limits count per-minute bursts. They are independent — you can be within your monthly allocation but exceed your burst limit, or vice versa.
Read operations
Read operations (GET /:type/:id, GET /_projections/* on the worker) count against your instance's read rate limit but are not counted against your plan's event allocation. Only writes count toward plan limits.
Size limits
Separate from rate limits, j17 enforces payload size limits:
| Limit | Value |
|---|---|
| Max event data size | 100 KB |
| Max batch size | 10,000 events per request |
| Max spec size | 1 MB |
Exceeding these returns 413 Request Entity Too Large.
See also
- Writing events - Retry strategies for 429 responses
- Batch operations - Reduce request count with batching
- Authentication - API key scopes and environments