Skip to content

Errors and rate limits

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.

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.

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.

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: 300
X-RateLimit-Remaining-Minute: 187
X-RateLimit-Reset-Minute: 1769558400

The 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.

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.

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.