English · Русский
Error handling
See also: index.md (index) · developer-guide.md (quickstart, key issuance, request examples) · security-model.md (localhost, key model, audit) · versioning-and-deprecation.md (semver policy).
Every error response carries an ApiError JSON body:
{
"error": "forbidden",
"message": "Insufficient tier or missing scope",
"required": { "tier": 2, "scopes": ["task:control"] },
"current": { "tier": 0, "scopes": ["task:read"] }
}
required/current (each: tier + scopes) are only populated on 403; every other status omits
them (message alone, or null).
Status codes and machine-readable error codes
The table below is the complete set of error codes this version returns.
| HTTP status | error code |
Meaning | Where it comes from |
|---|---|---|---|
400 |
bad_request |
Malformed request (bad JSON, invalid parameter). | Any operation, request-shape validation. |
401 |
unauthorized |
Missing or invalid Authorization: Bearer <api-key> — no key, unknown key, or an expired/disabled one. |
Auth gate, every operation except ping. |
403 |
forbidden |
Key is valid but lacks the required scope, or the operation’s tier exceeds the key’s maxTier. Body includes required/current. |
Auth gate. |
404 |
not_found |
Three distinct origins, same code: (a) the path/verb resolves to no known operation; (b) on the ZennoPoster host, the operation was resolved and authorized, but the task, instance, tab or element it addresses doesn’t exist — e.g. GET /tasks/{id} with an unknown id; (c) on the ProjectMaker host, the action, variable, list, table or spreadsheet the call names doesn’t exist in the open project, or the file passed to POST /projects/open doesn’t exist. “No project is open at all” is not a 404 — see project_not_open. |
(a) Auth gate; (b) ZennoPoster/Instance domain handlers; (c) ProjectMaker domain handlers. |
409 |
project_not_open |
Every /projects/current/* operation on the ProjectMaker host needs an open project and the editor has none (e.g. right after POST /projects/current/close of the last tab in a race, or before any project was opened). Open or create a project first (POST /projects/open, POST /projects). |
ProjectMaker domain handlers. |
409 |
failed_precondition |
The open project is not in a state that allows the call: POST /projects/current/close with unsaved changes and no discardUnsavedChanges: true, or while the project is running/being debugged; POST /projects/current/save when the target file exists and the user declined to overwrite it. |
ProjectMaker domain handlers. |
404 |
session_not_found |
GET/POST /api/v1/sessions/{id} — no open WaitForUserAction window with that id. |
Sessions domain, ZennoPoster host only. |
409 |
no_active_interaction |
POST /sessions/{id}/complete targets a WaitForUserAction window that is no longer open — already completed, or the window/task closed. instance:interact calls do not return this: driving tabs/DOM needs no open session. |
Sessions domain, ZennoPoster host only. |
409 |
session_expired |
Declared in the contract for a session whose window closed before complete was called. Not returned by this version — treat it as reserved, not as a code you will actually see in v1. |
Sessions domain (reserved). |
409 |
dom_unavailable |
GET /instances/{id}/tabs/{tabId}/dom — the DOM text couldn’t be retrieved right now (e.g. the page is navigating). Retry rather than treat as permanent. |
Instance domain, ZennoPoster host only. |
409 |
instance_busy |
DELETE /instances/{id} — the port belongs to a running task’s worker thread and cannot be released via the API. |
Instance domain. |
409 |
instance_view_protected |
POST /instances/{id}/show — the browser’s view is protected (view protection enabled and no open WaitForUserAction window), so the window cannot be revealed. |
Instance domain. |
409 |
task_scheduler_owned |
DELETE /tasks/{id} — the task is owned by a scheduler job and cannot be deleted directly; delete the scheduler job instead. |
Tasks domain, ZennoPoster host only. |
413 |
payload_too_large |
Request body exceeds the host’s upload limit (default 2 GB — a safety cap, not something normal usage hits). Applies to any operation with a JSON body on the ZennoPoster host. | ZennoPoster host, global request-body guard (not operation-specific business logic). |
429 |
rate_limited |
Too many concurrent GET /sessions/events long-polls (per-host cap, default 32); also reserved for remote-mode rate limiting. |
Sessions events long-poll; remote/TLS mode (not yet enabled). |
500 |
internal_error |
Unhandled server-side failure. | Any operation. |
501 |
not_implemented |
The path/verb is a declared operation that isn’t wired to a handler on this host (e.g. the human-confirmation confirmations_* trio). |
Auth gate. |
503 |
service_unavailable |
The AI/PublicApi runtime master-switch is off — every call is refused before the key is even checked. | Auth gate, checked before key/operation detail. |
A note on 409
The auth gate itself never returns 409: it only ever produces 401 / 403 / 404 / 501.
409 is a domain-level status, returned only by the sessions/instance domain when the call is
well-authorized but the runtime context it needs (an open interaction window) isn’t there. Don’t conflate the two: a 403 means “your key can’t do this”; a 409 means
“your key can do this, but not right now”.
Handling checklist for an integrator
401— the key is missing, wrong, expired, or was revoked. Re-issue a key via the UI; there is no refresh flow (opaque keys, no token exchange).403— readrequiredvscurrentin the body and either request a key with the missing scope/tier, or don’t attempt the call. Don’t retry as-is; it will never succeed with the same key.404/409on the sessions/instance domain — these are expected, not exceptional: aWaitForUserActionwindow can close between yourGET /sessionslisting and yourPOST /sessions/{id}/completecall. Re-list and confirm the session is still open before retrying.429— back off; only relevant once remote mode ships (not in this release).501— the operation is declared in the contract but not wired on this host (isAvailable: falsein/capabilitiesfor thatoperationId); don’t call it.503— the AI/PublicApi master-switch is off host-side; nothing will succeed until it’s turned back on. Not something a client can work around.409 dom_unavailable— transient; retry the DOM-text call rather than treating it as a permanent failure.413— your request body exceeds the host’s upload limit; this is a safety cap (default 2 GB), not a normal-usage limit — if you hit it, something is likely wrong client-side.- Always check
GET /api/v1/capabilitiesfirst (see developer-guide.md) to avoid triggering403/501s you could have predicted client-side.