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

# Blink PDF API Reference: Base URL, Auth, and Errors

> Everything you need to start converting Markdown to professional PDFs. Base URL, endpoints, request format, response headers, and error codes.

Blink PDF is a REST API that converts Markdown into professional, print-ready PDFs in roughly 100 milliseconds. You send a JSON request body containing your Markdown content, and the API streams back a binary PDF — no browser, no headless Chrome, no fuss. This overview covers the base URL, available endpoints, request and response conventions, and what to expect when something goes wrong.

## Base URL

All API requests are made to the following base URL:

```
https://api.blinkpdf.io
```

The current API version is **v1**. Rendering endpoints are prefixed with `/v1/`; the unversioned health check lives at `/health`.

<Note>
  The base URL does not include a trailing slash. Append endpoint paths exactly as shown in the table below.
</Note>

## Endpoints at a Glance

| Method | Path                                     | Description                                                                           |
| ------ | ---------------------------------------- | ------------------------------------------------------------------------------------- |
| `POST` | `/v1/render`                             | Convert Markdown to a PDF binary                                                      |
| `POST` | `/v1/render/validate`                    | Validate a render request body without generating a PDF or consuming render quota     |
| `POST` | `/v1/templates/sandwich-document/render` | Render a structured sandwich document (first-page header + last-page signature)       |
| `GET`  | `/v1/fonts`                              | List the hosted font catalog and Google Fonts limits for your key                     |
| `GET`  | `/v1/style-targets`                      | Machine-readable catalog of the styling vocabulary (`rules` selectors and properties) |
| `GET`  | `/v1/themes`                             | List the 15 curated design themes                                                     |
| `GET`  | `/`                                      | Machine-readable entry point linking the public API surfaces                          |
| `GET`  | `/health`                                | Liveness probe (no auth required)                                                     |

<Info>
  Pick a curated look with the `theme` field (default `report-clean`) — call `GET /v1/themes` for the catalog, see [Themes](/configuration/themes). Pick fonts per request with the `fontStack` field, or pull any Google Fonts family by URL with `googleFontsUrl` — see [Fonts & Typography](/configuration/fonts-layouts). Call `GET /v1/fonts` for the hosted catalog and `GET /v1/style-targets` for the full `rules` styling vocabulary.
</Info>

## Request Format

Render `POST` requests must include a JSON body and the following headers:

| Header         | Value              |
| -------------- | ------------------ |
| `x-api-key`    | `bp_...`           |
| `Accept`       | `application/pdf`  |
| `Content-Type` | `application/json` |

`POST /v1/render/validate` accepts the same JSON body and `x-api-key` header, but expects `Accept: application/json` because the response is JSON, not PDF bytes.

`GET` requests that require authentication need only the `x-api-key` header — no request body is sent.

## Response Format

A successful call to `POST /v1/render` or `POST /v1/templates/sandwich-document/render` returns a **binary PDF** (`application/pdf`). Pipe or write the response body directly to a file rather than reading it as text.

On the public free plan, successful PDF responses from `POST /v1/render` and template routes include a non-removable bottom-center footer ("Made with Blink PDF" plus the brand mark). The response status and schema are unchanged.

`GET /health`, `POST /v1/render/validate`, and all error responses return **JSON** (`application/json`).

## Validate before render

`POST /v1/render/validate` runs the same TypeBox schema and cross-field semantic checks as `POST /v1/render`, without generating a PDF, metering usage, or recording a successful render audit. Use it to iterate on `rules`, `typography`, `furniture`, and related options before spending a render.

A valid body returns `200` with `valid: true`. When the caller is authenticated, the response also reports the resolved typography (declared pool, active body/heading families, and any unused declarations) and a `contentDigest` for delivery deduplication:

```json theme={null}
{ "valid": true }
```

Validation failures return the same `400` error shape as a failed render request (`validation_failed`, `invalid_body`, and related codes). The endpoint does not emit `X-Render-*` or `X-BlinkPDF-Usage-*` headers.

## Response Headers

