Errors
Every error the E-Signing API can return, with a stable machine-readable code you can branch on.
Every error response has the same shape. Read error.code — it's a stable, machine-readable value.
The message is for humans and may change at any time, so never branch on the message text.
{
"error": {
"code": "unknown_signer_role",
"message": "One or more recipient roles do not match this template.",
"details": { },
"reference": "0190f3a2-7b6c-7e21-9a3f-2c9b1d4e5f60"
}
}| Field | Always present | What it's for |
|---|---|---|
code | Yes | The stable value to branch on. Every possible value is listed below. |
message | Yes | Human-readable explanation. May change without notice. |
details | No | Structured context, when we have any — see below. |
reference | No | A support id for failures we log server-side (5xx and 422). |
details
Present only when there's something useful to say. Two cases are worth handling explicitly:
{
"error": {
"code": "insufficient_scope",
"message": "This API key is missing the required scope \"envelopes:send\".",
"details": { "required_scope": "envelopes:send", "granted_scopes": ["templates:read"] }
}
}{
"error": {
"code": "rate_limited",
"message": "Too many requests. Slow down and retry after the indicated interval.",
"details": { "retry_after": "15 minutes" }
}
}Back off on the Retry-After header, not details.retry_after
details.retry_after is a human-readable string ("15 minutes", "42 seconds") meant for log
lines and error messages — don't parse it. Every 429 also carries the standard
Retry-After response header, which is an integer number of seconds. Use the header for
your backoff.
reference
A UUID identifying the server-side log entry for this failure. It appears on the errors we persist
(5xx and 422) and is absent on auth and validation errors that carry no server-side log.
Log it. When you report a problem, quoting the reference lets us find the exact failure in one
lookup instead of asking you to reproduce it.
Error codes
| HTTP | error.code | Meaning |
|---|---|---|
401 | missing_api_key | No X-API-Key header on the token request. |
401 | invalid_api_key | The key is unknown, revoked, expired, or not a live-mode key. |
401 | missing_token | No Authorization: Bearer token was sent. |
401 | invalid_token | The token is malformed or expired. Request a new one. |
401 | key_revoked | The key behind this token was revoked — its tokens no longer work. |
403 | insufficient_scope | The key doesn't carry the scope this endpoint requires. |
403 | forbidden | The key's owner lacks the underlying permission, or the feature (API access, webhooks) isn't in your plan. |
403 | signing_links_disabled | The envelope doesn't share signing links with the sender. |
402 | quota_exceeded | No remaining API signature requests (on send). |
403 | quota_exceeded | A resource limit was reached — the contact limit, or the webhook endpoint limit. |
404 | template_not_found | No template with that id exists in your company. |
404 | envelope_not_found | No envelope with that id exists in your company. |
404 | webhook_not_found | No webhook subscription with that id exists in your company. |
422 | unknown_signer_role | A recipient role doesn't match the template, or a locked role was supplied. |
422 | sender_not_found | sender_email isn't an active user with an eSignature seat in your company. |
422 | sender_fields_incomplete | The template's own sender ("Me") has required fields with no value, so the envelope can't go out. |
400 422 | validation_failed | The request body is malformed, missing a required field, over a length limit, or contains an unknown placeholder or a disguised link. |
429 | rate_limited | Too many requests — back off and retry. |
5xx | send_failed | The envelope was created but couldn't be sent, so it was discarded. Nothing was delivered. |
500 | internal_error | Something went wrong on our side. Safe to retry. |
Why some errors are deliberately vague
On the token endpoint, "no such key", "revoked", "expired", and "not live-mode" all return the same
401 body. Likewise, a template in another company and a template that doesn't exist both return
404 template_not_found. This is intentional — a more specific error would let someone probe which
ids or keys exist.
Fixing sender_fields_incomplete
This one surprises people, because the request is perfectly valid. It means the template has fields assigned to its own sender role ("Me") that are required and empty — so there is no complete document to send.
The API fills the sender's fields automatically from what's stored on the template; it cannot invent values. Fix it on the template, not in your request:
- give those sender fields a value on the template, or
- mark them optional, or
- remove them.
Then retry the same call.
Handling errors well
- Branch on
error.code, never onmessage. - Retry
5xx,429, and network errors with backoff, honouring theRetry-Afterheader on a429. Don't retry other4xx— the request reached us and was rejected; fix the request instead. - Refresh on
401 invalid_tokenby exchanging your key for a new token, then retry once. If you getkey_revoked, the key itself was revoked — load the replacement key rather than retrying. - Treat
quota_exceededas a plan signal, not a transient error — it clears when the period resets or your plan is upgraded. Retrying won't help, and on send it means nothing was delivered. send_failedmeans nothing went out. The envelope is deleted, so a retry creates a fresh one rather than duplicating a delivery.- Log
referencewhenever it's present.