# Errors, retries and limits

## Errors

Every error is `{ "error": { "kind", "message", "requestId", "retryable" } }`. `kind` is one of `invalid`, `unauthenticated`, `forbidden`, `not_found`, `conflict`, `rate_limited`, `dependency`, `timeout`. Quote `requestId` when you ask for help.

| Status | Usually means |
|---|---|
| 400 | A field is not valid; `message` says which and why |
| 401 | The key is missing, wrong, revoked or lacks the scope (one answer for all four) |
| 403 | The workspace's plan or a setting does not allow this |
| 404 | No such document, template or seat |
| 409 | The request is fine but the state is wrong (for example, voiding a finished document) |
| 429 | Over the rate limit; wait the `Retry-After` seconds |
| 5xx | Our side; safe to retry with the same `Idempotency-Key` |

## Idempotency

Requests that create a document or a template (send a document, make a template from HTML, make a template from a file) need an `Idempotency-Key` header of 8 to 200 characters. Repeating a request with the same key and the same body returns the first answer instead of doing it again. Reusing a key with a different body is an error. Other requests, such as a reminder, a void or an embedded session, do not need one. The SDKs make a key for you and reuse it on every retry.

## Rate limits

Each key has its own per-minute budget; over it you get `429` with a `Retry-After` header. See [API limits](#api-limits). Sign-in, public signing, download and verification pages also have a per-address limit shared by every server of the install.

## What counts as a document

A document is a **sent** document. A package of several documents sent together counts once. Drafts, signers, reminders, resends and test-mode documents never count.

The count is per calendar month in UTC and resets on the 1st.

- **Free:** 15 documents a month. At the limit, sending is blocked with a `403` whose message says the plan allows 15 documents a month and to upgrade to Pro. Nothing is ever billed for going over: there are no overage bills.
- **Pro:** no monthly document limit.

## Email limits

Each workspace can send 25 emails a day on Free and 500 a day on Pro. Signing requests, reminders and completed copies sent to signers count. Alerts to the workspace owner, sign-in emails and the usage warning emails do not count and are never blocked. A self-hosted install has no limit. The day is a UTC day and the count resets at midnight UTC. When the limit is reached the send is refused with a plain message.

If you send from your own connected mailbox or your own verified domain, the From address uses your provider's sending. That option is in **Settings → Email**; see [Email setup](/docs/email-setup.md).

## API limits

Each API key may make 600 requests per minute. Over that, the API answers `429` with a `Retry-After` header (in seconds). Nothing is lost if you wait and retry. Retried `POST` requests with the same `Idempotency-Key` never send twice. There are no per-call or per-API-document fees on any plan.

## Fair use on Pro

Pro is unlimited for one organisation's own documents. If your use becomes far above normal (many thousands of documents a month) we will contact you first and agree a plan. We do not bill extra charges automatically.

## Usage and alerts

The Billing and Reports pages show documents sent this month and emails sent today. We email the workspace owner once when 80% and once when 100% of the Free monthly document limit is used (the 12th and the 15th document), and once a day when 80% and 100% of the daily email limit is used (Free: the 20th and the 25th email; Pro: the 400th and the 500th).

`GET /api/v1/usage` returns the same numbers. It needs an API key with `documents:read`.

```bash
curl https://app.docustay.app/api/v1/usage -H "Authorization: Bearer $DOCUSTAY_KEY"
```

```json
{
  "plan": "free",
  "period": { "start": "2026-10-01T00:00:00Z", "end": "2026-11-01T00:00:00Z" },
  "documents": { "used": 4, "limit": 15 },
  "emails": { "usedToday": 6, "limitPerDay": 25, "resetsAt": "2026-10-07T00:00:00Z" }
}
```

## Pagination

Lists take `limit` (1 to 200) and `cursor`. The answer carries `page.nextCursor` (null on the last page).

## Upload limits

Files uploaded for signing are limited to 25 MB on Free and 100 MB on Pro.

## A retry loop that is safe

Always send the same `Idempotency-Key` on every retry of one logical request, and wait for `Retry-After` on a `429`:

```js
async function sendWithRetry(body) {
  const key = crypto.randomUUID();
  for (let attempt = 0; attempt < 5; attempt += 1) {
    const r = await fetch("https://docustay.app/api/v1/documents/send", {
      method: "POST", body: JSON.stringify(body),
      headers: { Authorization: `Bearer ${process.env.DOCUSTAY_KEY}`, "Idempotency-Key": key, "Content-Type": "application/json" },
    });
    if (r.status !== 429 && r.status < 500) return r;
    await new Promise((ok) => setTimeout(ok, (Number(r.headers.get("retry-after")) || 2 ** attempt) * 1000));
  }
  throw new Error("still failing after five tries");
}
```

## Reading an error

```json
{ "error": { "kind": "invalid", "message": "Parties: signer_2 is not a seat on this template.", "requestId": "req_123", "retryable": false } }
```

`retryable: false` means sending the same thing again will fail the same way: fix the request. `retryable: true` means try again with the same key.
