> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.thepennyinc.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.thepennyinc.com/_mcp/server.

# Errors & Rate Limits

## Error format

API errors use the following structure:

```json
{
  "type": "ObjectNotFoundException",
  "message": "spend_control with ID spend_control_019379d9-a170-796b-867a-a17096b67a21 not found.",
  "details": {
    "object_type": "spend_control",
    "object_id": "spend_control_019379d9-a170-796b-867a-a17096b67a21",
    "object_version": null,
    "object_stage": null
  },
  "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
}
```

| Field                   | Description                                                                                                                      |
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `type`                  | The error type. Use it with the HTTP status for programmatic handling rather than matching `message`.                            |
| `message`               | Human-readable description of the failure. The wording may change and is not part of the API contract.                           |
| `details`               | Structured context about the failure, when available.                                                                            |
| `errors`                | Field-level problems in a malformed request (`400`). See [400 vs 422](#400-vs-422).                                              |
| `validation_errors`     | Individual business-rule failures (`422`), when the failure has more than one cause.                                             |
| `authentication_errors` | Individual authentication failures (`401`), when available.                                                                      |
| `trace_id`              | 32-character lowercase hexadecimal identifier that correlates the failure with Penny's logs. Include it when contacting support. |

Keys whose value would be `null` are omitted from the top level of the response, so an error body contains only the fields that apply.

> **Info**
>
> Do not build application logic around the exact text of `message`. Use the HTTP status and structured error fields instead.

### Gateway errors

Some `401` and `403` responses are returned before the request reaches a Penny API — for example, when the access token is missing or invalid, or when the business is inactive. These responses have a single field:

```json
{
  "message": "Unauthorized"
}
```

Handle them by HTTP status. They do not include `type` or `trace_id`.

## HTTP status codes

| Code                        | Meaning                                                                                             | What to do                                                                                         |
| --------------------------- | --------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `202 Accepted`              | Penny accepted the request, but processing is not complete.                                         | Follow the returned resource or task until it reaches a terminal state.                            |
| `400 Bad Request`           | The request is malformed: a required field is missing, or a value has the wrong type or format.     | Correct the fields listed in `errors`.                                                             |
| `401 Unauthorized`          | Authentication is missing, invalid, or expired.                                                     | Obtain a valid access token and retry.                                                             |
| `403 Forbidden`             | The caller is authenticated but does not have the required permission, or the business is inactive. | Check the application's roles and permissions.                                                     |
| `404 Not Found`             | The requested resource was not found in the current business context.                               | Verify the identifier and business context.                                                        |
| `409 Conflict`              | The request conflicts with a concurrent change or with a request still being processed.             | See [Conflicts](#conflicts).                                                                       |
| `422 Unprocessable Entity`  | The request is well formed but violates a business rule.                                            | Inspect `message`, `details`, and `validation_errors`, then correct the request or resource state. |
| `429 Too Many Requests`     | Penny cannot process the request at the submitted rate.                                             | Wait for the `Retry-After` interval, then retry with backoff.                                      |
| `500 Internal Server Error` | Penny encountered an unexpected failure.                                                            | Retry only where the operation is safe to retry; keep the `trace_id` for support.                  |

Individual API operations may document additional error responses or more specific recovery behavior.

## Asynchronous operations

Some Penny operations return `202 Accepted` because work continues after the initial request completes.

The response represents the **current state**, not necessarily the final state.

For example, deleting an account returns `202 Accepted` with a task that tracks the deletion:

```text
DELETE request
     │
     ▼
202 Accepted  (task status: received)
     │
     ▼
pending / running
     │
     ├──► completed
     ├──► failed
     └──► cancelled
```

Where the response includes a task, poll `GET /tasks/{task_id}` until its `status` is `completed`, `failed`, or `cancelled`.

Otherwise, follow the resource lifecycle documented for that operation.

### Poll safely

When polling:

* use a reasonable interval;
* stop when the resource reaches a documented terminal state;
* place a maximum time or attempt limit on polling;
* handle terminal failure states explicitly.

Avoid unbounded loops.

## Conflicts

A `409 Conflict` means Penny could not safely complete the operation because it conflicts with another change or with a request that is still being processed. Penny returns `409` in two situations:

| Situation                                                              | Response `details`                                              | What to do                                                                                                                                |
| ---------------------------------------------------------------------- | --------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| Another request updated the resource between your read and your write. | `object_type`, `object_id`, `expected_version`, `found_version` | Read the latest version, reapply your change, and send the request again. See [Resource Versions & Concurrency](/get-started/versioning). |
| A request with the same `Idempotency-Key` is still being processed.    | —                                                               | Wait, then retry with the same key. See [Retries & Idempotency](/get-started/idempotency).                                                |

Sending the same request again unchanged after a concurrent-update `409` fails the same way. Re-read first.

> **Warning**
>
> Do not copy an entire GET response into a PATCH request. Read models can contain IDs, status fields, masked values, or derived properties that are not valid update fields.

## 400 vs 422

Both responses indicate a problem with the request, at different stages.

### `400 Bad Request`

The request is malformed. Penny could not read it as a valid request for the operation.

Typical causes:

* malformed JSON or form data;
* a required field is missing;
* a value has the wrong type or format (for example, a string where a number is expected, or an invalid date);
* an unknown enum value.

The response `type` is `RequestValidationError`, and `errors` lists each problem:

```json
{
  "type": "RequestValidationError",
  "message": "Invalid request.",
  "errors": [
    {
      "type": "missing",
      "location": "currency",
      "message": "Field required",
      "input": {
        "from_account_id": "account_019379d9-a170-796b-867a-a17096b67a21",
        "to_account_id": "account_019379d9-b2c4-7d1e-9f0a-3c5e7a9b1d20",
        "amount": "250.00"
      }
    }
  ],
  "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
}
```

| Field      | Description                                                                                                      |
| ---------- | ---------------------------------------------------------------------------------------------------------------- |
| `type`     | The kind of problem, such as `missing`, `string_type`, or `enum`.                                                |
| `location` | The path to the field, such as `amount` or `rules[0].limit`. Omitted when the problem applies to the whole body. |
| `message`  | Human-readable description of the problem.                                                                       |
| `input`    | The value Penny received at that location, when available.                                                       |

### `422 Unprocessable Entity`

The request is well formed but violates a business rule.

Typical causes:

* mutually exclusive fields were supplied together;
* a referenced resource is in a state that does not permit the operation;
* the operation would breach a limit, such as an insufficient balance;
* an `Idempotency-Key` was reused with a different request body.

The response `type` is the rule that failed. When more than one rule failed, `validation_errors` lists each one with its own `message` and `details`.

## Retry guidance

Not every failed request should be retried.

| Response | Retry?                                                                                           |
| -------- | ------------------------------------------------------------------------------------------------ |
| `400`    | No — correct the request first.                                                                  |
| `401`    | Yes, after obtaining valid authentication.                                                       |
| `403`    | No — correct permissions or business context first.                                              |
| `404`    | Usually no — verify the resource and context.                                                    |
| `409`    | Yes, after re-reading the resource (concurrent update) or waiting (idempotency key in progress). |
| `422`    | No — correct the request or resource state first.                                                |
| `429`    | Yes, after the `Retry-After` interval, with backoff.                                             |
| `500`    | Usually retry with bounded backoff, but only where doing so is safe for that operation.          |

For create and money-movement requests, retry only when you send the same `Idempotency-Key` with the same request body, so Penny can return the original result instead of repeating the operation.

See [Retries & Idempotency](/get-started/idempotency).

## Rate limits

Penny returns `429 Too Many Requests` when it cannot process a request at the submitted rate. The response includes a `Retry-After` header with the number of seconds to wait before retrying:

```http
HTTP/1.1 429 Too Many Requests
Retry-After: 1
```

Your integration should:

* wait at least the `Retry-After` interval before retrying;
* retry using exponential backoff;
* avoid large bursts of unnecessary requests;
* cache resources and access tokens where appropriate.

## Troubleshooting

When contacting Penny about an API failure, provide:

* the `trace_id`;
* the HTTP method and endpoint;
* the approximate request time;
* the environment;
* the resource ID, where relevant.

Do not send client secrets, access tokens, full card details, or other sensitive credentials in support requests.