Retries & Idempotency

Retry requests safely without accidentally repeating an operation.

Network failures are ambiguous.

A timeout, dropped connection, or process restart can prevent your application from receiving a response even though Penny may already have processed the request.

For read-only requests such as GET, retrying is normally safe.

For operations that create resources or move money, use the operation’s documented retry mechanism rather than blindly submitting the request again.

An Idempotency-Key tells Penny whether a request is a retry. Send the same key and Penny returns the original result instead of repeating the operation. Send a different key and Penny treats the request as a new operation.

Idempotency-Key

Prevents the same intended operation from being processed more than once on endpoints that accept the header.

client_reference

A reference controlled by your application for lookup and reconciliation. It does not prevent duplicates.

Idempotency keys

These endpoints accept an Idempotency-Key header:

APIEndpoint
BankingPOST /accounts/capital/transfer
SimulationPOST /transaction/card
SimulationPOST /transaction/payment
SimulationPOST /transaction/update

The header is optional, but we strongly recommend sending it on every money-movement request. Without it, Penny cannot recognize a retry, and a retried request is processed as a new operation.

Generate one unique key for each intended operation, persist it before sending the first request, and reuse that same key only when retrying that operation.

Transfer A → key A
retry A → key A
Transfer B → key B

Even if Transfer A and Transfer B have identical request bodies, they represent different intentions and must use different keys.

A UUID or ULID is a good choice. Keys can be up to 255 characters.

curl -X POST https://sandbox.api.thepennyinc.com/accounts/capital/transfer \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Idempotency-Key: 3f29c1a2-8b7e-4e51-9a2e-2b6a1c9d7e10" \
-H "Content-Type: application/json" \
-d '{
"from_account_id": "account_019379d9-a170-796b-867a-a17096b67a21",
"to_account_id": "account_019379d9-b2c4-7d1e-9f0a-3c5e7a9b1d20",
"amount": "100.00",
"currency": "USD",
"description": "Weekly operating float",
"client_reference": "float-run-2026-09-09-00042"
}'

A successful transfer returns 201 Created with both sides of the movement:

{
"source_transaction": {
"transaction_id": "transaction_019379da-0c1e-7a3b-9d2f-4e6a8b0c2d14",
// …
},
"destination_transaction": {
"transaction_id": "transaction_019379da-0c1f-7b4c-8e3a-5f7b9c1d3e25",
// …
}
}

The transaction objects are abridged here. See Transactions for the full shape.

Retrying with the same key

When retrying the same intended operation, reuse the original key and send the same request body.

SituationResponse
The original request completed.The original result, with the original status (201 Created for a transfer). No second operation is created.
The original request is still being processed.409 Conflict, in the standard error format. Wait, then retry with the same key.
The original request failed.The key is released, so the retry runs the operation again.
The same key is sent with a different request body.422 Unprocessable Entity. Penny does not treat it as a retry.

Never generate a new idempotency key because the first request timed out. A new key represents a new intended operation and can create a second transfer.

Idempotency key scope and retention

Penny scopes each key to your business and to the operation it was sent with. The same key sent to a different endpoint is a separate key.

Penny keeps each key for 7 days. After that, a request with the same key is processed as a new operation.

Store the idempotency key alongside your own record of the intended operation so the same key can be reused during recovery.

Client Reference

Some Penny resources and transactions support a client_reference.

Use it to associate Penny activity with an identifier from your own system:

{
"from_account_id": "account_019379d9-a170-796b-867a-a17096b67a21",
"to_account_id": "account_019379d9-b2c4-7d1e-9f0a-3c5e7a9b1d20",
"amount": "100.00",
"currency": "USD",
"client_reference": "float-run-2026-09-09-00042"
}

You can then query transactions using that reference:

GET /transactions/by-client-reference?client_reference=float-run-2026-09-09-00042

client_reference is useful for:

  • reconciliation;
  • tracing an operation back to your system;
  • investigating an ambiguous outcome.

It is not an idempotency mechanism.

Penny does not require client_reference to be unique and does not deduplicate requests based on it.

For endpoints that support both values:

  • use Idempotency-Key to make retries safe;
  • use client_reference for reconciliation and lookup.

Ambiguous outcomes

A timeout or 5xx response does not necessarily mean the operation failed.

Treat it as an unknown outcome until you determine whether Penny processed the request.

Request sent
│
├── response received ──► handle normally
│
└── outcome unknown
│
├── idempotent operation
│ └── retry using the same key
│
└── non-idempotent operation
└── reconcile before creating again

Do not assume:

no response = no side effect

Creation endpoints without idempotency

Endpoints that do not accept an Idempotency-Key, such as POST /cards/virtual, need more care after an uncertain result: Penny cannot tell an intentional second card from a retried duplicate.

Where the API provides suitable lookup fields:

  1. retain the identifiers and request context from the original attempt;
  2. query for the intended resource;
  3. determine whether the original operation appears to have succeeded;
  4. create again only when you can establish that doing so will not produce an unintended duplicate.
Check before create

Looking for an existing resource can reduce duplicate risk, but it is not the same guarantee as idempotency. A resource may not yet be visible, or another request may race with your lookup.

Do not invent a resource ID you did not receive, and do not assume a list lookup is an atomic duplicate-prevention mechanism.

If your integration requires a stronger exactly-once creation guarantee for an endpoint that does not support idempotency, contact Penny before relying on automated retries.

Retry guidance

Use the following as a starting point, then follow the operation-specific error contract.

OutcomeGuidance
Successful responseDo not repeat the operation unless you intend another one.
Timeout / connection failureTreat the outcome as unknown.
409 ConflictFor a request still in progress under the same key, wait and retry with the same key. For a concurrent update, see Resource Versions & Concurrency.
4xx validation or permission errorCorrect the request or permissions before retrying.
429 Too Many RequestsWait for the Retry-After interval, then retry with backoff and the same key.
5xxRetry only when safe for that operation; use the same idempotency key when retrying the same idempotent operation.

See Errors & Rate Limits for general error handling.

For every operation that supports idempotency:

  1. create a unique operation ID in your system;
  2. generate and persist an idempotency key;
  3. send the request;
  4. store the resulting Penny resource or transaction ID;
  5. reuse the original key for retries of that same operation;
  6. use client_reference where available to support reconciliation.

For example:

Your system
│
├── operation_id: float_48219
├── idempotency_key: 3f29c1a2-...
└── client_reference: float-run-2026-09-09-00042
│
▼
Penny
│
└── transaction_id: transaction_...

This makes the intent of each operation explicit and gives both systems identifiers that can be used during recovery and reconciliation.