unauthenticated | 401 | no | The credential was absent, unreadable, unknown, wrong or revoked — this answer is deliberately identical for all of them, so it cannot be used to probe which keys exist. Retrying the same request changes nothing. Check the key in Settings or issue a new one. |
forbidden | 403 | no | The key is valid and the organisation’s plan does not grant what this operation does. Nothing about the credential needs to change, and it is deliberately MORE specific than 401 because the caller has already proved they hold the key. TWO SITUATIONS PRODUCE IT. (1) THE PLAN EXCLUDES THE OPERATION: POST /api/v1/templates on the Free plan, which does not include authoring templates. A template that already exists stays readable at GET /api/v1/templates; authoring one needs a plan that includes templates. (2) WE CANNOT ESTABLISH WHAT THE PLAN INCLUDES, on any operation, so nothing is granted. That one is ours rather than yours, an ordinary account does not meet it, and it means ask us. |
insufficient_scope | 403 | no | This key is scoped and does not hold the scope this operation requires — x-required-scope on the operation names it, and the WWW-Authenticate header on the refusal repeats it (RFC 6750 §3.1). ⚠️ Retrying cannot help and neither can editing the key: scopes are fixed when a key is minted and no endpoint or screen can widen them. Issue a new key with the access it needs. A key created before scopes existed carries an empty scope list, which means EVERY capability, so this refusal cannot reach an integration that was already working. |
not_found | 404 | no | No such resource — or none this key’s organisation can see, or one that has been archived, or an id that is not a uuid. Those four are one answer on purpose: “it exists but is not yours” is itself a disclosure. Do not retry; check the id against the list endpoint. |
invalid_request | 400 | no | The request is malformed, or breaks a rule about its own shape: a body that is not JSON or does not match the operation’s schema (a missing or mistyped property, a value out of range), a document that is not a PDF or a Word file we can read, a text tag that cannot be placed, an Idempotency-Key that is present but not a valid key, a query parameter outside its range, or a cursor that names nothing. The message says which, and for a body names the property path (recipients[0].email) — there is no separate param field. Fix the request; retrying it unchanged will fail identically. |
rate_limited | 429 | Exponential from 1s, doubling, with jitter. Honour Retry-After when present. | This key has spent its minute. The limit is per KEY and per minute, and it follows your PLAN rather than the kind of key — 600 requests a minute on every paid plan, 60 on the free plan, and the sandbox gets the same number as production so that an integration which passes in rehearsal passes live. Every successful authenticated response, and this one, carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset (seconds), so a client can slow down before it is made to. TWO OPERATIONS ALSO ANSWER IT FOR A REASON OF THEIR OWN. POST /api/v1/envelopes/batches costs one request per row and is refused WHOLE when its rows do not fit what is left of the key’s minute — RateLimit-Remaining says how many would. GET /api/v1/envelopes/{envelopeId}/documents/{position}/draft has its own per-envelope and per-organisation ceilings on renders each minute, and the message says which one refused; there Retry-After is that ceiling’s, while the RateLimit-* headers still describe the key. Retry-After on this refusal is at most 60 and is read from the stored window rather than computed, so two of our instances refusing the same key quote the same instant. ⚠️ A REFUSED REQUEST STILL COUNTS: hammering a limit you have already exceeded pushes the counter higher rather than holding it. Honour Retry-After. If one key genuinely needs more throughput, issue a second one in Settings — the ceiling is a fairness and blast-radius control, not a commercial meter. |
internal_error | 500 | Exponential from 1s, at most three attempts, then stop and alert a human. | Ours, not yours. ⚠️ RETRY WITH THE SAME Idempotency-Key YOU SENT THE FIRST TIME, on any operation that takes one — that is what makes a retry safe here, and sending a NEW key would create a second envelope or send the same one twice. The safe GETs in this document can be retried freely. (This entry used to say every operation here was a GET; the write endpoints have since shipped, and the reasoning moved with them.) |
service_unavailable | 503 | Exponential from 5s, doubling, for a few minutes. Nothing was created. | Ours, and briefly. Something this operation depends on was not answering. Two services can produce it and which one it was depends on the operation: on a create, the service that renders a Word document to a PDF, so it is reachable only by sending a .docx and never by sending one that is already a PDF; on GET .../documents/{position}/draft, the service that renders the draft itself. ⚠️ THE DISTINCTION FROM 400 IS THE WHOLE POINT: your request is fine and your document is fine, so do not go and re-save it. On a create, nothing was created and no allowance was spent. On an operation that takes an Idempotency-Key, retry the identical request with the SAME one — a completed key replays, so a retry can never make a second envelope or a second template even if the first attempt got further than this answer suggests. A new key could. ⚠️ ON THE DRAFT READ THE ATTEMPT WAS ALREADY PAID FOR: the render is charged to the envelope’s and the organisation’s per-minute draft ceilings before the renderer is asked, and a render that then fails is not refunded. There is no key to reuse, because there is nothing to replay — so retry on the backoff above rather than at once, or an outage of ours becomes a rate_limited of yours. |
idempotency_key_required | 400 | no | Send Idempotency-Key: <uuid> — this operation takes one and the request carried none. A distinct code from invalid_request because the remedy is to ADD a header, and an integrator reading it in a log should not have to work that out. (A key that is present but malformed is invalid_request, and its message names the header.) |
idempotency_key_reused | 409 | no | This key was already used for a DIFFERENT request. ⚠️ The request itself may be perfectly valid — what conflicts is the key against state we already hold, which is what 409 means and why it is not a 400. Mint a new key. Do not retry with this one; it will conflict forever. |
request_in_progress | 409 | Exponential from 1s, up to about 30s in total. The first request is still running. | A request carrying this key has not finished. We deliberately do not block waiting for it — that would hold a connection across a send that calls an email provider N times. Retry the identical request; when the first one lands you will get its result, replayed. |
template_archived | 404 | no | The template exists and has been retired. It has a code of its own because a bare 404 tells an integrator whose template was archived yesterday nothing. It carries the same 404 status and it only ever fires for a template their own key could otherwise have read — discoverability without disclosure. Use a live template. |
template_unusable | 422 | no | The template cannot produce an envelope at all. The message says which way. |
template_has_no_fields | 422 | no | There are no fields, so the envelope would ask nobody to do anything. From a template, place at least one field on it in the editor. On POST /api/v1/envelopes/one-off, which has no template, the document you sent carries no field: add a text tag to it, or send fields. A batch refuses every row for this, because every row shares the template. |
recipient_role_unknown | 422 | no | A recipient names a role the template does not declare. Read the roles from GET /api/v1/templates/{templateId} — they are matched by NAME, exactly, because a positional address breaks when a sender reorders. |
recipient_role_missing | 422 | no | A role that has fields on it was given nobody to fill them. Every role the template declares with work to do needs a recipient. |
recipient_role_duplicated | 422 | no | Two recipients were given one role that the template does not route as cc. Everyone on a role is served the same fields and every one of them must sign, so both would answer the same boxes and the completed envelope could never be sealed. Give each person a role of their own; several cc readers may share a role. Answered by the create endpoints, naming each recipients[i].role that holds the role, and by the send endpoint for a draft saved before this was refused. |
value_subject_unknown | 422 | no | A value names a subject the template does not declare. The subjects are in the template response; null is the subject for a field belonging to nobody in particular. |
value_address_unknown | 422 | no | The (subject, key) pair addresses no field of this template. ⚠️ This is the refusal that most often means the template has UNADDRESSED boxes rather than that you mistyped: data_key is null on any field the sender never gave an address to. Check the template response before blaming the value. |
value_not_writable | 422 | no | The address names real fields and none of them is one a caller may fill in — a date_signed, a signer_name, a signature. Those are written by the server or by the signer, and supplying them would be a forgery with extra steps. |
value_not_lockable | 422 | no | The address names a signer_title or a signer_company, and locked cannot ride on a value for one. ⚠️ THE VALUE ITSELF IS FINE — send the same triple without locked and it is accepted. What is refused is FIXING the answer on the field: a title is a fact about whoever fills the slot, so it is seeded per recipient from the job_title and company you give on the recipient, while a locked answer is one value on one box — assert it and every party holding that role signs under the same title. Set job_title / company on each recipient instead. (A sender may still tick “Signer cannot change” on such a field in the editor: there the lock says the roster’s value is final and carries no answer of its own.) |
locked_answer_missing | 422 | no | A field on this envelope is locked, required and carries no answer — three statements that cannot all be honoured. A locked answer is the sender’s, so the signer is refused if they supply one and the completeness check does not ask them for it; once sent, nobody could fill the box and it would seal blank. ⚠️ THE LOCK MAY NOT HAVE COME FROM YOUR REQUEST — it can be authored on the template — so the message names the FIELD rather than a values[i] path. Send a value for that address, send {"value": "", "locked": false} to hand it back to the signer, or have the template make it optional. ⚠️ THE SAME CODE ALSO MEANS A QUESTION NOBODY LEFT COULD ANSWER. Grouped tickboxes — a Race or Gender question — are never individually required; the question’s own min_selected is what makes it compulsory. Lock every option of such a question with no tick in any of them and it asks for more answers than remain within anybody’s reach, which is the same unsatisfiable sentence and refuses the same way. There the message names the QUESTION, and the remedies are to tick one of the locked options, unlock one, or lower the question’s minimum. |
locked_answer_unusable | 422 | no | A field on this envelope carries a locked answer the field itself will not accept: a value its validation rule rejects, one its input format cannot carry, one longer than the printed cells of a comb or holding a character one of those cells does not accept, a date that is not a calendar day, or a dropdown answer that is not among the options it offers. ⚠️ A date DRAWN AS PRINTED CELLS IS MEASURED IN ITS OWN SPELLING: it holds the digits its boxes print — 15092026, not 2026-09-15 — and those digits must still name a day that exists, read in the order the field’s label states (Date of birth (DD/MM/YYYY)). ⚠️ THIS IS NOT locked_answer_missing — that one is a box with NO answer. This is a box with an answer nobody can use: a locked answer is the sender’s, the signer may not change it, and their very first save would be refused over it on an envelope nobody can edit any more. Refused here so that it is still a draft. ⚠️ THE ANSWER MAY NOT HAVE COME FROM YOUR REQUEST — a lock and its value can be authored on the template, and a rule can be tightened after the answer was fixed — so the message names the FIELD rather than a values[i] path. Send a value that satisfies the field, send {"locked": false} to hand the box back to the signer, or change the rule on the template. ⚠️ THE SAME CODE ALSO MEANS A QUESTION THE SENDER ANSWERED MORE TIMES THAN IT PERMITS, AND THEN NO FIELD RULE IS BROKEN AT ALL. Grouped tickboxes — a Marital status question at max_selected: 1 — can each carry a perfectly good locked tick while together they assert more than the question allows; every one of them would seal, side by side, on a question that says fewer can be true, and the signer may clear none of them. There the message names the QUESTION rather than a field, and sending a field-valid value resolves nothing because every value already is one. The remedies are to untick one of the locked options, to unlock one so the signer chooses, or to raise the question’s max_selected. ⚠️ AND THAT LAST ONE IS NOT SOMETHING YOU CAN SEND: max_selected is published by GET /api/v1/templates/{templateId} and NO ENDPOINT HERE SETS IT — a question’s bounds are authored on the template, and its id is a join key within that response rather than an address this API accepts. ⚠️ AND ONE ADDRESS MAY TICK EVERY OPTION AT ONCE, which is the commonest way a request reaches this: the options of one question often share a data_key (all six of Race are race), and a values entry fills EVERY box at its address — so {"value": "true", "locked": true} there locks a tick into all of them. Give the options their own addresses on the template if you need to fix exactly one. |
cannot_be_drawn | 422 | no | A value, a name or a title contains something the sealer cannot draw into the PDF. Asked BEFORE the send rather than discovered after everybody has signed. The message names which string. |
send_allowance_exhausted | 402 | no | ⚠️ 402, THE ONLY STATUS IN THIS API THAT NAMES MONEY. Not 403 (the credential is permitted; the plan is not) and not 429 (waiting does not help until the month turns). Upgrade the plan, or wait for the period to roll over. ⚠️ RETRYING IS POINTLESS BUT NOT HARMFUL — nothing was created, so there is no partial envelope to clean up. Only the Free plan (5 documents a month) is ever blocked, once that many documents have been sent in the month; every other plan is counted and never stopped, and a vsk_test_ key never meets this at all because a sandbox envelope consumes no allowance to exhaust. |
test_key_cannot_send | 403 | no | A vsk_test_ key tried a send it may not make. TWO SITUATIONS PRODUCE IT. (1) POST /api/v1/envelopes/{envelopeId}/send on an envelope a LIVE key created: sending it would email its real recipients and spend real allowance, so send it with a live key. (2) Far more often, a recipient who would be EMAILED and whose address is not one this organisation may send rehearsals to. THE RULE IS ABOUT WHO, NOT ABOUT WHETHER: a test key may email anybody who is a MEMBER of the sending organisation, and any other address that has CONFIRMED a verification link sent to it (Settings → Test recipients). Nobody else at all. So a mixed envelope — one "embedded": true signer in your application and one emailed counterparty — is fully rehearsable, provided that counterparty is on the list. An "embedded": true recipient is never checked against it, because an embedded recipient is issued no invitation and is emailed by no code path at all. ⚠️ WHY THE LIMIT EXISTS: a sandbox send consumes no billing allowance, so an unbounded one would be an unmetered way to email strangers from a free account, over a sending domain every customer shares. The message names each address that was refused. Your options are: add and verify the address; make the recipient "embedded": true; create the draft with "send": false; or send with a live key, which may email anybody. What a test key still never does is consume billing allowance or produce a sealed artefact anybody can be held to — its documents are watermarked on every page and sealed by a certificate issued for the sandbox and for nothing else, so a PDF reader reports the signature as intact and its issuer as untrusted, and everyone who receives one is told so before they can open it. |
test_key_cannot_read_documents | 403 | no | A vsk_test_ key asked GET /api/v1/envelopes/{id}/documents, or a draft render of one of them, for an envelope that is not a sandbox envelope. A test key may read the STATUS of any envelope in its organisation — that emails nobody and spends no allowance — but the documents endpoint hands over the bytes of a signed contract, and a sandbox is meant to be rehearsable without ever touching something real, and a draft is those bytes with the answers so far drawn on. Fetch this envelope’s documents with a live key, or fetch the documents of an envelope a test key created. |
test_key_cannot_mint | 403 | no | A vsk_test_ key asked POST /api/v1/envelopes/{envelopeId}/recipients/{recipientId}/signing-url for an envelope that is not a sandbox envelope. A minted URL opens that signer’s session on the document, and on a live envelope that document is binding, so a test key may mint only on an envelope a test key created. Nothing was minted and nothing was recorded. Mint this one with a live key, or rehearse on an envelope created and sent with your test key. Retrying with the same key fails identically. |
webhook_url_invalid | 422 | no | The url is not one we will POST to: it is not an absolute URL, it is not https://, it carries credentials, or its host is written as a loopback, link-local or private address (localhost, 127.0.0.1, 10.0.0.5, 169.254.169.254, [::1] and the like). Deliveries are made from inside our network, so a private address would make this API a request-forgery tool. The check reads the host as written and does not resolve a name. Answered on registration and on a PATCH that changes url. Fix the URL; retrying it unchanged fails identically. |
webhook_events_invalid | 422 | no | events is missing on a registration, empty, not an array of strings, or names a type outside the published vocabulary. ⚠️ An unknown name is REFUSED rather than accepted and silently never delivered — that is how an integrator spends a week waiting for envelope.singed. The message lists every valid name. |
webhook_brand_unknown | 422 | no | brand_id names no brand of this organisation. ⚠️ 422 and not 404 because you asked for a WEBHOOK — the brand is a property of the body that does not fit. Send null to receive every envelope in the organisation. On registration, omitting it means the same; on a PATCH, omitting it leaves the endpoint’s filter as it was. |
webhook_limit_reached | 422 | no | This organisation already holds 10 endpoints, which is the maximum: every endpoint multiplies every event into another outbound request. If you are registering one per brand, use ONE endpoint with brand_id: null and route on the brand_id every event already carries. |
api_key_owner_removed | 403 | no | The person who created this key has left the organisation, and everything the key creates or sends — an envelope, a batch, a template — is attributed to that person, who must still be a member. The key is not revoked and its reads still work, and so does managing webhooks; mint a new key from a current member and use that. |
recipient_not_embedded | 422 | no | This recipient was created without "embedded": true, so they have been emailed a link and no URL can be minted for them. ⚠️ THE REMEDY IS NOT ON THIS ENDPOINT: embedding is declared when the envelope is created, because it decides how a human is reached, and an envelope that has gone out has already reached them. Create the next one with the flag set. |
recipient_not_yet_turn | 409 | Do not poll. Wait for the recipient.completed webhook for the position in front, then retry once. | A sequential envelope is holding this recipient back until everybody at an earlier position has finished. Nothing about the request is wrong and the identical call will succeed later, which is why it is a 409 rather than a 422 — the same answer Dropbox Sign gives. |
recipient_cannot_sign | 409 | no | The recipient has signed, approved, declined, or been superseded or delegated away — or another member of their signing group signed first (group_resolved). Every status that produces this has no outgoing edge, so a retry cannot help and a fresh URL would open onto the same nothing. A signature or an approval is announced by the recipient.completed webhook, and a decline that ends the envelope by envelope.declined — a member of a signing group who declines does not end it while another member can still sign. ⚠️ A group_resolved recipient gets no recipient.completed of their own: the one that fired names the member who signed. A recipient who delegated or was superseded has a replacement: read it from GET /api/v1/envelopes/{envelopeId}. |
envelope_not_sent | 422 | no | This operation needs an envelope that is out for signing, and this one is not: it is still a draft, or it is finished (completed, declined, voided or expired). Two operations answer it. Minting a signing URL: a draft has not been sent to anybody — create it with "send": true, or send it first. Voiding: a draft has nothing to withdraw, because nobody has been asked to sign it, and a finished envelope cannot be withdrawn. The message says which state it is in. ⚠️ A vsk_test_ key CAN reach an open envelope: it may send one when everybody it would email is a member of your organisation or a confirmed test recipient, and an "embedded": true recipient is never emailed, so is never checked. A test key meets this code for the same reasons a live key does — most often, a draft that was never sent — except that when minting, on an envelope a test key did not create, it is refused first with test_key_cannot_mint. |
envelope_not_draft | 409 | no | Only a draft can be sent, and this envelope has already left draft — through this API, through the sender’s own Send button, or by being voided. ⚠️ THE EXACT OPPOSITE OF envelope_not_sent, and the two are not interchangeable: that one means the envelope has NOT gone out. An envelope never returns to draft, so this is terminal — read it with GET /api/v1/envelopes/{envelopeId} to see where it got to. ⚠️ If you were retrying a request that timed out, send the SAME Idempotency-Key instead and you will be told what the first attempt did. |
brand_has_no_embed_origins | 422 | no | The brand has no embedding origins registered, so a signing URL for it could not be framed by anything — and an embedded recipient is never emailed, so they would be unreachable. Register the domains you embed on against that brand in Settings; they must be bare origins (https://example.com, no trailing slash and no path). ⚠️ THE BRAND IS THE ONE THE ENVELOPE GOES OUT UNDER — the brand_id you sent, or the organisation’s default when you sent none. It is NOT always the default: registering an origin against the default while sending under another brand is the mistake this refusal most often means. |
recipient_cannot_be_embedded | 422 | no | A recipient was declared "embedded": true on a role the template routes as cc. A copied-in reader is never asked to sign and is never issued a signing credential, so an embedded one could be reached by nothing at all — no email, and no URL. Either drop the flag, or give the role a routing type that signs, in the template editor. |
batch_interrupted | 409 | Do not retry the batch. Retry the individual envelope this row names, with POST /api/v1/envelopes/{envelopeId}/send and its own Idempotency-Key. Immediately is fine; that endpoint refuses a second send by itself. | ⚠️ THIS CODE APPEARS INSIDE A 207 BODY, ON THE ROW IT IS ABOUT, AND NEVER ON A STATUS LINE. It means our own request died partway through a bulk send and you are being shown the batch as it actually stands: the envelopes that went out are sent, and this one is a draft that never was. It already carries its recipients and its values, so nothing needs rebuilding — send it. ⚠️ RETRYING THE BATCH IS THE WRONG MOVE: under the same key you are replayed this same answer, and under a new key you get a second hundred envelopes. |
brand_unknown | 422 | no | brand_id names no live brand of your organisation — it may belong to another account, or have been archived. Read the ids from Settings, or omit the field to send under the default. ⚠️ IT IS REFUSED RATHER THAN IGNORED because the branding an envelope went out under is frozen the moment it is sent: a wrong constant would put somebody else’s letterhead on every envelope you send, permanently, and nothing else would say so. (The product’s own send screen falls through to the default instead, because there the id came from a list a person was shown rather than from a program.) |
draft_not_available | 422 | no | There is no draft of this envelope to render, and the message says which of the two reasons it is. Either it is still a DRAFT — nobody has been asked to do anything, so the render would be your original document with nothing written on it — or it has already SEALED, and the final executed copy exists. ⚠️ ONE CODE FOR BOTH BECAUSE THE NEXT REQUEST IS THE SAME ONE: GET /api/v1/envelopes/{envelopeId}/documents serves the original in the first case and the sealed copy in the second. What differs is what you do with it — send the envelope, or take the executed contract — and that is what the sentence tells you. Retrying cannot help in either case: a sealed envelope never unseals, and a draft stays a draft until somebody sends it. ⚠️ A VOIDED, DECLINED OR EXPIRED envelope is NOT refused here — a contract that died part-signed is exactly the thing this endpoint exists to hand you. |