Every successful render (`POST /v1/render` or `POST /v1/templates/sandwich-document/render`) includes render-status headers and, when the caller is authenticated under plan limits, usage and attribution headers. Both routes share the same spend-cap, tier rate/concurrency, and usage ledger path.

### Render status

| Header                           | Description                                                                                                                                                                                      |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `X-Render-Status`                | `ok` when no warn- or error-level diagnostics were emitted; `degraded` otherwise                                                                                                                 |
| `X-Render-Rendered-As-Requested` | `true` when the PDF matches what you asked for (no error-severity issues such as missing images or glyphs, and no `image-fetch-skipped` warnings). `false` when the output diverged              |
| `X-Render-Diagnostics`           | Base64url-encoded UTF-8 JSON array of actionable diagnostics. Omitted when there were no diagnostics to report                                                                                   |
| `X-Render-Diagnostics-Truncated` | `true` when the diagnostics array was truncated to fit header size limits (highest-severity entries are kept first). The diagnostics header itself may be omitted when even one entry cannot fit |
| `X-Render-Fonts`                 | The per-role font families actually embedded, including per-depth heading overrides                                                                                                              |
| `X-Render-Font-Fallback`         | The family used for glyphs the primary fonts missed (e.g. `inter`; see `glyphFallback`)                                                                                                          |
| `X-Render-Theme-Fallback`        | Present when the requested `theme` slug was unknown; names the applied theme and a `didYouMean` suggestion                                                                                       |
| `X-Render-Typography-Notes`      | Informational notes about typography resolution                                                                                                                                                  |
| `X-Render-Rule-Warnings`         | Advisories for `rules` that matched nothing or were inert                                                                                                                                        |
| `X-Render-Static-Warnings`       | Node-level advisories raised during layout                                                                                                                                                       |
| `X-Render-Ms`                    | Render wall-time in milliseconds                                                                                                                                                                 |
| `X-Request-Id`                   | Unique request identifier. Include this when emailing [support@blinkpdf.io](mailto:support@blinkpdf.io)                                                                                          |

Each diagnostic object includes `severity`, `code`, `message`, and `help` (a `blinkpdf://reference/…` URI). When applicable, entries also include `resource` and `httpStatus`.

Decode `X-Render-Diagnostics` like this:

```python theme={null}
import base64
import json

raw = response.headers.get("X-Render-Diagnostics")
if raw:
    padded = raw + "=" * (-len(raw) % 4)
    diagnostics = json.loads(base64.urlsafe_b64decode(padded))
```

### Usage and attribution

Successful authenticated renders on both `POST /v1/render` and `POST /v1/templates/sandwich-document/render` emit usage and attribution headers when billing limits apply:

| Header                                                           | Description                                                                                                                                                                                                                                        |
| ---------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `X-BlinkPDF-Billing`                                             | Billing outcome: `counted` (billed), `deduped-cache` (cache replay, not billed; **`POST /v1/render` only**), or `deduped-idempotent` (idempotency-key replay, not billed; **`POST /v1/render` only**). The authoritative "was this billed?" signal |
| `X-BlinkPDF-Usage-Event-Id`                                      | The metering event id for this render                                                                                                                                                                                                              |
| `X-BlinkPDF-Usage-PDFs-This-Period`                              | Render units used in the current billing period (when attributable)                                                                                                                                                                                |
| `X-BlinkPDF-Usage-Included`                                      | Render units included in your plan for the period                                                                                                                                                                                                  |
| `X-BlinkPDF-Usage-Overage`                                       | Render units used beyond the included volume                                                                                                                                                                                                       |
| `X-BlinkPDF-Cache`, `X-BlinkPDF-Cache-Age`                       | Cache outcome (`hit`/`miss`) and, on a hit, the age of the cached render in seconds — **`POST /v1/render` only** (template routes do not use the process-local PDF LRU)                                                                            |
| `X-BlinkPDF-Content-Digest`                                      | Canonical per-account render digest used for delivery deduplication — **`POST /v1/render` only**                                                                                                                                                   |
| `X-BlinkPDF-Period-Start`                                        | ISO-8601 start of the current billing period                                                                                                                                                                                                       |
| `X-BlinkPDF-Account`, `X-BlinkPDF-Key-Id`, `X-BlinkPDF-Key-Name` | Opaque account id, API key id, and optional key label for attribution                                                                                                                                                                              |

