> ## 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.

# POST /generate

> Render a PDF from a JSON template and data. Synchronous — the response is the PDF.

Renders a PDF from a `template` and `data`, and returns the document bytes directly.

```
POST https://api.tipar.dev/generate
```

<Note>
  Synchronous: the response body is the finished PDF (`application/pdf`). No job, no polling.
</Note>

## Headers

<ParamField header="Authorization" type="string" required>
  `Bearer tipar_live_<key>`. See [Authentication](/authentication).
</ParamField>

<ParamField header="Content-Type" type="string" required>
  `application/json`.
</ParamField>

## Body

<ParamField body="template" type="object" required>
  The template document. Has a single key, `page`, describing the document. See [Template basics](/templates/overview) and the [full schema](/api-reference/template-schema).
</ParamField>

<ParamField body="data" type="object" required>
  The data the template interpolates against. Any JSON object. Every `{{path}}` in the template must resolve here.
</ParamField>

```json Request body theme={"dark"}
{
  "template": {
    "page": {
      "content": { "type": "text", "value": "Hello, {{name}}!", "style": { "fontSize": 24, "bold": true } }
    }
  },
  "data": { "name": "world" }
}
```

## Response

<ResponseField name="200 OK" type="application/pdf">
  The rendered PDF, as raw bytes. There is no JSON wrapper — write the body straight to a file or stream it to your client.
</ResponseField>

<Note>
  **Every plan returns clean, unbranded PDFs** — Free and Pro alike. (The anonymous [playground](/api-reference/playground) is always watermarked with a diagonal `tipar.dev` stamp; that's a demo backstop for the no-key endpoint, not a plan feature.)
</Note>

```bash theme={"dark"}
curl https://api.tipar.dev/generate \
  -H "Authorization: Bearer $TIPAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d @request.json \
  --output document.pdf
```

## Errors

| Status | `code`                | When                                                                                             |
| ------ | --------------------- | ------------------------------------------------------------------------------------------------ |
| `400`  | —                     | Malformed JSON, a missing/unknown node `type`, or a body that omits `template`.                  |
| `401`  | —                     | Missing, malformed, or revoked API key. Carries `WWW-Authenticate: Bearer`.                      |
| `402`  | `quota.exceeded`      | Plan's monthly document quota is reached.                                                        |
| `413`  | —                     | Request body exceeds 4 MB.                                                                       |
| `422`  | `template.*`          | Template is well-formed JSON but unusable — structural error, missing data, or a render failure. |
| `429`  | `rate_limit.exceeded` | More than 60 req/min (burst 120) on this key. Carries `Retry-After`.                             |

Each non-2xx response is a [Problem Details](/api-reference/errors) document. The full breakdown — including every `422` `template.*` code and example bodies — is on the [errors page](/api-reference/errors).

<Warning>
  `422` is the one to handle in template development: it means the template reached the renderer but couldn't produce a document. The response lists **every** problem at once (all missing data paths, all structural errors), so you can fix them in a single pass. See [Errors → 422](/api-reference/errors#422-unprocessable-entity).
</Warning>

## Full example

A complete invoice request, in a few languages:

<CodeGroup>
  ```bash curl theme={"dark"}
  curl https://api.tipar.dev/generate \
    -H "Authorization: Bearer $TIPAR_API_KEY" \
    -H "Content-Type: application/json" \
    -d @invoice-request.json \
    --output invoice.pdf
  ```

  ```javascript Node.js theme={"dark"}
  import { writeFile } from "node:fs/promises";

  const res = await fetch("https://api.tipar.dev/generate", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.TIPAR_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ template, data }),
  });

  if (!res.ok) {
    const problem = await res.json();
    throw new Error(`Tipar ${res.status}: ${problem.title}`);
  }
  await writeFile("invoice.pdf", Buffer.from(await res.arrayBuffer()));
  ```

  ```python Python theme={"dark"}
  import os, requests

  res = requests.post(
      "https://api.tipar.dev/generate",
      headers={"Authorization": f"Bearer {os.environ['TIPAR_API_KEY']}"},
      json={"template": template, "data": data},
  )
  if res.status_code != 200:
      raise RuntimeError(f"Tipar {res.status_code}: {res.json()['title']}")
  with open("invoice.pdf", "wb") as f:
      f.write(res.content)
  ```
</CodeGroup>

See the [invoice example](/examples/invoice) for a full `template` + `data` pair to drop in.
