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.
| Status | Code | Means |
|---|---|---|
| 400 | bad_request | The request could not be read at all. |
| 401 | unauthorized | No 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. |
| 403 | forbidden | The key is live and its scopes do not carry this. |
| 404 | not_found | No such thing, in this account. A thing in another account answers the same way, and deliberately: confirming it exists would be the leak. |
| 405 | method_not_allowed | That path exists under another method. Said separately from 404 because it saves an hour. |
| 402 | quota_exceeded | The 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. |
| 409 | conflict | An idempotency key reused for a different body, or the first attempt still running. |
| 413 | too_large | Larger than a signing request ever is. |
| 422 | invalid | The body did not validate, or the engine refused it. The message is the engine's own sentence. |
| 429 | rate_limited | Too many attempts. The message names the budget, the hint says when, and the Retry-After header says how many seconds. |
| 500 | internal | Ours. 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