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

# API reference

> Base URL, authentication, content types, errors, rate limits, and quotas — the things that apply to every endpoint.

The Tipar API is a small, synchronous HTTP API. There is one endpoint you'll use in production — [`POST /generate`](/api-reference/generate) — plus a public [playground](/api-reference/playground) endpoint and a health check.

## Base URL

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

All requests are HTTPS. `.dev` is on the HSTS preload list, so plain HTTP is never served.

## Authentication

`POST /generate` requires an API key sent as a bearer token. See [Authentication](/authentication).

```bash theme={"dark"}
Authorization: Bearer tipar_live_<32 characters>
```

## Requests and responses

* **Request bodies are JSON** (`Content-Type: application/json`).
* **A successful generate response is the PDF itself** — `Content-Type: application/pdf`, the raw bytes as the body. There's no JSON envelope around it.
* **The API is synchronous.** You get the finished PDF in the response to your request. There's no job to create, no status to poll, no webhook to wait for.

## Endpoints

| Method | Path                                                   | Auth    | Purpose                                                           |
| ------ | ------------------------------------------------------ | ------- | ----------------------------------------------------------------- |
| `POST` | [`/generate`](/api-reference/generate)                 | API key | Render a PDF from `template` + `data`.                            |
| `POST` | [`/v1/playground/generate`](/api-reference/playground) | none    | Anonymous, watermarked render for trying things out.              |
| `GET`  | `/health`                                              | none    | Liveness + version. Returns `{ "status": "ok", "version": "…" }`. |

<Note>
  API keys themselves are created and revoked in the [dashboard](https://app.tipar.dev), not through this API. The key-management routes are part of the dashboard's signed-in session, not the programmatic surface.
</Note>

## Errors

Every error is an [RFC 7807](https://datatracker.ietf.org/doc/html/rfc7807) Problem Details document, served as `application/problem+json`:

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

* `title`, `status`, and (usually) `detail` are always present.
* **`errors`** is an array of `{ code, message }` objects, present on most failures. The `code` is a stable, machine-readable string — branch on it; show `message` to humans. (It's a top-level field, per the Problem Details extension convention — not nested under `extensions`.)

A full catalogue of status codes and `code` values is on the [errors page](/api-reference/errors).

## Rate limits

Limits are enforced per API key (and per IP for anonymous endpoints). Exceeding one returns **`429 Too Many Requests`** with a `Retry-After` header (seconds).

| Endpoint                       | Limit                                       | Partition   |
| ------------------------------ | ------------------------------------------- | ----------- |
| `POST /generate`               | 60 requests/minute sustained, bursts to 120 | per API key |
| `POST /v1/playground/generate` | 5 renders/day                               | per IP      |

The `/generate` limiter is a token bucket: it refills at 60 permits per minute and lets you spend up to 120 in a burst. The `429` body uses the standard error envelope with `code: "rate_limit.exceeded"`.

<Tip>
  Build for `429`: on receiving one, wait for the `Retry-After` seconds and retry. A token bucket means short bursts are fine — you only get limited if your *sustained* rate exceeds the refill.
</Tip>

## Quotas and billing

Separate from the per-minute rate limit, each plan includes a **monthly document quota**. When you reach it, `/generate` returns **`402 Payment Required`** with `code: "quota.exceeded"` — the request is blocked, not billed as overage. You upgrade deliberately, or wait for the month to roll over.

```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." }
  ]
}
```

Quota sizes per plan are on the [pricing page](https://tipar.dev/pricing).

## Request size

| Endpoint                       | Max request body | On exceeding            |
| ------------------------------ | ---------------- | ----------------------- |
| `POST /generate`               | 4 MB             | `413 Payload Too Large` |
| `POST /v1/playground/generate` | 256 KB           | `413 Payload Too Large` |

The body includes any base64-encoded [images](/templates/images) — they're the usual reason a request gets large.
