Skip to main content
Every refusal the API returns deliberately, at every status, is { "error": { "code", "message" } }, and such a response is a failure if and only if it carries an error key — that test is right even for a client that ignores the status line. An unexpected server failure may not have this shape: treat a 5xx without an error key as an internal_error, which is retryable. code is the contract and never changes meaning or spelling; message is prose for a log and may be reworded. The enum is closed, so a switch over it can be total and needs no default branch that means “something we did not publish”. Each code says whether retrying the identical request could ever succeed, and how to wait if so. The same table is at the root of the OpenAPI document as x-error-codes.

Retrying a request safely

These operations require an Idempotency-Key: The key identifies one request, not one envelope: generate a key for each request you intend, store it before the first attempt, and reuse it only to retry that same request with the identical body. Creating a draft and then sending it are separate requests, each with its own key; a key already used for one is refused for the other as idempotency_key_reused, and so is the same key with a different body. A retry while the first attempt is still running is answered request_in_progress: retry the identical request under the backoff the table above gives for that code. Within the key’s retention window, which the create-envelope reference states, a retry returns the first attempt’s answer and does nothing again. Past the window the key can be forgotten at any time, and a request carrying it is then a new request:
  • A send that already went out is refused as envelope_not_draft, and a void that already took effect is refused as envelope_not_sent. A late retry of either changes nothing.
  • A late retry of a request that creates something is not harmless: it can create it again — a second template, envelope or batch, and an envelope that is sent emails its recipients again.
So past the window, stop and find out whether the first attempt created something before creating anything:
  • For a template, the template list is newest first and each row carries id, name and created_at. Look for one created around your first attempt under the name it was given.
  • For an envelope, the envelope list cannot be searched by your own identifiers. It is newest first, filters only by status, and each row carries id, title, status, template_id, created_at and sent_at — no recipients.
  • Narrow it to rows with the template_id you sent (null for a one-off) created around your first attempt, then read each candidate with GET /api/v1/envelopes/{envelopeId}.
  • Match its recipients on the email and the external_ref you sent. external_ref is stored NFC-normalised, so normalise your own value to NFC before comparing. Set external_ref on every recipient at creation so that this match is possible.