Skip to main content
Writes take an Idempotency-Key header. A write without one gets 400 IDEMPOTENCY_KEY_REQUIRED. Each operation’s reference page shows whether it takes one.

How it behaves

A key can be at most 255 characters. Its answer is replayed for 7 days per tenant. After that, a key that posted an entry or created a hold is still refused, with IDEMPOTENCY_KEY_REUSED, so a very late retry never posts twice.

A refusal is replayed too

The stored outcome includes refusals. If a payout got INSUFFICIENT_FUNDS, sending the same key again gives INSUFFICIENT_FUNDS again, even after the customer has topped up. To try again as a new attempt, use a new key. If the failure was on PanoQore’s side, such as a 500 or a dropped connection, nothing was stored, and retrying with the same key is the right thing to do.

Choosing a key

Generate it before the call and store it with your own transaction, so a retry after your process restarts uses the same one. A value derived from your own record, like instalment:42:paid, works well.

Operations without the header

  • POST /v1/partners/{partner}/credits is idempotent on the partner’s transaction id. A repeat answers with result: DUPLICATE and credits nothing. See Named accounts.
  • Webhook endpoint calls do not use the header.
  • POST /v1/fee-schedules/preview changes nothing, so it does not need one.

Duplicates across keys

Idempotency-Key protects a retry, not your business rule. By default externalRef is not unique: two entries can share it. If your retries can’t share a key, for example when each attempt from a queue gets its own, set uniqueExternalRef: true on the entry. Only one live entry can then hold that reference, whatever the key, even when attempts race. A second gets 409 EXTERNAL_REF_EXISTS, and you can read the first by its reference. Voiding an entry frees the reference. A reversal doesn’t, because the original still happened. To find an entry by its reference: