---
key: api-errors
title: Errors and how to recover
audience: developer
section: api/getting-started
routes: []
since: 0.0.1
updated: 0.0.1
status: published
summary: Every error a Nova integration or AI agent can get, the exact response it arrives in, and what to do next.
keywords: [errors, error codes, 403, 409, 429, approval_required, insufficient_scope, retry, troubleshooting]
related: [api-conventions, api-limits, wait-for-a-person-to-approve, api-scopes-reference, api-reference]
---

Every error arrives in the same envelope ([[api-conventions]]):
`{"error": {"code", "message", "request_id", "details"}, "detail": …}`.
Switch on `error.code` first. Where the table says **in `detail`**, the
specific reason is inside `detail` and `error.code` is the generic one.

## The codes

| Status | Where the reason is | What happened | What to do |
|---|---|---|---|
| `401` | `error.code: unauthorized` | Token missing, expired or revoked; API key revoked or expired | Refresh the OAuth token, or ask an admin for a new key |
| `403` | `error.code: forbidden` + `detail.missing_permissions` + `WWW-Authenticate: … error="insufficient_scope", scope="…"` | The credential was not granted a permission this needs | Ask for the named permission keys (see below) |
| `403` | `error.code: approval_required` | A person must approve this action: the organization listed it, or Nova guards it with an identity check | [[wait-for-a-person-to-approve]] |
| `403` | `error.code: phone_number_purchase_requires_approval` | Additional phone-number purchases need explicit Nova platform approval, including on full-access beta plans | Ask Nova support to approve purchases and the number allowance; do not retry purchasing automatically |
| `403` | **in `detail`**: `detail.error: agent_outside_working_hours` | An AI teammate tried to change something outside its working hours | Wait until `detail.next_window_starts_at`; reads still work |
| `401`, then `403` at `/oauth/token` | OAuth error `unauthorized_client` / `invalid_grant`, *"This agent is paused"* | The AI teammate was paused: its tokens were revoked and it cannot get a new one | Stop; a person has to resume it |
| `403` | `error.code: forbidden` | The route is not open to software (sign-in, OAuth, platform and credential-management routes) | Nothing to retry; use a person's session |
| `404` | `error.code: not_found` | No such record in this organization, or a uid of the wrong type | Check the uid and its prefix |
| `409` | `error.code: account_archived` / `task_archived` | The record is archived; reads work, changes do not | A person restores it (`details.cta` says how) |
| `409` | `error.code: idempotency_payload_mismatch` | Same `Idempotency-Key`, different body | Use a new key for a new request |
| `409` | `error.code: conflict` | The record changed under you, or is in a state that refuses this | Read it again, then decide |
| `409` | `error.code: email_message_not_in_deleted_items` | Permanent email deletion was requested while the message is outside Deleted Items | Move the message to Deleted Items first; permanent deletion is a separate action |
| `409` | `error.code: spend_parked` | A paid action (a text, an email) was stopped for a person to review | Nothing to retry; tell a person |
| `422` | `error.code: validation_error` | A field is missing or wrong; `details` names it | Fix the field |
| `429` | `error.code: rate_limit_exceeded`, `details.retry_after_seconds` | Per-minute limit, organization limit or daily cap | Wait `retry_after_seconds` ([[api-limits]]) |
| `429` | `error.code: spend_deferred` / `spend_hold` | The organization's daily budget for a paid action is used, or the provider is switched off | Retry after `details.retry_at`, or tell a person |
| `503` | `error.code: service_unavailable`, `Retry-After: 3` | Nova is briefly unavailable | Retry after a few seconds with backoff |

## A missing permission, in full

```http
HTTP/1.1 403 Forbidden
WWW-Authenticate: Bearer realm="nova", error="insufficient_scope", scope="billing.invoices.void"
```

```json
{
  "error": {"code": "forbidden",
            "message": "This credential does not have the permission this needs.",
            "request_id": "…"},
  "detail": {
    "missing_permissions": [
      {"permission": "billing.invoices.void",
       "label": "Void frozen billing invoices",
       "machine_access": "grant"}
    ],
    "documentation_url": "/help/api-scopes-reference/"
  }
}
```

`machine_access: grant` means the organization *can* give your credential
this permission: ask the admin who manages it to add the key. `never` means
no organization credential can hold it (platform and personal-profile
permissions). To see everything else that key unlocks, search the operation
index for it: [[find-what-to-call]].

> **Note:** read the missing keys from `detail.missing_permissions` or the
> `WWW-Authenticate` header. Do not rely on `detail.code`; it is not
> readable in this release.

## Outside working hours, in full

```json
{
  "error": {"code": "forbidden",
            "message": "This teammate is outside its working hours, so it can read but not change anything right now.",
            "request_id": "…"},
  "detail": {"error": "agent_outside_working_hours",
             "next_window_starts_at": "2026-09-29T13:00:00+00:00"}
}
```

## Retrying safely

- Retry only `429` and `503`, after the time the response gives you.
- Never retry `401`, `403`, `404`, `409` or `422` unchanged; they will fail
  the same way.
- Send an `Idempotency-Key` on creates that accept one, so a retry after a
  dropped connection cannot make a duplicate.
