Errors
Every failure returns the same JSON shape, whatever went wrong — including a mistyped URL. Your client only ever has to parse one thing.
The shape
{
"error": {
"code": "invalid_api_key",
"message": "The provided API key is not valid.",
"traceId": "0HN7A2QK9V1M4:00000003"
}
}Branch on code, never on message
code is a frozen contract: a new code may be added, but an existing one is never renamed or repurposed. message is human-readable text that may be reworded at any time, and traceId identifies the request in our logs — quote it when contacting support.
Every code
| Code | Status | Meaning |
|---|---|---|
| missing_api_key | 401 | No X-API-Key header was sent. |
| invalid_api_key | 401 | The key is not recognised. |
| expired_api_key | 401 | The key passed its expiry date. |
| revoked_api_key | 401 | The key was revoked in the Nubo portal. |
| invalid_request | 400 | A parameter is missing, malformed or out of range. |
| plan_limit | 403 | A plan or account limit was reached. |
| project_not_found | 404 | No such project in this key's workspace. |
| session_not_found | 404 | No such session in this key's workspace. |
| rate_limited | 429 | Too many requests. See the Retry-After header. |
| internal_error | 500 | Something failed on our side. Retry, and quote the traceId. |
A 404 does not distinguish “no such id” from “not in your workspace”. Check the id against GET /v1/projects first.
What to retry
| Status | Retry? |
|---|---|
| 400, 401, 403, 404 | No — the request will fail identically until you change something. Fix the parameters or the key. |
| 429 | Yes, after the Retry-After header says to. |
| 500 | Yes, with exponential backoff. If it persists, quote the traceId to support. |
A slow first request is not an error
After a quiet period the first call can take up to a minute while the database resumes. That is a slow success, not a failure — set a generous client timeout so you do not abandon a request that was about to succeed.