# Errors

> Every error the Revnu API returns, the JSON body each one carries, and whether to retry, fix the request or re-read the card.

Canonical: https://revnu.com/docs/errors
Last updated: 2026-09-29

Every error is JSON with an `error` field, sent with `Cache-Control: no-store`. Some carry one more field that tells you what to do next.

```json
{ "error": "forbidden", "requiredScope": "review:write" }
```

| Status | `error` | Extra fields | What to do |
|---|---|---|---|
| `400` | A sentence naming the problem, such as `limit must be a positive integer` | none | Fix the request. Retrying it unchanged gets the same answer. |
| `401` | `unauthorized` | none | The token is missing, malformed, unknown or revoked. Mint a new one. |
| `403` | `forbidden` | `requiredScope` | The token lacks that scope. Mint a token that has it. |
| `404` | `not found` | none | The id does not exist or belongs to another account. |
| `409` | `invalid_action` | `actions` | The card no longer accepts that verb. Re-render from `actions`. |
| `409` | `use_send` | `message`, `actions` | A Gmail card was sent to `/answer`. Use `POST /review/{id}/send`. |
| `409` | `rejected` | `message` | A settled refusal, such as a card that was already handled. |
| `409` | `this address unsubscribed and cannot be unblocked` | none | The address opted out, so it stays on the block list. |
| `429` | `rate limited` | `Retry-After` header | Wait that many seconds, then retry. |
| `500` | `server misconfigured` | none | Our fault. Retrying will not help until we fix it. |
| `503` | `temporarily unavailable` | `Retry-After` header | Infrastructure, not your request. Retry the same call unchanged. |
| `503` | `delivery_pending`, `unavailable` | `message`, `Retry-After` header | Gmail sends only: delivery is still being confirmed, or the mail service is down. Retry the same send. |

## 400: the request is wrong

The message is written to be shown to a developer, so log it. Common ones: `body must be JSON` (the body is not a JSON object), `cursor is not valid` (pass back the `nextCursor` you were given, unmodified), `limit must be a positive integer`, `text required`. Unknown fields on a workflow write are also a `400`, so a typo in a field name fails loudly instead of being ignored.

## 401 and 404 look alike on purpose

A card, lead or workflow id from another account is a `404`, exactly as if it did not exist, so a token cannot probe for ids it should not see. An unknown or revoked token is always `401`, never `404`.

## 409: re-read, don't retry

A `409` is a decision, not an outage. Two people, or a person and the [trust ladder](/docs/concepts#the-trust-ladder), can act on the same card, so a verb that was valid when you listed the queue can be gone a second later:

```json
{ "error": "invalid_action", "actions": ["snooze", "dismiss"] }
```

Draw the card again from `actions` and move on. `rejected` carries a `message` a person can read, for example `This was already handled.`

## 429 and 503: back off and retry

Both send `Retry-After` in seconds. It is computed from the bucket, so retrying sooner only spends another request. See [Rate Limits](/docs/rate-limits) for the numbers. A `503` means the token could not be checked, the limiter was down for a write, or a delivery answer was lost. Retrying is safe: approving a card that already shipped comes back as a `409`, and agent messages take a `clientRequestId` so a retried send is not delivered twice.

## Frequently asked questions

### Is there a machine-readable list of routes and errors?

Yes. [`/openapi.json`](https://revnu.com/openapi.json) describes every route, its scope and the error statuses it can return. Each operation links back to its page here.

### Why did an approve return 409 when the card was pending a moment ago?

Someone else resolved it, or it shipped itself at `autoResolveAt`. The body's `actions` says what the card accepts now; an empty list means it is settled.

### Which errors are safe to retry unchanged?

`429` and `503`, after `Retry-After`. Everything else answers the same way until you change the request or the state behind it.
