Errors & Rate Limits

How failures are shaped, and what to expect on request volume.

Error format

API errors use the following structure:

{
"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"
}
FieldDescription
typeThe error type. Use it with the HTTP status for programmatic handling rather than matching message.
messageHuman-readable description of the failure. The wording may change and is not part of the API contract.
detailsStructured context about the failure, when available.
errorsField-level problems in a malformed request (400). See 400 vs 422.
validation_errorsIndividual business-rule failures (422), when the failure has more than one cause.
authentication_errorsIndividual authentication failures (401), when available.
trace_id32-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.

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:

{
"message": "Unauthorized"
}

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

HTTP status codes

CodeMeaningWhat to do
202 AcceptedPenny accepted the request, but processing is not complete.Follow the returned resource or task until it reaches a terminal state.
400 Bad RequestThe request is malformed: a required field is missing, or a value has the wrong type or format.Correct the fields listed in errors.
401 UnauthorizedAuthentication is missing, invalid, or expired.Obtain a valid access token and retry.
403 ForbiddenThe caller is authenticated but does not have the required permission, or the business is inactive.Check the application’s roles and permissions.
404 Not FoundThe requested resource was not found in the current business context.Verify the identifier and business context.
409 ConflictThe request conflicts with a concurrent change or with a request still being processed.See Conflicts.
422 Unprocessable EntityThe 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 RequestsPenny cannot process the request at the submitted rate.Wait for the Retry-After interval, then retry with backoff.
500 Internal Server ErrorPenny 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:

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:

SituationResponse detailsWhat to do
Another request updated the resource between your read and your write.object_type, object_id, expected_version, found_versionRead the latest version, reapply your change, and send the request again. See Resource Versions & Concurrency.
A request with the same Idempotency-Key is still being processed.—Wait, then retry with the same key. See Retries & Idempotency.

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

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:

{
"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"
}
FieldDescription
typeThe kind of problem, such as missing, string_type, or enum.
locationThe path to the field, such as amount or rules[0].limit. Omitted when the problem applies to the whole body.
messageHuman-readable description of the problem.
inputThe 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.

ResponseRetry?
400No — correct the request first.
401Yes, after obtaining valid authentication.
403No — correct permissions or business context first.
404Usually no — verify the resource and context.
409Yes, after re-reading the resource (concurrent update) or waiting (idempotency key in progress).
422No — correct the request or resource state first.
429Yes, after the Retry-After interval, with backoff.
500Usually 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.

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/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.