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

# Errors

> Every status code Tipar returns, the machine-readable codes, and how to react.

Errors are [RFC 7807](https://datatracker.ietf.org/doc/html/rfc7807) Problem Details documents (`application/problem+json`). The shape:

```json theme={"dark"}
{
  "title": "Human-readable summary",
  "status": 422,
  "detail": "More specific detail, usually the first error's message",
  "errors": [
    { "code": "template.too_complex", "message": "…" }
  ]
}
```

Branch on **`status`** and on the **`code`** inside `errors`. The `code` strings are stable; `title`/`detail`/`message` are for humans and may be reworded. (Responses also carry a `type` URI, per the RFC 7807 default for the status code.)

## Status codes at a glance

| Status | Meaning                         | `code`(s)             | `errors` array? |
| ------ | ------------------------------- | --------------------- | --------------- |
| `400`  | Malformed or incomplete request | —                     | no              |
| `401`  | Authentication failed           | —                     | no              |
| `402`  | Monthly quota reached           | `quota.exceeded`      | yes             |
| `413`  | Request body too large          | —                     | no              |
| `422`  | Template unusable               | `template.*`          | yes             |
| `429`  | Rate limit / playground cap     | `rate_limit.exceeded` | yes             |
| `500`  | Unexpected server error         | —                     | no              |

## 400 Bad Request

Two cases, distinguished by `title`:

**The body isn't valid JSON, or can't be parsed into a template.** Malformed JSON, wrong field types, or a missing/unknown node `type` — an unknown `type` is rejected here by the parser, before validation. `detail` carries the parser's message.

```json theme={"dark"}
{ "title": "Invalid request body", "status": 400, "detail": "Unknown node type: 'foo'." }
```

**The body is valid JSON but omits the `template` field.**

```json theme={"dark"}
{ "title": "Invalid request", "status": 400, "detail": "Request body must include 'template' and 'data'." }
```

Neither carries an `errors` array. Fix the request shape and retry.

<Note>
  A missing **`data`** is *not* a 400 — `data` defaults to empty, so the request proceeds and any unfilled `{{tokens}}` surface as a [`422` missing-data](#422-unprocessable-entity) (or it renders, if the template has no tokens).
</Note>

## 401 Unauthorized

The API key is missing, malformed, or revoked. The response carries a `WWW-Authenticate: Bearer` header; the body is a Problem Details document **without an `errors` array**, so branch on the **status code**. The request never reaches the renderer, so no quota or rate budget is spent.

See [Authentication](/authentication#when-authentication-fails).

## 402 Payment Required

Your plan's monthly document quota is used up. The request is **blocked, not billed** — Tipar never charges overage you didn't choose.

```json theme={"dark"}
{
  "title": "Monthly quota exceeded",
  "status": 402,
  "detail": "Monthly document quota reached for your plan. Upgrade to keep generating.",
  "errors": [
    { "code": "quota.exceeded", "message": "Monthly document quota reached for your plan. Upgrade to keep generating." }
  ]
}
```

Upgrade your plan in the [dashboard](https://app.tipar.dev/billing), or wait for the quota to reset at the start of the month. Quota sizes are on the [pricing page](https://tipar.dev/pricing).

## 413 Payload Too Large

The request body exceeded the cap — **4 MB** for `/generate`, **256 KB** for the playground. Almost always an oversized base64 [image](/templates/images); downscale it before encoding.

## 422 Unprocessable Entity

The body was valid JSON and well-formed, but the template couldn't produce a document. **This is the error you'll meet while building templates.** Every problem is reported at once, so one response shows everything to fix.

The `code` tells you which kind:

<Tabs>
  <Tab title="Missing data">
    A `{{path}}` in the template has no matching value in `data`. One entry per missing path; the path is in `message`.

    ```json theme={"dark"}
    {
      "title": "Template references missing data",
      "status": 422,
      "errors": [
        { "code": "template.missing_data", "message": "invoice.total" },
        { "code": "template.missing_data", "message": "customer.name" }
      ]
    }
    ```

    Fix: add the missing fields to `data`, or correct the path in the template.
  </Tab>

  <Tab title="Structural">
    The template breaks a rule — a missing required field, a cell count that doesn't match the column count, a bad enum or colour, or a negative size. The `code` is `template.` followed by the path to the offending field. *(A missing or unknown node `type` is caught earlier, by the parser — that's a [400](#400-bad-request), not a 422.)*

    ```json theme={"dark"}
    {
      "title": "Template is structurally invalid",
      "status": 422,
      "errors": [
        { "code": "template.page.content.body.row", "message": "body has 2 cell(s), columns has 3" }
      ]
    }
    ```

    A template with more than **10,000 nodes** is also rejected here (code `template.page`) as too complex. Fix: correct the structure the `message` points at, or reduce the node count.
  </Tab>

  <Tab title="Render failure">
    The template was structurally valid but the renderer couldn't lay it out or read its content.

    ```json theme={"dark"}
    {
      "title": "Template is structurally invalid",
      "status": 422,
      "errors": [
        { "code": "template.too_complex", "message": "Template could not be rendered — an element doesn't fit the page, or the layout doesn't converge." }
      ]
    }
    ```

    | `code`                     | Cause                                                                                                                        |
    | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
    | `template.too_complex`     | An element can't fit the page (e.g. a `constant` width wider than the page), or the layout won't converge.                   |
    | `template.invalid_content` | Content the renderer can't process — most often an [image](/templates/images) that isn't valid base64 of a supported format. |
  </Tab>
</Tabs>

<Note>
  Render failures surface as `422`, **not** `500`. A genuine `500` from `/generate` is rare and should be treated as a bug — please report it.
</Note>

## 429 Too Many Requests

You exceeded a [rate limit](/api-reference/overview#rate-limits). The response carries a `Retry-After` header (seconds).

```json theme={"dark"}
{
  "type": "https://tools.ietf.org/html/rfc6585#section-4",
  "title": "Too Many Requests",
  "status": 429,
  "detail": "Rate limit exceeded. Slow down and retry after the Retry-After period.",
  "errors": [
    { "code": "rate_limit.exceeded", "message": "Rate limit exceeded." }
  ]
}
```

On the [playground](/api-reference/playground), the same status carries a message about the daily cap instead. Wait `Retry-After` seconds and retry.

## 500 Internal Server Error

An unexpected server-side failure. Recoverable template and render problems are reported as `4xx` (mostly `422`), so a `500` is unusual — retry, and if it persists, [check status](https://status.tipar.dev) or report it.

## Handling errors well

<CardGroup cols={2}>
  <Card title="Branch on code, show message" icon="code-branch">
    Switch on `status` and `errors[].code` in code; surface `message`/`title` to humans.
  </Card>

  <Card title="Respect Retry-After" icon="clock">
    On `429`, wait the header's seconds before retrying rather than hammering.
  </Card>

  <Card title="Treat 422 as a dev-time signal" icon="hammer">
    Missing-data and structural `422`s mean a template/data mismatch — fix the template, not the runtime.
  </Card>

  <Card title="Don't retry 4xx blindly" icon="ban">
    `400`/`401`/`402`/`422` won't succeed on retry without a change. Only `429` (and transient `500`) are worth retrying as-is.
  </Card>
</CardGroup>
