Error codes
Every error response from the gateway has the same shape:
{
"error": {
"type": "<error_type>",
"message": "<human-readable description>",
"code": "<optional machine-readable subcode>",
"request_id": "req_01abc...",
"incident_id": "01H..."
}
}| Field | Always present | Meaning |
|---|---|---|
| type | Yes | Stable identifier for the class of error. Switch on this in code. |
| message | Yes | A human-readable description. Safe to surface to internal logs; don't rely on the exact text for control flow. |
| code | No | Optional, machine-readable subcode for finer-grained distinctions. |
| request_id | Almost always | The request's ID in our system. Include this when contacting support. |
| incident_id | Only on 403 request_blocked | The ID of a guard incident. Include this if you want a manual review. |
The full table
| Status | type | When you see it | What to do |
|---|---|---|---|
| 400 | invalid_request | Malformed body, missing required field, bare model name without a vendor prefix, unknown model. | Fix the request. The message will usually tell you exactly what's wrong. |
| 401 | unauthorized | Missing, malformed, or revoked API key. | Check that you're sending Authorization: Bearer unnma-sk-... and that the key is still active in the dashboard. |
| 402 | insufficient_funds | Your paid balance and trial credit are both zero. | Top up at app.unnma.ai/billing. Or enable auto-topup so this doesn't happen unattended. |
| 403 | forbidden | Your account is disabled, or the requested operation is not permitted on this key. | Check the dashboard for account status. If it's not clear, contact support with the request_id. |
| 403 | request_blocked | The prompt-extraction guard flagged the request. | See If your request was blocked below. |
| 404 | not_found | The endpoint doesn't exist, or the resource you queried isn't yours. | Check the URL. |
| 429 | rate_limit_exceeded | You exceeded a per-key rate limit, a per-account rate limit, or a daily spend cap. | Back off and retry after the Retry-After header. See Rate limits. |
| 500 | internal_error | Something failed on our side. | Retry with exponential backoff. If it persists, contact support with the request_id. |
| 502 / 503 | upstream_error | The underlying vendor returned an error or is unavailable. | Retry with backoff. If a specific vendor is consistently failing, route to another vendor by changing the model prefix. |
Recommended client behavior
Always retry with exponential backoff
429, 500, 502, and 503 are all retryable. Wait at least the Retry-After value on 429 (or 1 second if not present), and back off exponentially on subsequent failures. A reasonable default policy:
import time
import random
def retry_with_backoff(call, max_attempts=5):
for attempt in range(max_attempts):
try:
return call()
except RetryableError as exc:
if attempt == max_attempts - 1:
raise
wait = min(2 ** attempt + random.random(), 60)
if exc.retry_after_seconds:
wait = max(wait, exc.retry_after_seconds)
time.sleep(wait)Never retry on 400 or 401 or 404
These are client errors. The retry will just fail again. Fix the request, the key, or the URL.
Never retry on 402
A 402 insufficient_funds won't resolve until you top up. Surface the error to your user (or your monitoring), and route to top-up.
403 forbidden and 403 request_blocked are not retryable
A 403 means the gateway has made a deliberate decision. Retrying without changing anything will hit the same decision. Investigate before retrying.
If your request was blocked
If you get a 403 with type: "request_blocked", the prompt-extraction guard flagged your request. The error response includes an incident_id:
{
"error": {
"type": "request_blocked",
"message": "This request was blocked by our content guard.",
"request_id": "req_01abc...",
"incident_id": "01HXYZ..."
}
}The guard is tuned to catch prompt-extraction attempts and prompt-injection patterns. Most legitimate traffic clears it. If you believe a request was blocked inappropriately:
- Save the
incident_id. It's the only way to retrieve the specific request for review. - Email
support@unnma.aiwith theincident_idand a brief description of what you were trying to do. - We will review and respond. If the block was a false positive, we will work on the underlying calibration; if it was correct, we will explain why.
We default to caution on this. The guard is part of how we keep the platform usable for everyone.
A note on request_id
Every response — successful or failed — carries an x-unnma-request-id header. On errors, the same value is in the body as request_id. Log this for every request you make in production. When something goes wrong and you need our help, the request_id is the fastest path to a fix; with it, we can find your specific request in our logs and tell you exactly what happened.
Next
- Rate limits — what trips
429, and how to size your client. - Pricing — how to avoid
402in production.