> For clean Markdown content of this page, append .md to this URL. For the complete documentation index, see https://docs.postnuvia.com/llms.txt.

# Error Reference

> Every PostNuvia API error code, what it means, and how to fix it.

Every error response from the PostNuvia API includes a stable, machine-readable `code` you can branch on, a `message` describing what went wrong, and a `docs` link pointing to the matching entry on this page. Most responses also include a `fix` describing the concrete next action that resolves it; `fix` is omitted when no generic remedy applies.

```json
{
  "name": "ForbiddenError",
  "code": "missing_permission",
  "message": "Forbidden",
  "fix": "This API key does not have the 'message_send' permission. Retry with an API key that includes 'message_send' — note a key cannot create a new key with permissions it lacks itself, so the new key must come from a credential that already holds 'message_send' (an unrestricted key via POST /v0/api-keys, or the console at https://console.postnuvia.com). If no such key exists, the permission may be outside your credential's scope — use a key with a broader scope (organization-scoped keys hold the widest permissions).",
  "docs": "https://docs.postnuvia.com/errors#missing_permission"
}
```

Branch on `code`, not on `name` or `message`: the `name` and `message` fields keep their long-standing pre-`code` values for backward compatibility (a permission denial still reads `Forbidden`), so they neither identify the specific cause nor carry the remedy — `code` and `fix` do.

## Authentication (401)

### missing\_authorization

