Idempotency
Retry any POST safely with an Idempotency-Key, and name payouts with a customId.
Two mechanisms keep a retried request from doing its work twice. They do different jobs.
Idempotency-Key
Send an Idempotency-Key header on every POST. It is a client-chosen string of 1 to 255 characters; a UUID is recommended.
- The same key on the same endpoint for the same team within 24 hours replays the stored response, with the header
Idempotent-Replayed: true. The retry does no work. - The same key with a different body is 409
IDEMPOTENCY_KEY_REUSED. - The same key while the first request is still running is 409
IDEMPOTENCY_KEY_IN_PROGRESS. Retry after a moment. - A 500
INTERNAL_ERRORis stored like any other response, because the request can have done part of its work before the fault. Its replay does not run the request again. Check the state first, for a payout withGET /payouts/{customId}, then send the request with a new key if it is still needed.
So a timeout-and-retry loop is safe: send the identical request again and you get the original response back.
curl -X POST "$BASE_URL/payouts" \
-H "Authorization: Bearer $TALENTIR_API_KEY" \
-H "Idempotency-Key: payout-create:INV-42" \
-H "Content-Type: application/json" \
-d '{ "customId": "INV-42", "description": "Invoice 42", "amount": "100.00", "currency": "EUR", "recipient": { "type": "email", "email": "jane@example.com" } }'customId
A customId is your own key for a payout or a counterparty: an order id, a campaign line, an invoice number. It is unique within your team and per resource.
A payout's customId is used once: it stays taken after the payout is deleted, so a repeated create can never pay twice. A deleted counterparty is gone from the API, and its customId is free again for a new counterparty.
POST /payoutsonly creates. A second create with the samecustomIdis 409CUSTOM_ID_TAKEN, anddetails.idnames the payout that holds it.details.idisnullwhen a deleted payout holds it.- A
customIdworks in place of the id wherever a payout or a counterparty is referenced: the{id}in a path (GET /payouts/campaign-42), theidsof a batch request,recipient.counterpartyId, thepayoutIdandpayoutIdsof a session, and therecipientCounterpartyIdfilter. Responses always carry the prefixed id. - A new or changed
customIdhas 1 to 255 characters: ASCII letters, digits,.,_,:, and-, starting with a letter or a digit. Such a key is safe in a URL path as is. A key saved earlier through v1 that does not follow this format keeps working in every lookup and is returned as it is. - A
customIdmust not start with the resource prefix (payout_,counterparty_), so the two key spaces stay apart.
Use both
The key says which request attempt this is, for 24 hours. The customId says which payout this is, forever. For a create, derive the key from your business key, for example Idempotency-Key: payout-create:INV-42. A retry inside 24 hours replays the 201. A retry later gets 409 CUSTOM_ID_TAKEN with the payout's id. Both end on the same payout.
Locking a payout down
If the payout must not change after creation — for example when the request data comes from a signed source — create it with "locked": true. A locked payout refuses PATCH and dashboard edits with 409 PAYOUT_LOCKED. Only delete is left.
What is not idempotent
Session minting (POST /sessions) returns a fresh URL each call by design; the header still replays a retry. Webhook creation replays with the header too; without it, check GET /webhooks before subscribing if you might retry.