Agent Error Codes
Every error the agent, session, file and webhook routes raise is a CognipeerAPIError. Branch on its errorCode — the machine-readable code of the response body — not on the message text.
New in 3.1.0
AgentActionErrorCodes, isAgentActionErrorCode(), and a fix to how errorCode is read: when error is just the message string ({ error: 'Webhook not found', code: 'not_found' }, the shape of the webhook routes), the top-level code is used, so not_found, forbidden, limit_reached, endpoint_disabled and delivery_pending are no longer lost. error.code still wins when both are present.
The error object
class CognipeerError extends Error {
statusCode?: number; // HTTP status
response?: unknown; // the parsed response body
}
class CognipeerAPIError extends CognipeerError {
errorType?: string; // the body's error.type
errorCode?: string; // the body's error.code (or a top-level `code`) — branch on this
retryAfterMs?: number; // parsed Retry-After, in milliseconds, when present
}The agent routes answer { "error": { "message", "type", "code", ...extra } } with snake_case extras; the webhook routes answer { "error": "<message>", "code": "<code>" }. Extra fields live in error.response:
import { CognipeerAPIError, AgentActionErrorCodes, isAgentActionErrorCode } from '@cognipeer/console-sdk';
try {
await client.agents.sessions.actions.resolve(conversationId, actionId, { decision: 'allow' });
} catch (error) {
if (error instanceof CognipeerAPIError) {
console.log(error.statusCode, error.errorCode, error.message);
const body = error.response as { error?: { action_status?: string } };
console.log(body.error?.action_status); // 'resolved' | 'cancelled' | 'expired'
}
if (isAgentActionErrorCode(error, AgentActionErrorCodes.NotPending)) { /* … */ }
}Helpers
| Helper | Use |
|---|---|
isAgentRunErrorCode(error, AgentRunErrorCodes.X) | Run codes (conflict, concurrency, idempotency, timeout) |
isAgentActionErrorCode(error, AgentActionErrorCodes.X) | Session, approval, file and budget codes |
error.errorCode === 'agent_not_found' | Codes without a constant (listed below) |
AgentRunErrorCodes: Conflict, AlreadyTerminal, ConcurrencyLimit, SyncTimeout, IdempotencyKeyConflict, IdempotencyKeySyncNotSupported.
AgentActionErrorCodes: SessionNotFound, SessionConflict, StorageUnavailable, StorageNotConfigured, AttachmentsDisabled, BucketInUse, NotFound, NotPending, ActionCancelled, Expired, Forbidden, AuthorizationUnavailable, MarkdownUnavailable, InvalidResolution, PendingAction, BudgetOverrideForbidden, InvalidBudget, BackgroundDisabled, BudgetReached.
Runs and Responses
| Status | errorCode | Meaning |
|---|---|---|
| 409 | agent_run_conflict | A run is already queued/running on this conversation (sync or background), including when you delete a session or update its budget during a run |
| 504 | timeout | A synchronous turn exceeded the server ceiling. Tool side effects may have happened, so the SDK does not retry it (retryable: false). Use background: true |
| 429 | agent_run_concurrency_limit | The tenant's concurrent background-run limit is reached |
| 409 | idempotency_key_conflict | Idempotency-Key reused with a different request |
| 400 | idempotency_key_sync_not_supported | Idempotency-Key sent without background: true |
| 409 | agent_run_already_terminal | runs.cancel() on a finished run |
| 400 | agent_background_disabled | background: true, a budget resume or actions.resolve({ background }) on an agent with background execution disabled |
| 404 | agent_not_found | No agent with that key in the token's project |
| 400 | agent_not_active | sessions.create() for an agent that is not active |
| 400 | invalid_request | A malformed request: for example session_id and conversation naming two different sessions, or agent missing from sessions.create() |
Sessions
| Status | errorCode | Meaning |
|---|---|---|
| 404 | session_not_found | No such conversation in this project — also a session_id / conversation of another agent or project |
| 400 | session_conflict | session_id / conversation and previous_response_id of one request name different sessions (session_id vs conversation alone is 400 invalid_request) |
| 400 | invalid_metadata | metadata is not up to 16 string pairs (keys 1–64 characters, values up to 512) |
| 400 | invalid_limit | sessions.list({ limit }) is not a whole number from 1 to 100 |
| 400 | invalid_status | sessions.list({ status }) is not idle, running, requires_action or budget_paused |
| 400 | invalid_cursor | sessions.list({ after }) is not a cursor the API returned |
| 409 | agent_run_conflict | sessions.delete() or sessions.update() while a run is active |
Budgets
| Status | errorCode | Meaning |
|---|---|---|
| 400 | invalid_budget | The budget is malformed, missing from sessions.update(), or not above what the session already spent |
| 403 | budget_override_forbidden | The agent's own session budget binds API callers: they may lower it, not raise or remove it |
| 402 | budget_reached | The session used up its budget; a new message is refused. error.response.error.spent_usd / max_cost_usd say how much. Raise it with sessions.update() |
Approvals and questions
| Status | errorCode | Meaning |
|---|---|---|
| 404 | action_not_found | No such action in this conversation |
| 409 | action_not_pending | The request is no longer open because it was answered already. error.response.error.action_status carries the final state |
| 409 | action_cancelled | Cancelled before it was answered (a new message withdrew it, a run cancel, the agent's timeout policy). action_status is cancelled |
| 410 | action_expired | The approval request ran out of time |
| 403 | action_forbidden | This caller may not answer (the Console's agent.approve permission, or the agent's approvers setting on a Console without the permission engine) |
| 503 | authorization_unavailable | The Console could not decide whether this caller may answer (nothing changed; retry after Retry-After) |
| 400 | invalid_resolution | The answer does not fit the action (bad decision, answers, approved_args) |
| 409 | pending_action | A new message was sent while approvals/questions are open. error.response.error.pending_action_ids lists them |
Files
| Status | errorCode | Meaning |
|---|---|---|
| 403 | attachments_disabled | The agent takes no files: off for it, or on with no bucket chosen (agents.get(key).attachments.enabled says so in advance). Also raised by responses.create() with input_file / input_image parts |
| 409 | storage_unavailable | The agent's bucket cannot take a file now (deleted, disabled, or its provider is gone, inactive or not assigned to the project). The message names the bucket |
| 409 | storage_not_configured | Files are on but no bucket was chosen (older data); pick one in the agent's Files settings |
| 409 | too_many_files | The session already holds the agent's maximum number of files |
| 413 | file_too_large | Over the agent's file size limit |
| 415 | unsupported_file_type | Type not in the agent's allowed list |
| 429 | quota_exceeded | The tenant's rate or storage quota |
| 404 | file_not_found | The file is not on this session (or was deleted or expired) |
| 409 | markdown_unavailable | files.content(…, { variant: 'markdown' }) on a file without a text version (a picture, or a conversion that did not succeed) |
| 409 | bucket_in_use | From the Files management API: a bucket cannot be deleted or disabled while an agent keeps session files in it (error.response.agents lists them). No call of this SDK raises it |
Webhooks
| Status | errorCode | Meaning |
|---|---|---|
| 403 | forbidden | Missing webhooks.read / webhooks.admin, or creating for a project that is not the token's. error.response.decisionId names the decision |
| 404 | not_found | No such endpoint or delivery |
| 409 | limit_reached | 50 endpoints per tenant |
| 409 | endpoint_disabled | redeliver on a disabled endpoint |
| 409 | delivery_pending | redeliver on a delivery that is already queued |
| 503 | authorization_unavailable | The permission engine could not decide; retry — never an allow |
What the SDK retries on its own
The HTTP client retries 429, 502, 503 and 504 up to maxRetries (default 3) with exponential backoff, honouring Retry-After up to 30 seconds; a longer Retry-After is thrown with retryAfterMs set so you can reschedule. A server can veto the retry with { error: { retryable: false } } — that is why a synchronous timeout (504) is not retried: re-sending a turn whose tools may already have run would repeat the side effects.
Other statuses — 400, 401, 402, 403, 404, 409, 410, 413, 415 — are thrown at once.
Which errors can I fix by retrying?
| Retry | Do not retry |
|---|---|
429 (agent_run_concurrency_limit, quota_exceeded) after a pause, 503 authorization_unavailable, 502/503/504 | 400 (fix the request), 402 budget_reached (raise the budget), 403, 404, 409 action_not_pending / action_cancelled, 410 action_expired, 504 timeout (switch to background: true) |
409 agent_run_conflict once the active run ends | 409 storage_unavailable until the bucket is fixed |

