> ## Documentation Index
> Fetch the complete documentation index at: https://docs.craftkit.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Errors

> The error envelope, the codes you'll encounter, and retry semantics.

## Envelope

Every error response is JSON with an `error` object:

```json theme={null}
{
  "error": {
    "code": "invalid_input_data",
    "message": "Variable data did not match the template manifest.",
    "issues": { "fieldErrors": { "customer.name": ["Required"] } }
  }
}
```

* `code` — stable, machine-readable. Branch on this.
* `message` — human-readable; do not parse.
* `issues` — present on validation errors; a Zod `flatten()` with `fieldErrors`/`formErrors`.

## Codes

| Status | code                   | Meaning                                                          | Retry?           |
| ------ | ---------------------- | ---------------------------------------------------------------- | ---------------- |
| 400    | `invalid_json`         | Body is not valid JSON.                                          | No               |
| 400    | `invalid_request`      | Body failed schema validation.                                   | No               |
| 400    | `invalid_input_data`   | `data` did not match the manifest. Inspect `issues.fieldErrors`. | No               |
| 401    | `unauthorized`         | Missing, invalid, revoked, or wrong-environment key.             | No               |
| 403    | `forbidden`            | Key's project was not found.                                     | No               |
| 404    | `template_not_found`   | No such template in this key's project.                          | No               |
| 404    | `version_not_found`    | Pinned `versionNumber` does not exist.                           | No               |
| 404    | `not_found`            | Render (or other resource) not found in this project.            | No               |
| 409    | `no_published_version` | Template has no published version.                               | After publishing |
| 409    | `not_ready`            | Share requested before the render succeeded.                     | After completion |
| 503    | `queue_unavailable`    | Render queue temporarily unreachable.                            | Yes — backoff    |
| 500    | `internal`             | Unexpected server error.                                         | Yes — backoff    |

## Retry semantics

* **Never retry 4xx** except where noted — the request will fail again unchanged. Fix the input.
* **Retry 503 / 5xx** with exponential backoff (e.g. 1s → 2s → 4s → 8s → 16s, cap \~30s, \~5 attempts).
* For render submission, pair retries with an [`Idempotency-Key`](/guides/idempotency) so a retried
  submit never produces a duplicate document.

```ts theme={null}
async function craftkit(url: string, init: RequestInit) {
  const res = await fetch(url, init);
  if (res.ok) return res.json();
  const { error } = await res.json();
  switch (error.code) {
    case 'template_not_found':
      throw new Error('Wrong slug or wrong project key');
    case 'no_published_version':
      throw new Error('Publish a template version first');
    case 'invalid_input_data':
      throw new Error(`Data mismatch: ${JSON.stringify(error.issues?.fieldErrors)}`);
    case 'queue_unavailable':
    case 'internal':
      return backoffRetry(url, init); // your backoff wrapper
    default:
      throw new Error(`${error.code}: ${error.message}`);
  }
}
```
