Rate Limiting
Every request is subject to a rate limit. The limits are the same for every company and depend only on the kind of request: read, write, or ingest. Each kind is tracked in its own bucket, so telemetry ingestion never competes with your interactive API calls.
Limits
| Request kind | Methods / path | Limit |
|---|---|---|
| Reads | GET, HEAD, OPTIONS | 600 per minute |
| Writes | POST, PUT, PATCH, DELETE | 120 per minute |
| Ingest | POST /api/v1/ingest | 600 per minute |
Limits apply over a rolling one-minute window.
Who shares a budget
| Requests | Counted per |
|---|---|
| Signed in with a Bearer JWT, or sent with a service-account API key | Your company. Everyone in the company shares one budget per request kind |
Ingest (POST /api/v1/ingest) and the edge-agent heartbeat (/api/v1/agent/heartbeat) | Network address. Edge agents and scripts behind the same plant address share its budget |
| Anything else made without a credential | Network address |
The authentication endpoints under /api/v1/auth/ and the /health check are not counted against these limits. Sign-in has its own protection instead: 10 failed attempts within 10 minutes lock the account for 15 minutes, and further attempts return 429 until the lock lifts.
Additional limits
Two kinds of request are expensive to serve, so they also have a tighter cap of 10 requests per minute per company, on top of the read limit above:
- Report downloads:
GET /api/v1/reports/fleet-summary,/oee,/maintenance,/downtime, and/work-orders, and running a saved report withPOST /api/v1/reports/templates/{template_id}/run. - AI narratives:
GET /api/v1/machines/{machine_id}/prediction/narrative.
Over the cap these return 429 with Retry-After: 60.
Response headers
Responses within the limit carry the current budget for their bucket:
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 597
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests allowed in the current window |
X-RateLimit-Remaining | Requests remaining in the current window |
When you are rate limited
Exceeding a limit returns 429 Too Many Requests with a Retry-After header and a JSON body:
HTTP/1.1 429 Too Many Requests
Retry-After: 12
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1754558400
{
"detail": "Rate limit exceeded. Please retry after the indicated period.",
"retry_after": 12
}
| Header | Description |
|---|---|
Retry-After | Seconds to wait before the next request will be accepted |
X-RateLimit-Reset | Unix timestamp when the window resets |
Best practices
Respect Retry-After. On a 429, wait the indicated number of seconds before retrying rather than retrying immediately.
Back off exponentially. For automated clients, add exponential backoff with jitter on repeated 429 responses.
import time
import random
def request_with_backoff(make_request, max_retries=5):
for _ in range(max_retries):
response = make_request()
if response.status_code != 429:
return response
retry_after = int(response.headers.get("Retry-After", 5))
time.sleep(retry_after + random.uniform(0, retry_after * 0.1))
raise RuntimeError("Max retries exceeded")
Batch your telemetry. Send up to 1,000 readings in a single POST /api/v1/ingest request instead of one request per reading. See Sensor Data.
Watch X-RateLimit-Remaining. Throttle your client proactively as the remaining count approaches zero.