### Sandwich template

`POST /v1/templates/sandwich-document/render` is metered like `POST /v1/render`: it counts **1 render unit** on a successful billed response, shares spend-cap and tier rate/concurrency, applies the free-tier watermark when applicable, and emits the usage/attribution headers above (except cache and content-digest headers). Successful responses always include `X-Template-Version` (currently `1.0.0`).

Page-1 body clearance uses a flow offset for the header band; bottom margins reserve the repeating page-footer band and, when the last-page signature would not fit under the body, the signature band as well — so the Markdown body never collides with either chrome band.

Client `x-idempotency-key` is **not** accepted on template routes (no body fingerprint / pre-check). Do not send it expecting dedupe.

## Error Codes

When a request cannot be completed, the API returns a standard HTTP error status code with a JSON body of the shape `{ "error": "<code>", "detail": "<message>" }`. Field-level validation failures may also include an `errors` array; each member can carry machine-readable `kind`, allowed keys/values, relocation hints, and optional `variants` (accepted shapes for object-valued unions: discriminant `when`, `keys`, `required`, enums — never internal numeric ranges).

| Status Code | `error`                                  | What It Means                                                                                                                                                                               |
| ----------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`       | `validation_failed` / `invalid_body` / … | The request body is malformed, is not valid JSON, or fails schema validation                                                                                                                |
| `401`       | `unauthorized`                           | The `x-api-key` header is absent or the API key is invalid                                                                                                                                  |
| `402`       | `spend_cap_reached`                      | Spend cap reached on `POST /v1/render` or `POST /v1/templates/*/render` — you hit your configured cap or your plan's hard cap (2× included volume); billing is paused until the next period |
| `404`       | `not_found`                              | No matching route — the path does not exist. Documented operations never return this when the path is correct; check for typos                                                              |
| `405`       | `method_not_allowed`                     | The HTTP method is not supported on this path                                                                                                                                               |
| `413`       | `payload_too_large`                      | The request body exceeds the maximum allowed size                                                                                                                                           |
| `422`       | `image_too_large`                        | A single image exceeds 24 megapixels, or the render's total image area exceeds 48 megapixels                                                                                                |
| `429`       | `rate_limited`                           | You have exceeded your rate limit. Check the `Retry-After` header for when you can retry                                                                                                    |
| `409`       | `idempotency_conflict`                   | A different request body was sent with an already-used idempotency key (`POST /v1/render` only)                                                                                             |
| `500`       | `internal_error`                         | The rendering pipeline encountered an unexpected error                                                                                                                                      |
| `502`       | —                                        | Bad gateway (`POST /v1/render` only; HTML body)                                                                                                                                             |
| `503`       | `service_unavailable`                    | A dependency (such as the font source) is temporarily unavailable; retry shortly                                                                                                            |

<Tip>
  When you receive a `429`, read the `Retry-After` response header. It contains the number of seconds you should wait before sending another request.
</Tip>

## Rate Limiting

Rate limits are enforced per API key and vary by plan. Template renders share the same sustained-rate and concurrency buckets as `POST /v1/render`. If you exceed your limit, the API returns `429 Too Many Requests` and includes a `Retry-After` header.

<Note>
  Calls to `GET /health` and `POST /v1/render/validate` never consume render units. `POST /v1/render/validate` is, however, rate-limited **in its own per-tier bucket**, independent of render and template throughput — a validate burst never spends render throughput, and vice versa, so either can return `429` on its own. Only successful `POST /v1/render` and `POST /v1/templates/*/render` responses with `X-BlinkPDF-Billing: counted` consume render units.
</Note>

## Versioning

The current API version is `v1`, reflected in every endpoint path. When breaking changes are introduced, a new version prefix (e.g. `/v2/`) will be released alongside the existing version. You will always receive advance notice before an older version is deprecated.
