Developer essentials
Idempotency
A network fails after the request was processed and before you saw the answer. Retry with the same key and you get the same answer, and the invoice is not issued twice.
The key
Every POST carries an Idempotency-Key. It is required; a POST without one answers 400 IDEMPOTENCY_KEY_REQUIRED. GET, PATCH and DELETE do not take one.
POST /v1/companies/cmp_…/origins
Idempotency-Key: cmp_…-inv-2026-000117
| scope | Your partner. Two partners can use the same key without colliding; two of your own companies cannot, so include the company id. | |
| bound to | The method, the full path including the query string, and the body | |
| kept | 30 days | |
| format | Any non-empty string. Your own document id, prefixed with the company id, is a good key. A random UUID per attempt is not: it defeats the purpose. |
Replays and conflicts
- Same key, same request. The stored response is returned, status code and all, without running anything. The second call to create an invoice returns the invoice the first call created.
- Same key, different request.
409 IDEMPOTENCY_CONFLICT, withdiffersset to"path"or"body". The original is untouched. - Same key, first request still running.
409 IDEMPOTENCY_IN_PROGRESS. Wait and retry. - The first attempt failed. A 4xx or 5xx releases the key. Fix the request and send it again with the same key.
409
{ "rule": "IDEMPOTENCY_CONFLICT", "message": "Idempotency-Key was already used with a different body", "differs": "body" }
A retry policy
- Choose the key before the first attempt and persist it with the intent, so a crash between attempts does not mint a new one.
- On a timeout or a 5xx, retry with the same key and the same body. Back off; a few seconds is plenty.
- On a 4xx, do not retry blindly. The rule tells you what to change; change it and send again with the same key.
- On
409 ORIGIN_DUPLICATE, stop: the document is already in, andexistingIdsays where.
Beyond the key
The key guards a request. Vexless also guards the things the request creates, so a retry that somehow arrives with a new key is still harmless:
- An origin with the same content, or the same
externalReferencefrom the same connection, or the sameexternalIdon a bank transaction, answers409 ORIGIN_DUPLICATEwithexistingId. See deduplication. - An attachment with the same bytes returns the existing attachment.
- Webhook events are delivered at least once and carry a stable id and, for origins, a version. See handling every event once.
Dry runs do not consume the key: with ?dryRun=true nothing is recorded, the key included, and the header is not required.