> ## Documentation Index
> Fetch the complete documentation index at: https://docs.neobank.proofof.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# Confirmation and idempotency

> Bind customer confirmation to a payload, replay operations safely, and handle uncertain provider writes.

# Confirm and replay financial operations

Sensitive customer actions require a short-lived confirmation intent. Every provider mutation also requires an idempotency key and returns an asynchronous operation.

## Create a confirmation intent

Create an intent with the action and normalized payload fingerprint. Transfer confirmation uses the quote identifier; mandate and card confirmation uses the same public fields as the later mutation.

```http theme={null}
POST /v1/confirmation-intents HTTP/1.1
Origin: https://neobank.example.com
X-CSRF-Token: session_csrf_token_here
Content-Type: application/json

{
  "action": "transfer.create",
  "payload": {"quote_id": "quote_1234567890123"}
}
```

The API marks the intent confirmed only when the customer session contains fresh multifactor evidence. The later mutation consumes it once.

## Submit an idempotent mutation

Send the confirmation and idempotency identifiers as headers:

```http theme={null}
POST /v1/transfers HTTP/1.1
Origin: https://neobank.example.com
X-CSRF-Token: session_csrf_token_here
Confirmation-Intent-ID: confirmation_1234567890123
Idempotency-Key: transfer_1234567890123
Content-Type: application/json

{"quote_id":"quote_1234567890123"}
```

Expect `202 Accepted` with an operation. Repeating the same key and body returns that operation. Reusing the key with another body returns a stable `409` with code `idempotency_key_conflict`.

## Rely on the replay window

Each idempotency key is honoured for twenty-four hours from first acceptance.

A replayed acceptance carries the response header `Idempotent-Replayed: true`. The header is absent on a first acceptance and is never sent as `false`, so its presence alone distinguishes a replay. A replay returns the operation's **current** state, not a snapshot of the original response — a transfer accepted yesterday may already read `succeeded` or `failed` — so follow a replay with `GET /v1/operations/{operation_id}` when you need the outcome.

A key presented after its window is refused with `409` and code `idempotency_key_expired`, and the request is **never executed**. The remedy is a fresh key for the same intent — never a second uncoordinated mutation. Expired records are removed on a schedule that touches no operation row, so the operations themselves remain readable indefinitely.

Client guidance: mint one UUID per user intent, send it on every transport retry of that intent, and keep it until the operation settles. Do not derive keys from payload content alone, and do not reuse a key for a new intent.

### Which routes take a key

Every route that creates or transitions a financial or card resource requires `Idempotency-Key`: beneficiary creation, transfers, mandate creation, revocation, execution, operator suspension, card creation, and the card freeze, unfreeze, and close transitions.

Quotes, confirmation intents, card reveal sessions, KYC sessions, and approval polls take no key: a quote is a re-readable estimate whose one-use confirmation prevents duplicate transfers, intents and reveal sessions are short-lived single-use objects, the hosted KYC session performs no provider write, and polls are reads with idempotent local effects.

## Complete browser approval

A transfer or mandate operation can enter `requires_action` with a trusted Perflo URL, expiry, and poll delay. Open the URL without an opener, then call `POST /v1/operations/{operation_id}/approval/poll`. Repeated polling cannot enqueue another execution.

## Handle provider uncertainty

The worker commits `submitting` before each provider money or authority write. A timeout, reset, possible-acceptance server error, malformed accepted response, or restart while submitting produces `indeterminate`.

Never create another mutation to recover an indeterminate operation. Read `GET /v1/operations/{operation_id}` while the scheduler performs read-only reconciliation. Operator review remains required when provider evidence is missing or ambiguous.
