# API error codes and what they mean

> The SignWith API uses conventional HTTP status codes, and every error response has the same small envelope with a stable code your program can branch on.

Source: https://signwith.co/docs/api/errors · Updated 2026-10-02

## The error envelope

Every `4xx` and `5xx` response looks like this:

```json
{
  "error": {
    "code": "invalid_signer",
    "message": "Signer 2 needs an email or phone"
  }
}
```

- `code` is stable. Branch on it in your code.
- `message` is for people. It may change, so don't parse it; show it or log it.

## All error codes

| HTTP | Code | Meaning |
| --- | --- | --- |
| 400 | `invalid_json` | The request body is not valid JSON. |
| 401 | `unauthenticated` | The API key is missing, malformed or revoked, or its account has been archived. |
| 401 | `api_key_expired` | The API key has passed its expiry date. Create a new one. |
| 402 | `insufficient_credits` | Your account doesn't have enough credits to send this document. See [Buying credits](#section/Buying-credits). |
| 403 | `read_only_api_key` | A read-only key was used for a write request. |
| 403 | `insufficient_scope` | MCP only: the AI app was connected with **View only** access and tried to change something. |
| 403 | `not_available` | MCP only: the tool isn't offered to this app (credit purchase tools in ChatGPT). |
| 403 | `forbidden` | The key's user doesn't have access to this resource. |
| 404 | `not_found` | The resource doesn't exist or belongs to another account, or no endpoint matches the method and path. |
| 409 | `idempotency_key_in_use` | Another request with the same `Idempotency-Key` is still running. |
| 422 | `idempotency_key_reused` | The `Idempotency-Key` was already used with a different request body. |
| 422 | `missing_parameter` | A required parameter is missing. |
| 422 | `invalid_parameter` | A parameter has an unsupported value (e.g. `status`, `signing_order`, `expires_at` not an ISO 8601 time in the future, `prefill`/`metadata` not an object, invalid signer email on update). |
| 422 | `invalid_request` | The request is well-formed but can't be processed (e.g. invalid prefill value). |
| 422 | `missing_documents` | A template was created without any documents. |
| 422 | `too_many_documents` | More than 10 documents were sent for one template. |
| 422 | `document_too_large` | A document is larger than 25 MB. |
| 422 | `invalid_document` | A document is empty, could not be downloaded, or is not valid base64 / PDF. |
| 422 | `invalid_file_type` | A document is not a PDF or an image. |
| 422 | `invalid_field` | A template field in `fields` is invalid (unknown type, missing options or areas, page or coordinates out of range, too many roles). |
| 422 | `template_has_no_fields` | The template has no fields to fill in or sign yet. Add fields with `PATCH /templates/{id}` or in the editor. |
| 422 | `template_archived` | The template is archived and can't be sent. Duplicate it or pick another one. |
| 422 | `pdf_encrypted` | The PDF is password protected and no `password` was sent. |
| 422 | `missing_signers` | A signature request has no usable signers. |
| 422 | `invalid_signer` | `signers` isn't a list of objects, a signer has no email or phone or an invalid email, has an unknown role, a role is given twice, or there are more signers than roles. |
| 422 | `already_completed` | The signature request is already fully signed. |
| 422 | `not_open` | The signature request was canceled, declined or has expired, so it can't be reminded and its signers can't be changed. |
| 422 | `nothing_to_remind` | No signer is currently waiting to sign. |
| 422 | `signer_finished` | The signer has already signed or declined. |
| 422 | `invalid_discount_code` | The discount code is invalid, used up, expired or not valid for this credit pack. |
| 422 | `already_purchased` | The user already has the lifetime deal. |
| 422 | `billing_details_required` | The user must add a billing country in SignWith before buying credits. |
| 429 | `rate_limited` | Too many requests (or more than 20 feedback messages in an hour). Wait for `Retry-After` seconds when present. |
| 429 | `remind_too_soon` | Signers of this request were reminded less than an hour ago, or (`resend: true`) this signer was emailed less than 10 minutes ago. |
| 500 | `internal_error` | Something went wrong on SignWith's side. Retry (with the same `Idempotency-Key` for `POST`s); contact support if it persists. |
| 502 | `payment_provider_error` | The payment provider couldn't start the checkout. Try again later. |

## Handling errors in practice

- **`402 insufficient_credits`** is the one most integrations should handle in the product: tell the user, or buy credits from the API and retry. See [credits and billing](https://signwith.co/docs/api/credits).
- **`422` codes** mean the request itself needs fixing. The message names the parameter, signer or field, for example `Signer 2 has role "Buyer", but the template's roles are: Tenant, Landlord`.
- **`429`, `409` and `5xx`** are temporary. [Retry them safely](https://signwith.co/docs/api/requests#retrying-safely) with the same `Idempotency-Key`.
- **`401` and `403`** are about the key. See [authentication](https://signwith.co/docs/api/authentication).

Each endpoint in the [API reference](https://signwith.co/docs/api) lists the statuses it can return.