The `Authorization` header was missing, did not use the required case-sensitive `Bearer ` scheme, used the scheme correctly but carried no key value after `Bearer `, or carried whitespace inside the key value (no credential contains whitespace) — the message states which. Send your API key as `Authorization: Bearer <api_key>`. Create a key at [console.postnuvia.com](https://console.postnuvia.com) or via `POST /v0/api-keys`.

### invalid\_token\_type

The endpoint requires an API key, but the request carried a console session token (JWT). Authenticate with an API key instead: keys start with `am_`. Create one at [console.postnuvia.com](https://console.postnuvia.com) or via `POST /v0/api-keys`.

### unknown\_api\_key

The API key was not recognized. Confirm you copied the full value (keys start with `am_`) and that the key has not been revoked. Create a new key at [console.postnuvia.com](https://console.postnuvia.com) or via `POST /v0/api-keys`.

### api\_key\_expired

The API key is valid but its `expires_at` has passed, so it no longer authenticates. Expiry is set when a key is created and cannot be extended or changed. Create a new key at [console.postnuvia.com](https://console.postnuvia.com) or via `POST /v0/api-keys`, choosing its lifetime with `expires_at`, and retry with it. Depending on how the request is routed, an expired key can instead be refused with a bare `403` whose body is `{"message":"Forbidden"}`; the remedy is the same.

### unauthorized

Generic authentication failure. Send a valid API key in the `Authorization: Bearer <api_key>` header. Create or rotate keys at [console.postnuvia.com](https://console.postnuvia.com) or via `POST /v0/api-keys`.

## Authorization (403)

### missing\_permission

The credential lacks a specific required permission (for example `message_send`). For backward compatibility the `message` stays `Forbidden` (event-type permission checks on webhooks and WebSocket subscriptions keep their own legacy form, `<permission> is forbidden`); the missing permission is named in the `fix`. The `fix` also names the remedy for the actual cause: a restricted key may need the permission granted (retry with a key that includes it, or create one via `POST /v0/api-keys` from a credential that already holds it — a key cannot grant a permission it lacks), while other denials come from the credential's scope or the organization's state, where creating another key at the same scope cannot help — for example `organization_read` requires an organization-scoped key, some permissions (like creating API keys, send-list entries, or pods) require completing agent verification via `POST /v0/agent/verify` first (an agent that signed up without a human email attaches one with `POST /v0/agent/human` before it can verify), and `agent_verify` itself is only available while an unverified agent organization still needs verification.

### permission\_escalation

A create or update API key request asked for a permission the calling key does not hold itself. A key cannot grant more than it has. Remove the extra permissions from the request, or retry with a key that already holds them.

### unrestricted\_key\_required

Creating an unrestricted child key (`permissions: null`), clearing a key's permissions, or changing the permissions of a currently-unrestricted key requires an unrestricted credential: a dashboard session or an API key stored without a permissions attribute. Retry with one; the `fix` on the response says which gate fired.

### forbidden

The authenticated API key is not allowed to perform this action, usually because of its scope (organization, pod, or inbox). Retry with a key whose scope and permissions cover this resource. Calendar endpoints also return `forbidden` when the organization does not have calendar access (private beta); no key can fix that, so email [support@postnuvia.com](mailto:support@postnuvia.com) to request access.

## Request errors (400 / 404 / 422)

### validation\_error

One or more request fields failed validation. Inspect the `errors` array in the response: each entry has a `path` and a `message` identifying the invalid field. Correct the offending fields and resend. Two common cases carry field-specific messages: an invalid `page_token` (restart pagination by omitting `page_token`, then follow `next_page_token`) and a malformed `Idempotency-Key` header.

### not\_found

No resource with the given identifier is visible to this credential. That can mean the ID is wrong — but the same response also deliberately hides resources outside your credential's scope (organization, pod, or inbox) and resources behind a restricted-label read permission you lack (`spam`, `blocked`, `unauthenticated`, `trash`), so a correct ID can still return `not_found`. Check the ID, your credential's scope, and your label-read permissions; list endpoints return only the IDs visible to you.

### unprocessable

The request was well-formed but cannot be processed as-is (for example, a send with no recipients in `to`, `cc`, or `bcc`). Adjust the request per the message and retry.

### query\_range\_too\_wide

A metrics query requested too wide a time range, or a calendar list asked for an `after` to `before` window wider than 366 days (or one holding too many dates of a recurring event). Narrow the requested time range, or increase the metrics period/bucket size, then retry.

## Resource state (403 / 409)

### already\_exists

A resource with these details already exists. Fetch or update the existing resource instead of creating a duplicate.

### resource\_taken

The requested value (for example, an inbox username) is already in use. Choose a different value and retry.

### limit\_exceeded

A resource limit has been reached (for example, the webhook endpoint limit). Remove an existing resource before adding another, or email [support@postnuvia.com](mailto:support@postnuvia.com) to raise your limit. For agent-verification OTP attempts, the `fix` distinguishes the two states: while the exhausted code is still live, every submission is rejected (even the correct code) until it expires 24 hours from issue, after which `POST /v0/agent/sign-up` issues a fresh code with a reset attempt count; if the code has already expired, sign up again immediately.

### domain\_not\_verified

The sending domain has not completed DNS verification. Add the DNS records returned by `GET /v0/domains/{domain}`, then call `POST /v0/domains/{domain}/verify` before sending from an address on this domain.

### conflict

The request conflicts with another request under the same `Idempotency-Key`. Either the original send is still in progress — wait briefly and retry the identical request: once the first attempt completes the retry returns its send, and if the first attempt failed without completing, the key becomes retryable again after a short window — or the key was already used for a different message (generate a new key for new messages). See [Idempotency](/idempotency).

### race\_condition

A concurrent modification conflicted with your request. Re-fetch the resource to get its latest state, then retry. A calendar change sent without `If-Match` returns this when the resource changed while the request ran. A calendar event create also returns this when the calendar is at its event limit, where retrying cannot help: check `event_count` with Get Calendar.

### resource\_deleting

The resource is currently being deleted and cannot be used. Wait for deletion to finish, or use a different resource.

### cannot\_delete

The resource cannot be deleted yet, usually because dependent resources still exist. Resolve the blocker described in the message, then retry.

## Calendar (409 / 410 / 412 / 428)

These codes come from the [calendar API](/calendar). See [Concurrency, Limits & Errors](/calendar-reference) for how ETags and retries work.

### precondition\_required

The request needs an `If-Match` header and has none (`428`). Read the resource, copy its `etag` into `If-Match`, and retry. Public calendar endpoints accept requests without `If-Match`.

### precondition\_failed

The resource changed since the version in your `If-Match` was read (`412`). The body includes `current_revision`. Read the resource again, reapply your change and retry with its new `etag`.

### idempotency\_conflict

The `client_id` on a create was already used for a different event request, or a delete's `Idempotency-Key` was already used to delete a different event (`409`). Delete keys are unique across your organization. Reuse a `client_id` or key only to retry the identical request, or choose a new one.

### event\_starting

The event or date is starting at this moment, so its schedule cannot change for a few seconds (`409`). Retry shortly. Once it has started, its end and status can still change.

### event\_already\_started

The event or date has already started, so its start can no longer change, or it has ended, so only its title, description, location, metadata and attendees can change (`409`). Send the unchanged start with a new end, or change only the status, or apply a wider change from a later date.

### event\_in\_progress

The change conflicts with edits already made (`409`): a recurring event with edited dates cannot become a one-off event, or a date first edited with one `mode` was changed with the other. Create a replacement event, or repeat the change with the date's original `mode`.

### future\_edit\_start\_move\_not\_supported

A `mode=future` change tried to move `start` (`409`). Use `mode=single` to move one date, or update the series to move every date.

### recurrence\_density\_limit

The recurrence produces more than 730 dates in some 90-day window (`409`). Increase `INTERVAL`, add `COUNT` or `UNTIL`, remove extra dates, or use a lower `FREQ`.

### materialization\_failed

The recurrence's first date could not be found within the evaluation bound (`409`). Add `COUNT` or `UNTIL`, or simplify the rule.

### page\_token\_stale

The recurring event changed while you were paging through its dates (`409`). Restart the list without `page_token`.

### calendar\_response\_invalid

The calendar cannot do this for the event (`409`): only events the inbox organizes can send invitations or cancellations, and only invitations the inbox received (where it is an attendee) can be responded to.

### calendar\_inbox\_required

Sending invitations or cancellations needs the inbox the calendar belongs to (`409`). Every calendar is linked to its inbox, so this is not expected in normal use. If you see it, email [support@postnuvia.com](mailto:support@postnuvia.com); retrying with `send_invites` set to `false` applies the change without emailing attendees.

### event\_expired

The date ended more than 30 days ago and its record is no longer kept, or it was removed from its series (`410`). It can no longer be read or changed.

## Sending (403)

### message\_rejected

The message was not sent. The message names the reason, and the `fix` states which case applies:

* **Recipients on a send block list** — remove them from the request, or delete the matched block entry. The `fix` names the stored entry (which may be a domain rather than the recipient address) and the exact `DELETE /v0/…/lists/send/block/{entry}` path at the scope where it lives. Deleting an entry at a broader scope than your key (an organization-level block hit by a pod- or inbox-scoped key) requires a key scoped at that level. Read-only entries — added automatically from bounces, complaints, and unsubscribes — cannot be deleted via the API; email [support@postnuvia.com](mailto:support@postnuvia.com) to have one reviewed.
* **Recipients missing from an active send allow list** — add them via `POST /v0/lists/send/allow` (there is no block entry to delete). On an agent organization that has not completed verification, sending is restricted to the human's email and list entries cannot be created yet — complete `POST /v0/agent/verify` instead. If no human email is attached yet, sending is blocked for every recipient until one is attached with `POST /v0/agent/human`.
* **A suspended account** — email [support@postnuvia.com](mailto:support@postnuvia.com); retrying will keep failing until the suspension is resolved.
* **An unfetchable attachment URL** — use a URL that PostNuvia can download without custom authentication headers or cookies, or send the attachment inline as Base64. Redirects and pre-signed URLs are supported, and the final response must be a successful 2xx response.

### inbox\_paused

The inbox is paused, so it cannot send. Resume it with `PATCH /v0/inboxes/{inbox_id}` and `{"status": "active"}` using a key with the `inbox_update` permission, then send again. Mail that arrived while the inbox was paused was dropped and is not delivered on resume. See [Pausing an inbox](/inboxes#pausing-an-inbox).

If the inbox has also been suspended for its bounce or complaint rate, the send reports that suspension instead. A suspension of the inbox's pod is reported only after the inbox is resumed. Resuming the inbox does not lift either suspension: email [support@postnuvia.com](mailto:support@postnuvia.com).

## Request too large (413)

Oversized inline attachment requests return an infrastructure-generated response:

```json
{ "message": "Request Entity Too Large" }
```

This response is an exception to the standard `code`, `fix`, and `docs` error envelope. It only contains `message`.

Requests with inline `content` attachments are limited to 6 MB total, including the message body, metadata, and attachment content.

Reduce the attachment size or provide larger attachments through the `url` field. Keep URL-backed attachments around 30 MB total per message.

### payload\_too\_large

A calendar request body is larger than its limit: 128 KB for events, 8 KB for calendar settings. Unlike the response above, this one uses the standard error envelope. Shorten the description or metadata and retry.

## Transient errors (429 / 500 / 503)

### rate\_limit\_exceeded

You are sending requests too quickly or have hit a usage limit, including the send limits that calendar invitation emails count toward. Honor the `Retry-After` header when present, and retry with exponential backoff. A calendar request rejected for its invitation emails changed nothing; retry it later, or without `send_invites`.

### service\_unavailable

A downstream service is temporarily unavailable. Retry the request after a short delay with exponential backoff.

### internal\_error

A server-side error, not a problem with your request. Retry with exponential backoff; if it persists, email [support@postnuvia.com](mailto:support@postnuvia.com).