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
scopeYour partner. Two partners can use the same key without colliding; two of your own companies cannot, so include the company id.
bound toThe method, the full path including the query string, and the body
kept30 days
formatAny 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, with differs set 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

  1. Choose the key before the first attempt and persist it with the intent, so a crash between attempts does not mint a new one.
  2. On a timeout or a 5xx, retry with the same key and the same body. Back off; a few seconds is plenty.
  3. On a 4xx, do not retry blindly. The rule tells you what to change; change it and send again with the same key.
  4. On 409 ORIGIN_DUPLICATE, stop: the document is already in, and existingId says 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 externalReference from the same connection, or the same externalId on a bank transaction, answers 409 ORIGIN_DUPLICATE with existingId. 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.