Documentation menu

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.
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 month's included agreements are spent on a plan that has no overage, or the account's own spending limit has been reached. Nothing is wrong with the request.
409conflictAn idempotency key reused for a different body, or the first attempt still running.
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. The message names the budget, the hint says when, and the Retry-After header says how many seconds.
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 a plan change, not a pause. It is the one refusal that a retry cannot fix: the allowance returns when the billing period turns, which may be three weeks away. 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.

Every successful send carries x-agreements-remaining when the plan has a fixed allowance: how many completed agreements are left in this billing period. A send that nobody signs costs nothing, so the number moves on completion rather than on the call — it is on the send's answer because that is the call an integration already makes. It is absent on plans that bill for overage, because on those there is no number 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 honours 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 25 agreements a month.

Get an API key