Stable error codes mapped to HTTP statuses. Pin your error handling to the code, not the human-readable message.
Errors are JSON-bodied with a stable code string and a human-readable
error message. The error is for humans (logs, dashboards, oncall
chat) and may improve over time. The code is for code — pin your
catch logic to that.
{ "error": "Signer with role \"tenant\" has no [[ls:…:tenant]] anchor in the PDF", "code": "signer_has_no_anchor", "meta": { "role": "tenant" }}
meta carries structured context when relevant (which role failed,
which idempotency key collided, etc). The one exception is
ip_not_allowed, which carries the observed caller address as a
top-level ip field rather than inside meta — it is produced by the
authentication layer, before any route-level meta exists.
Bearer token missing, malformed (neither wsk_live_ nor lsk_live_), unknown, or revoked.
402
tier_required
Workspace not on a tier with API access, or the monthly document cap is reached (meta.tier, meta.cap, meta.used).
402
enterprise-required
The endpoint itself is Enterprise-only, whatever the plan's API access: template channels, QES, and minting embedded signing sessions. Sales-led — the message names the feature.
403
ip_not_allowed
The key is valid, but its IP allowlist is non-empty and the caller's address is not on it — or could not be determined. The body carries the address we saw as ip (null when unknown). An empty allowlist means unrestricted; see Restricting a key to your own IP addresses.
404
not_found
Resource doesn't exist, OR belongs to a different workspace than the key.
A field/anchor references a role no signer claims.
400
signer_has_no_anchor
A signer was passed but no anchor for their role exists in the PDF.
400
duplicate_anchor
Same signature/role appears twice on the same page.
400
no_anchors_found
placement="anchors" strict + the PDF has no extractable text or no markers. Switch to auto_append or explicit.
400
placement_retired
placement="manual" on POST /v1/signing-requests — retired 2026-07-31, refused before any document is minted. Use anchors, explicit coordinates or auto-append.
410
placement_retired
Any call to POST /v1/documents — retired 2026-07-31 with the manual flow; file_url on POST /v1/signing-requests covers ingest-by-URL.
422
placement_failed
pdf-lib threw while masking anchors / appending the signature page.
instantiate: a slot that has fields was given no recipient (meta.slots).
404
version_not_found
instantiate pinned a version that has no snapshot (meta.version, meta.current_version).
422
template_input_invalid
One or more field_values failed validation; problems[] lists every one with its own code (field_required, invalid_enum, …). See Templates → Validation.
409
not_staged / discarded / already_confirmed
Confirm or discard called on an instance that is not (or no longer) staged.
410
review_expired
The 14-day review window closed.
422
invalid_recipients
Bad email, non-E.164 phone, SMS without a number, duplicate signing_order on confirm.
502
render_failed
The PDF could not be rendered on confirm; nothing was sent.
503
review_unavailable
Staging a generated file is not available on this deployment.
origin on POST /v1/embedded/sign-sessions is not a plain https origin — it carried a path, query, fragment, credentials or a wildcard, or used http:.
400
origin_not_allowed
The origin is well-formed but is not registered on this API key. Add it under Settings → API, "Allowed embed origins". The message lists what is registered.
409
request_not_signable
The signing request is not pending/viewed, or is past its own expiry, so there is nothing to frame.
503
embedding_unavailable
This deployment has not applied migration 0155 yet. Transient by definition — retry, or contact support.
ttl_seconds outside 60–900 and an unknown locale are refused as
400 invalid_request rather than silently clamped or defaulted. See
Embedded signing.