Every error uses OpenAI's format:
{
"error": {
"message": "API key is missing the \"images\" scope (it has: chat, responses, embeddings). Grant it on the key, or create a new key with this scope.",
"type": "permission_error",
"code": "developer_api_scope_denied",
"param": null,
"detail": {
"requiredScope": "images",
"grantedScopes": ["chat", "responses", "embeddings"]
}
}
}
message and detail first. On 4xx responses those two fields say exactly what to do
next: which scope is missing, which task to poll, which parameter is wrong. Don't guess from code
alone.code | HTTP | Meaning |
|---|---|---|
invalid_api_key | 401 | Wrong or revoked key. Check you copied all of it |
api_key_disabled | 401 | The key was disabled |
api_key_expired | 401 | The key expired |
api_key_ip_not_allowed | 403 | Your egress IP is not on this key's IP allowlist |
developer_api_scope_denied | 403 | The key lacks the scope this endpoint needs — see below |
model_not_allowed | 403 | The key has a model allowlist that excludes this model |
model_not_found | 404 | No such model, or your account cannot use it. Call GET /v1/models |
insufficient_credits | 403 | Not enough balance. detail carries the amount needed and your balance |
model_pricing_required | 503 | The model has no price configured — contact an administrator |
rate_limit_exceeded | 429 | A rate or quota limit was hit; back off and retry |
daily_spend_limit_exceeded | 429 | This key's daily spend cap was reached |
monthly_spend_limit_exceeded | 429 | This key's monthly spend cap was reached |
image_generation_timeout | 504 | Image wait timed out, but the task is still running — use detail.taskId |
upstream_unavailable | 502 / 503 | The upstream model provider failed; retryable |
bad_request | 400 | Bad parameters; message names the field |
Each key carries a set of scopes that decide which endpoints it can reach:
| Scope | Endpoints |
|---|---|
chat | /v1/chat/completions |
responses | /v1/responses |
embeddings | /v1/embeddings |
images | /v1/images/generations, /v1/tasks (images) |
videos | /v1/videos, /v1/tasks (video) |
New keys get all of the above by default. The audio scope has no endpoint behind it yet.
GET /v1/models is filtered by scope too — a chat-only key won't see image models in the list. So
"my model isn't in the list" is usually a scope problem, not a missing model.Each key can carry its own:
Individual models may also carry daily quotas (request counts, image counts). Hitting one returns
429 rate_limit_exceeded; the message says which limit it was.
Calls are charged against your account balance — the same balance the built-in AI uses in the BotHub apps.
Incoming requests reserve a deposit first, then settle against actual usage once finished; anything beyond the deposit is still charged. That's why the balance sometimes dips and then partially returns — it is working as intended.
When the balance is short you get insufficient_credits, with the required amount and your current
balance (in cents) in detail:
{
"error": {
"code": "insufficient_credits",
"detail": { "requiredFen": 320, "balanceFen": 95 }
}
}
The easiest place to check usage is the same screen where you created the key — "External Access" in the app lists each key's request count, token count and spend over the last 7 days.
Work through this list — the first two steps catch most problems:
GET /v1/models work?endpoints field — an image model will never
answer on /v1/chat/completionsmessage: it says whether the missing piece is a scope, the IP allowlist, or
balancemessage says which. Back
off and retrydetail.taskId via
GET /v1/tasks/{id}, and raise your client timeout