Errors and rate limits
Status codes
Section titled “Status codes”| Code | Meaning | Retry? |
|---|---|---|
400 |
The request body or parameters failed validation | No — fix the request |
401 |
Missing, malformed, expired or revoked credential | No — unless a JWT expired, in which case refresh and retry once |
403 |
Valid credential, but not permitted — including some cross-tenant access | No |
404 |
Not found — or it exists in another tenant | No |
409 |
Conflict with current state | No — re-read, then decide |
410 |
The endpoint has been removed; the body names its replacement | No — migrate |
429 |
Rate limited | Yes, after a backoff |
500 |
Unexpected server error | Cautiously, with backoff |
502 / 503 / 504 |
A downstream service is unavailable or slow | Yes, with backoff |
Both 403 and 404 can mean “this belongs to another tenant” — which one you get depends on the
service. See Tenants and isolation for where each applies.
410 is worth handling explicitly rather than treating as a generic failure: it is how removed
endpoints announce their replacement, and the detail field tells you what to call instead.
Error bodies
Section titled “Error bodies”There are two shapes, and you need to handle both.
Validation failures produced by the framework follow RFC 7807:
{ "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1", "title": "One or more validation errors occurred.", "status": 400, "errors": { "Username": ["Username is required"] }, "traceId": "00-206989bb1217af34b7708b0489f85205-..."}Errors raised deliberately by application code are flatter, and carry no status and no
errors:
{ "error": "The model 'gpt-5' is not available on this server."}The LLM service uses a third shape for its own validation failures:
{ "code": "VALIDATION_ERROR", "message": "...", "validationErrors": { }, "requestId": "...", "timestamp": "..."}So: branch on the HTTP status code, not on the body shape. When you need to surface a reason to
a human, look for errors, then error, then message, then title, and fall back to the status.
Never assume status is present in the body — in the most common failure shape it is not.
Correlation IDs
Section titled “Correlation IDs”Every response carries X-Correlation-ID. Log it. When you report a problem it is what lets the
request be traced through the Gateway and every service that handled it, and without one an
investigation usually starts from nothing.
You may also send your own X-Correlation-ID and it will be preserved, which is useful for
stitching platform activity into your own tracing.
Rate limits
Section titled “Rate limits”
Authenticated traffic is not rate limited today, and returns no rate-limit headers.
The Gateway skips its limiter entirely for requests carrying a valid API key or JWT, which means
the limits and headers below apply only to anonymous traffic. Per-key quotas are planned, and
when they arrive this will change without warning to your code — so build as though they already
apply. Handle 429, and back off. Software that assumes unlimited throughput will
break; software that already backs off will not notice.
The limits that apply to anonymous traffic, as deployed:
| Path | Per minute | Per hour |
|---|---|---|
/api/llm/* |
300 | 3,000 |
/api/ml/* |
200 | 2,000 |
/api/ai/* |
300 | 3,000 |
/api/vector/*, /api/vectordb/* |
400 | 4,000 |
/api/public/* |
1,000 | 10,000 |
/api/workspaces/* |
1,200 | 12,000 |
/api/auth/* |
1,500 | 15,000 |
| Everything else | 3,000 | 30,000 |
/api/models* and /health are exempt from rate limiting altogether.
Inference limits are the tightest relative to their cost because each request occupies a compute node for far longer than an ordinary API call.
When a response is rate limited, it carries the current state:
X-RateLimit-Limit-Minute: 300X-RateLimit-Remaining-Minute: 187X-RateLimit-Reset-Minute: 1769558400The reset headers are Unix timestamps in seconds. When present, wait until the reset rather than retrying straight away — retrying immediately just consumes the next window. When absent, which is the normal case for an authenticated integration, fall back to exponential backoff.
Retrying safely
Section titled “Retrying safely”Use exponential backoff with jitter, and cap the attempts. Retrying a 500 in a tight loop turns one
failure into an outage.
Only GET and DELETE are safe to retry blindly. A POST that timed out may already have been
processed — the response was lost, not the work. If duplicates would matter, make the operation
idempotent on your side before retrying.
Timeouts
Section titled “Timeouts”The Gateway applies per-route timeouts. Inference is allowed far longer than a typical call, since generation legitimately takes time; most other routes are expected to answer quickly. Model installation is longer still and is best treated as a background job rather than a request you wait on. Set your own client timeout above the platform’s for the routes you use, or you will abandon requests that would have succeeded.