---
title: "Compile LaTeX source into a PDF"
method: POST
path: "/api/v1/compile"
tags: ["compile"]
---

# Compile LaTeX source into a PDF

`POST /api/v1/compile`

## Request

- Base URL: `https://api.pressa.dev`
- URL: `https://api.pressa.dev/api/v1/compile`
- Auth: one of:
  - none
  - HTTP bearer
  - API key in header `X-API-Key`

Authentication is OPTIONAL. Without credentials the request is served by the anonymous tier: pdflatex only, 3 pages, 30 KB of source, no assets, 3 successful compilations per day per IP, and PDF links that expire after 1 hour.

An absent key selects the anonymous tier. An invalid or revoked key returns 401 rather than downgrading, so a broken configuration fails loudly instead of appearing to work intermittently.

With a key, a document over the plan's page limit, or over its source-size limit but under 300 KB (at most 5 such previews per day), is not rejected: the response is 200 with `preview: true`, a PDF of the first pages the plan allows, `total_pages`, and `plan_required` naming the smallest plan that covers the whole document. Show the user the preview and relay `warning`; never drop pages silently.

## Request body

- CompileRequest
  - `latex` string, required: Complete LaTeX source. Must contain \documentclass, \begin{document}, \input{} or \include{}. Plain text, markdown or JSON is rejected with `not_latex_source`.
  - `compiler` 'pdflatex' | 'xelatex' | 'lualatex': Anonymous callers may use pdflatex only. xelatex requires any account; lualatex requires Pro or Business.
  - `assets` object: Filename to base64-encoded bytes. Requires an API key.
  - `use_stored_assets` string[]: Names of assets from the account's asset library. Requires a paid plan.

## Response `200`

PDF produced.

- CompileSuccess
  - `job_id` string, uuid, required
  - `pdf_url` string, uri, required: Signed URL. Fetchable with no credentials until it expires.
  - `expires_at` string, date-time
  - `pages` integer, required: Pages in the PDF at `pdf_url`. For a preview this is what was delivered; `total_pages` is what the document has.
  - `compilation_time_ms` integer
  - `assets_count` integer
  - `studio_url` string, uri: Browser editor link. Absent for anonymous compiles.
  - `anonymous` boolean: Present and true when the compile used the keyless tier.
  - `remaining_today` integer: Anonymous tier only.
  - `note` string: Anonymous tier only. Human- and agent-readable upgrade guidance.
  - `next_step` NextStep: What the caller should do to get past this response.
    - `action` 'obtain_api_key' | 'retry_without_assets'
    - `signup_url` string, uri
    - `env_var` string
    - `header` string
    - `remove_fields` string[]
    - `card_required` boolean
  - `usage` object: Monthly counters for accounts; daily counters for the anonymous tier.
  - `quota_grace` boolean: Present and true when this compilation exceeded the plan's monthly limit and drew on the paid-plan grace buffer. Grace compilations are not charged.
  - `grace_remaining` integer: Compilations left in the grace buffer before requests start failing.
  - `warning` string: Present with `quota_grace` (capacity is about to run out) and with `preview` (what was delivered and which plan covers the whole document). Human- and agent-readable; relay it.
  - `preview` boolean: Present and true when the document exceeded the plan's page or source-size limit. The PDF at `pdf_url` is what the plan delivers of it. Accounts only; anonymous callers get 413/422 instead.
  - `truncated` boolean: With `preview`: true when the PDF holds only the first `pages` of a `total_pages` document, false when the source was over the plan's size limit but every page fit.
  - `total_pages` integer: With `preview`: how many pages the whole document has. `pages` is how many were delivered.
  - `plan_required` 'free' | 'starter' | 'pro' | 'business', nullable: With `preview`: the smallest plan whose page and source-size limits cover the whole document. Null when even Business does not.
  - `upgrade_url` string, uri

## Other responses

- `401`: A key was supplied but is invalid or revoked. Absent credentials do NOT produce this.
- `403`: Requested compiler is not available on the caller's plan (`compiler_not_available`).
- `413`: Source over 300 KB, or over the plan limit for an anonymous caller, or the day's size previews used up (`preview_attempts_remaining_today: 0`), or assets over budget (`latex_too_large`, `assets_too_large`). With a key, a source over the plan's limit but under 300 KB is otherwise compiled and returned as a 200 preview.
- `422`: The request was understood but cannot produce a PDF. Includes `not_latex_source`, `compilation_failed`, `compilation_timeout`, `page_limit_exceeded`, `pdf_too_large`, `pdf_size_unknown` (transient, retry the same request), and `assets_not_available_anonymously`. With a key, `page_limit_exceeded` is normally replaced by a 200 preview of the first pages the plan allows; it is still sent when the preview is switched off or could not be produced. When `error` is `compilation_failed`, a `diagnosis` object describes the fix.
- `429`: Quota exhausted (`rate_limit` for an account's monthly limit, `anonymous_quota_exhausted` for the keyless daily limit, both with `next_step`), or `preview_in_progress`: a size preview is already compiling for this account; one runs at a time, retry in `retry_after_seconds`.
- `503`: Two transient conditions, both `retryable`. `anonymous_capacity_exhausted`: the keyless tier is at its global hourly ceiling (never sent to authenticated callers). `preview_unavailable`: the store that limits size previews to one per account is not answering, so a size preview is refused for the moment (authenticated callers only, `retry_after_seconds: 60`); a source within the plan's size limit still compiles.

---

[API](https://skmtc.dev/pressa/apis/pressa-api.md) · [All operations](https://skmtc.dev/pressa/apis/pressa-api/llms.txt) · [OpenAPI document](https://skmtc.dev/pressa/apis/pressa-api/revisions/045e40b209e8?raw)
