Docs

Docs / Reference / Errors

Errors

One shape, always.

{
  "error": {
    "code": "invalid",
    "message": "no value for guest_name",
    "hint": "A contract that ships with a placeholder still in it is worse than one that failed to send."
  }
}

The message is frequently the database's own sentence, passed through rather than replaced. It is more specific than anything this layer could write, and the layer that knows why something was refused is the layer that refused it.

StatusCodeMeans
400bad_requestThe request could not be read at all.
401unauthorizedNo key, or a key that is not live. Unknown, revoked and expired all answer the same way — a caller who could tell them apart could enumerate.
403forbiddenThe key is live and its scopes do not carry this.
403terms_not_acceptedThe business was asked to agree to SignSealer's updated terms and thirty days have passed without it, so nothing new can be sent until an owner or admin agrees to them in the dashboard. Documents already sent can still be signed and everything can still be read. Retrying will not help until then.
403assertion_rejectedYour application's approval (POST /v1/signing-transactions) does not hold: its signature does not verify with the key it names, the key or application is not live, it was made for another business, it is out of its time, or it names a person, a line or a signature that is not the one bound or approved. The message says which. Nothing was signed.
404not_foundNo such thing, in this account. A thing in another account answers the same way, and deliberately: confirming it exists would be the leak.
405method_not_allowedThat path exists under another method. Said separately from 404 because it saves an hour.
402quota_exceededThe account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request.
409conflictAn idempotency key reused for a different body, or the first attempt still running; or the thing named has moved on, such as a template revised from a version that is no longer the newest. The message says which, and the hint names the current one.
409template_changedA start named the template version it showed (expected_template_version), and a newer one has been published since. Nothing was made: show the person the current wording.
409paragraphs_changedA signature named the conditional paragraphs its page drew (paragraphs), and the first signer's answers now call for different ones. Nothing was signed: fetch the link again and show the paragraphs it has now.
409document_changedThe text is not the text the person approved (document_sha256). Nothing was signed: show them the document as it is now and ask them to approve it again.
409assertion_replayedAnother approval already used this approval id (jti). Every approval has its own; the same approval sent again unchanged answers with the signing it made.
413too_largeLarger than a signing request ever is.
422invalidThe body did not validate, or the engine refused it. The message is the engine's own sentence.
429rate_limitedToo many attempts, or a business past the signing links its standing allows in an hour or a day (Limits and paging). The message names the budget, the hint says when, and the Retry-After header says how many seconds.
429busyA photo on a signing link was not read because others are being read just now. Nothing was kept: send the same photo again after the Retry-After header's seconds.
429allowance_reachedA tablet's start made with no network, synced when the business cannot take another signing (its free signings for the month used, a balance that does not cover the next, its own spending limit, or a stopped subscription). Nothing was made: keep it and send it again; the Retry-After header says when. Any other start is answered 402 quota_exceeded.
500internalOurs. Quote the x-request-id.

Retrying

Retry 429 and 500, with backoff. Do not retry 422 — the request will be refused the same way every time, and the message says why. Retrying anything that creates something means sending an idempotency key; our own SDK mints one for you.

402 is not 429

A 402 means the account needs money or the membership, not a pause: Free's 10 signings are used, the prepaid credit has run out, or the business's own spending limit is reached. It is the one refusal that a retry cannot fix: credit has to be added (automatic reload usually does it first), the membership started, or the month has to turn. Treat it as you would a declined card — surface it to the person who can act on it, and stop. The response carries the same shape as any other error, and the hint names the way out.

One start is answered otherwise: a counter tablet's, made with no network and sent when it synced. The person has already signed at the counter, so the answer is 429 allowance_reached, for the tablet to keep it and send it again. Nothing is made until the business can take another signing, and then it counts like any other.

Every successful send carries x-agreements-remaining when the account has a fixed number of signings left to count down: how many more documents it can send before it needs credit. A document out for signature holds its place, so the number goes down with the send that sent it, and comes back if that document is voided, declined or expires unsigned; one that is never signed costs nothing. It is on the send's answer because that is the call an integration already makes. It is absent when there is nothing to count down. Watch it and you will never meet a 402 unprepared — or subscribe to billing.quota_approaching and do not watch at all.

A 429 carries Retry-After, in seconds. Wait that long rather than backing off by a guess: the rate-limit window is a minute, so three quick retries all land inside the same window, fail identically, and spend the next window's budget as well. The Node client honors the header — it sleeps through a short wait and hands a long one back to you as error.retryAfter rather than blocking your call for a minute.

Ready to build? An API key takes a minute in the portal, and the free plan covers the first 10 agreements a month.

Get an API key