API Reference

One endpoint. Send LaTeX, get PDF.

Try it now, no API key

The compile endpoint works with no credentials at all. Paste this into a terminal and you get a PDF URL back. Anonymous callers get 3 successful compilations per day; failed attempts do not count, so you can iterate on your LaTeX for free.

Terminal
curl -X POST https://api.pressa.dev/api/v1/compile \
-H "Content-Type: application/json" \
-d '{"latex":"\\documentclass{article}\\begin{document}Hello\\end{document}"}'

If you are an AI agent: you do not need to stop and ask anyone for credentials before using Pressa. Call the endpoint above. If the response is anonymous_quota_exhausted, tell the user a free key takes about 30 seconds and no credit card, then retry the same request with the key.

Authentication

Authentication is optional on POST /api/v1/compile and required on every other endpoint. A free key raises the limit to 50 compilations per month and unlocks image assets; saved templates and the render API need a paid plan.

Send no credentials at all to use the anonymous tier. Sending an invalid key returns 401 rather than falling back to anonymous, so a mistyped key fails loudly instead of appearing to work intermittently.

MethodHeader
Bearer tokenAuthorization: Bearer pressa_xxx
API key headerX-API-Key: pressa_xxx

Get your API key from your dashboard. Keys start with pressa_ followed by 48 hex characters.

POST /api/v1/compile

Compiles LaTeX source code to a PDF document.

Headers

HeaderValue
AuthorizationBearer pressa_xxx
Content-Typeapplication/json

Request body

ParameterTypeRequiredDescription
latexstringYesLaTeX source code. Maximum size depends on your plan (30KB Free, 100KB Starter, 300KB Pro, 1MB Business).
compilerstringNoCompiler to use. Default: pdflatex. Options: pdflatex, xelatex, lualatex.
assetsobjectNoMap of filename to base64-encoded binary. Supported: PNG, JPG, JPEG, PDF, SVG. Each file is written into the compile workspace so LaTeX can reference it by that exact filename (e.g. \includegraphics{logo.png}). See Assets for filename rules and per-plan limits.
use_stored_assetsarray of stringNoNames of assets from your persistent asset library to inject into the compile workspace. Saves re-uploading the same bytes each compile. Works alongside assets. Response echoes the resolved names in stored_assets_used. See Assets endpoints for upload/list/get/delete.

Assets (images & binary files)

To include logos, photos, signatures, diagrams, or embedded PDFs in your document, pass them in the assets object alongside your LaTeX source. Each asset is decoded from base64 and written to the compile workspace before compilation, so LaTeX commands like \includegraphics{logo.png} resolve normally. You do not need to upload files separately or embed them inline in the LaTeX - one request carries everything.

Typical use case: an AI agent is asked to replicate a PDF that contains a company logo. The agent extracts the logo as PNG, base64-encodes it, and passes it via assets while referencing \includegraphics{logo.png} in the generated LaTeX. The compiled PDF contains the real logo, not a placeholder.

Rules

  • Supported extensions: png, jpg, jpeg, pdf, svg. Magic bytes are verified server-side so a renamed file will be rejected.
  • Filenames must be plain: alphanumeric plus _, -, and a single extension. No paths, no leading dot, no ... Max 64 characters.
  • Sizes below are for the DECODED payload (the base64 transport adds about 33% to the wire size but does not count toward the limit).

Per-plan limits

LimitFreeStarterProBusiness
Assets per document252050
Total decoded size1 MB5 MB25 MB75 MB

Example request with an image

JSON
{
"latex": "\\documentclass{article}\\usepackage{graphicx}\\begin{document}\\includegraphics{logo.png}\\end{document}",
"compiler": "pdflatex",
"assets": {
"logo.png": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB..."
}
}

Limits

Every compile request is validated against your plan's per-document limits before and after compilation. Rejected compilations do not count toward your monthly quota.

LimitFreeStarterProBusiness
Pages per document520100500
LaTeX source size30 KB100 KB300 KB1 MB
PDF output size10 MB25 MB50 MB100 MB
Assets per document252050
Assets total size1 MB5 MB25 MB75 MB
Compile timeout15s30s60s120s
Compilerspdflatex, xelatexpdflatex, xelatexall threeall three

The GET /api/v1/usage endpoint returns a limits block with the exact values for the authenticated API key so you can validate input client-side before sending.

Example request

Terminal
curl -X POST https://api.pressa.dev/api/v1/compile \
-H "Authorization: Bearer pressa_xxx" \
-H "Content-Type: application/json" \
-d '{"latex": "\\documentclass{article}\\begin{document}Hello World\\end{document}", "compiler": "pdflatex"}'

Success response

200 OK

JSON
{
"pdf_url": "https://api.pressa.dev/api/v1/pdfs/abc123?sig=xxx&exp=xxx",
"job_id": "abc123",
"pages": 1,
"compilation_time_ms": 1250,
"expires_at": "2026-04-04T12:00:00Z",
"ephemeral_assets_used": [],
"stored_assets_used": [],
"usage": {
"monthly_limit": 50,
"used_this_month": 1,
"resets_at": "2026-05-01T00:00:00Z"
}
}

Error responses

401Unauthorized

Missing or invalid API key.

422Compilation Error

LaTeX compilation failed. Response includes the compiler log for debugging.

JSON
{
"error": "compilation_failed",
"message": "LaTeX compilation failed",
"log": "! Undefined control sequence..."
}
429Rate Limit Exceeded

Monthly compilation limit reached. Upgrade your plan for more compilations.

GET /api/v1/usage

Returns your current plan and usage statistics. Requires authentication.

Response

200 OK

JSON
{
"user": {
"email": "[email protected]",
"plan": "free"
},
"usage": {
"monthly_limit": 50,
"used_this_month": 3,
"resets_at": "2026-05-01T00:00:00Z"
},
"stored_assets": {
"count": 0,
"total_bytes": 0,
"count_limit": 0,
"total_bytes_limit": 0,
"remaining_bytes": 0
},
"templates": {
"count": 0,
"limit": 0,
"remaining": 0
},
"api_key": {
"prefix": "pressa_a1b2...",
"created_at": "2026-03-15T10:00:00Z"
},
"limits": {
"max_pages_per_document": 5,
"max_latex_bytes_per_document": 30720,
"max_pdf_bytes_per_document": 10485760,
"max_assets_total_bytes_per_document": 1048576,
"max_assets_count_per_document": 2,
"max_stored_assets_count": 0,
"max_stored_assets_total_bytes": 0,
"compile_timeout_seconds": 15,
"allowed_compilers": ["pdflatex", "xelatex"]
}
}

GET /api/v1/pdfs/:id

Download a compiled PDF via its signed URL. No authentication required - the URL itself contains the signature.

Query parameters

ParameterTypeDescription
sigstringHMAC signature for URL verification.
expstringExpiration timestamp. URLs expire after 24 hours.

Returns the PDF file directly as application/pdf. Use the pdf_url from the compile response - it already includes the signature and expiration parameters.

Templates

Save and reuse LaTeX templates. Available on paid plans only - free plan users receive a 403 response.

Each template has a required latex_content (the document layout) and two optional fields that mean different things. description is a short human-readable summary surfaced in GET /api/v1/templates for UI lists. instructions is a longer prose markdown playbook (up to 50000 chars) that an AI agent reads alongside the LaTeX when filling the template - defaults, workflow rules, edge cases, conditional logic. Use description for humans, use instructions for agents. They are independent fields.

POST /api/v1/templates

Save a new template or update an existing one. If a template with the same name already exists, it gets updated (upsert).

Request body

ParameterTypeRequiredDescription
namestringYesTemplate name. Max 100 characters.
latex_contentstringYesLaTeX source code. Maximum size depends on your plan (30KB Free, 100KB Starter, 300KB Pro, 1MB Business).
descriptionstringNoShort human-readable summary. Max 500 characters.
instructionsstringNoAgent playbook (prose markdown). Describes how to fill the template - defaults, workflow rules, edge cases, conditional logic. Max 50000 characters. Surfaced verbatim in GET /api/v1/templates/:id so AI agents can apply the rules when generating a document.

Response

201 Created (new) or 200 OK (updated)

JSON
{
"template": {
"id": 1,
"name": "invoice",
"description": "Standard invoice template",
"latex_content": "\\documentclass{article}...",
"instructions": "Ask the user only for total amount...",
"updated_at": "2026-04-13T10:00:00Z",
"latex_size_bytes": 2048
},
"created": true
}

GET /api/v1/templates

List all your saved templates. Does not include latex_content or instructions in the response - both can be large. Each row carries a has_instructions boolean so clients can decide whether a follow-up GET /api/v1/templates/:id will return a useful playbook.

Response

200 OK

JSON
{
"templates": [
{
"id": 1,
"name": "invoice",
"description": "Standard invoice template",
"updated_at": "2026-04-13T10:00:00Z",
"latex_size_bytes": 2048,
"has_instructions": true
}
]
}

GET /api/v1/templates/:id

Get a single template by numeric ID or URL-encoded name. Returns the full latex_content along with instructions (or null if the template has none) so an AI agent receives layout and playbook in a single round trip.

Response

200 OK

JSON
{
"template": {
"id": 1,
"name": "invoice",
"description": "Standard invoice template",
"latex_content": "\\documentclass{article}...",
"instructions": "Ask the user only for total amount. Date is today's date. Invoice number format YYYYMMDD-N where N is the sequential count of invoices issued this calendar year.",
"updated_at": "2026-04-13T10:00:00Z",
"latex_size_bytes": 2048
}
}

DELETE /api/v1/templates/:id

Delete a template by numeric ID or URL-encoded name.

Response

204 No Content

Error responses

403Forbidden

Templates are not available on the free plan. Upgrade to use this feature.

404Not Found

Template with the given ID or name does not exist.

Assets endpoints

Store binary files (logos, signatures, diagrams, embedded PDFs) in a persistent, user-scoped library and reuse them across many compiles and templates. Upload a logo once, then reference it in every future compile via use_stored_assets. Paid plans only - free plan users receive a 403 response with error plan_required.

Compare: the assets field on POST /api/v1/compile carries bytes inline for a single compile (ephemeral, discarded after). The asset library is the opposite - upload once, persist, reuse by name. Both are valid; pick whichever fits your workflow.

Per-plan limits

These caps govern your persistent library total. They are independent of the per-compile ephemeral asset limits, but when a compile merges ephemeral assets with use_stored_assets, the combined count and bytes are checked against the per-compile limits.

LimitFreeStarterProBusiness
Max stored assets01050unlimited
Max total size050 MB500 MB5 GB

POST /api/v1/assets

Upload a new asset or update an existing one. Upsert by name - if an asset with the same name exists for this user, its bytes are replaced. Same validation rules as the compile assets field (filename whitelist, extension whitelist, magic-bytes match).

Request body

ParameterTypeRequiredDescription
namestringYesFilename used to reference the asset from LaTeX (e.g. logo.png). Same rules as ephemeral assets - alphanumeric plus _, -, single extension, max 64 chars.
content_base64stringYesBase64-encoded file bytes. Supported formats: PNG, JPG, JPEG, PDF, SVG. Magic bytes are verified server-side.
content_typestringNoMIME type. Inferred from the filename extension if omitted.

Response

201 Created (new) or 200 OK (updated)

JSON
{
"asset": {
"id": 42,
"name": "logo.png",
"content_type": "image/png",
"size_bytes": 18450,
"sha256": "3fe4a5...",
"updated_at": "2026-04-21T16:05:00Z"
},
"created": true
}

GET /api/v1/assets

List all your stored assets. Does not include the base64 bytes. Includes quota rollup so clients can render progress bars without extra round trips.

Response

200 OK

JSON
{
"assets": [
{
"id": 42,
"name": "logo.png",
"content_type": "image/png",
"size_bytes": 18450,
"sha256": "3fe4a5...",
"updated_at": "2026-04-21T16:05:00Z"
}
],
"count": 1,
"total_bytes": 18450,
"count_limit": 10,
"total_bytes_limit": 52428800,
"remaining_bytes": 52410350
}

count_limit is null when the plan grants unlimited assets (Business).

GET /api/v1/assets/:id_or_name

Get a single asset by numeric ID or URL-encoded name (case-insensitive). Returns metadata plus the full base64-encoded content. Numeric path segments lookup by id; anything non-numeric is treated as a name.

Response

200 OK

JSON
{
"asset": {
"id": 42,
"name": "logo.png",
"content_type": "image/png",
"size_bytes": 18450,
"sha256": "3fe4a5...",
"updated_at": "2026-04-21T16:05:00Z",
"content_base64": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB..."
}
}

DELETE /api/v1/assets/:id_or_name

Delete an asset by numeric ID or URL-encoded name. Removes the database row and the underlying bytes. Compiles that referenced the name via use_stored_assets after deletion will fail with asset_not_found.

Response

204 No Content

V2 Templates & Render API

V2 templates separate a document's layout from its data. The LaTeX source contains Liquid placeholders like {{ customer_name }}, and Pressa derives a JSON schema and sample data from them automatically on save. Once a template is saved, the render endpoint fills it with fresh values and returns a PDF in a single call - no LaTeX in the request, just data.

V2 endpoints require a paid plan (Starter or above). Free-plan keys receive plan_required (403).

POST /api/v2/templates

Creates a V2 template. The source is validated against a 14-rule LaTeX security policy (shell escape, absolute-path input, unsafe packages, and similar are rejected with template_security_violation). Names are unique per account: a duplicate name returns name_taken (422) instead of overwriting.

ParameterTypeRequiredDescription
namestringYesTemplate name, unique per account (case-insensitive).
latex_with_placeholdersstringYesFull LaTeX source containing Liquid placeholders.
descriptionstringNoHuman-readable description.

The 201 response includes the derived schema (JSON Schema with required fields and types inferred from filter usage) and sample_data (realistic placeholder values for previews). Standard CRUD is available: GET /api/v2/templates lists, GET /api/v2/templates/:id fetches one (by numeric ID or case-insensitive name), PATCH updates (changing the source re-derives the schema and bumps version), and DELETE removes the template.

POST /api/v2/templates/:id/render

Renders a saved V2 template with your data and returns a finished PDF. accepts the numeric ID or the case-insensitive template name. The request body carries a single data object whose keys match the template schema; it is validated server-side before anything compiles, so structural mistakes come back as actionable errors with missing_fields and type_errors lists. Extra keys are ignored.

The compiler is selected automatically from the rendered source: templates using fontspec compile with xelatex, templates using \directlua with lualatex, everything else with pdflatex. Stored assets referenced via \includegraphics{name} are resolved from your asset library automatically - no use_stored_assets field needed.

Every user-supplied value is passed through latex_escape automatically, so characters like % & $ # _ are safe by default. Template authors can opt out per value with the raw filter, and format values with currency, date and asset filters.

Example request

Terminal
curl -X POST https://api.pressa.dev/api/v2/templates/invoice/render \
-H "Authorization: Bearer pressa_xxx" \
-H "Content-Type: application/json" \
-d '{"data": {"customer_name": "Acme GmbH", "invoice_number": "2026-041", "amount": 1250.0}}'

Success response

200 OK

JSON
{
"job_id": "abc123",
"pdf_url": "https://api.pressa.dev/api/v1/pdfs/abc123?sig=xxx&exp=xxx",
"expires_at": "2026-06-10T12:00:00Z",
"pages": 1,
"render_time_ms": 940,
"compilation_time_ms": 660,
"template": { "id": 12, "name": "invoice", "version": 3 },
"usage": { "plan": "pro", "compilations_this_month": 14, "monthly_limit": 2000 }
}

Render error responses

StatusError codeMeaning
403plan_requiredRender API requires a paid plan.
403compiler_not_availableThe template needs a compiler (e.g. lualatex) not included in your plan.
404template_not_foundNo template with that ID or name in your library.
422template_engine_mismatchTemplate is V1 (raw LaTeX, no placeholders). Use the compile endpoint instead.
422invalid_data_shapeThe data parameter must be a JSON object.
422schema_validation_failedData does not match the template schema. Includes missing_fields and type_errors.
422render_parse_failedLiquid syntax error in the template source.
422render_failedUndefined variable or filter, or a render resource limit was hit.
422render_asset_not_foundThe template references an asset name that is not in your library.
422compilation_failedThe rendered LaTeX failed to compile. Includes the compiler log.
429rate_limitMonthly compilation cap reached. Renders count toward the same cap as compiles.
504render_total_timeoutThe render exceeded the 60-second wall clock. Simplify the template or data.

Rate Limits

PlanCompilationsSaved TemplatesPrice
Free50 per month0$0
Starter500 per month10$9/month
Pro2,000 per month50$29/month
Business10,000 per monthUnlimited$99/month

When you exceed your monthly limit, the API returns a 429 response with details about your usage and when the limit resets.

429 Response
{
"error": "rate_limit_exceeded",
"monthly_limit": 50,
"used_this_month": 50,
"resets_at": "2026-05-01T00:00:00Z"
}

Plan limit errors

In addition to monthly rate limits, every compile is validated against your plan's per-document limits. These errors include a message, the requested vs allowed values, your current plan, and an upgrade URL. Rejected attempts do not count toward your monthly quota.

Error codeHTTPWhen it fires
compiler_not_available403Requested a compiler that is not available on your plan (e.g. lualatex on Free).
latex_too_large413LaTeX source exceeds 300 KB, or the plan limit for an anonymous caller. Rejected before compile. With an API key, a source over your plan's limit but under 300 KB is compiled and delivered as a preview instead (see below).
page_limit_exceeded422The compiled PDF has more pages than allowed and is not delivered. With an API key this is normally replaced by a 200 preview of the first pages your plan allows; you still get the 422 when the preview is switched off or could not be produced, so handle both.
pdf_too_large422Compiled PDF exceeds your plan's PDF size limit (or, for a whole document checked before a preview slice, the compile ceiling; the body then carries both numbers).
preview_in_progress429A preview of a larger document is already compiling for this account; one runs at a time. Retry after it finishes.
preview_unavailable503Size previews are briefly refused because the store that limits them to one per account is not answering. Retry in a minute; a source within your plan's size limit still compiles.
pdf_size_unknown422The size of the compiled PDF could not be measured, so it was not delivered. Transient; retry the same request.
not_latex_source422Submitted content is not LaTeX (no \documentclass, \begin{document}, \input{}, or \include{} marker found). Response includes a requirements list and a ready-to-use example_template so an AI agent can self-correct without bouncing back to the user.
too_many_assets422Number of assets exceeds your plan's limit (Free 2, Starter 5, Pro 20, Business 50).
assets_too_large413Decoded assets total exceeds your plan's size limit.
assets_not_allowed403Your plan's asset count is 0 and at least one asset was supplied.
invalid_asset_filename422Filename contains a path separator, leading dot, .., null byte, or is longer than 64 characters.
invalid_asset_format422Extension is outside png|jpg|jpeg|pdf|svg, or the decoded bytes do not match the declared format's magic bytes.
invalid_asset_encoding422Base64 string is malformed or decodes to zero bytes.
plan_required403Free plan tried to create a stored asset via POST /api/v1/assets. Upgrade to a paid plan to use the asset library.
asset_limit_reached403User hit the per-plan count cap for stored assets (Starter 10, Pro 50). Delete an existing asset or upgrade.
storage_quota_exceeded413Upload would push the user past their total bytes cap for stored assets (Starter 50 MB, Pro 500 MB, Business 5 GB).
asset_not_found422use_stored_assets referenced a name that does not exist in the user's library. Response includes the missing name so an agent can self-correct.
asset_name_collision422The same filename appeared in both assets and use_stored_assets. Pick one source to avoid ambiguity.
invalid_stored_assets_shape422use_stored_assets must be an array of strings (filenames). Any other shape is rejected.
asset_storage_error500Internal error - database row present but underlying binary missing. Should not happen in practice; surfaces as a safeguard.

Pressa is a LaTeX compiler, not a text-to-PDF converter. If your input is plain text, markdown, JSON, or notes, generate the LaTeX yourself before submitting.

422 page_limit_exceeded
{
"error": "page_limit_exceeded",
"message": "Your document has 23 pages, but your anonymous plan allows max 3 pages per document.",
"pages": 23,
"limit": 3,
"plan": "anonymous",
"upgrade_url": "https://pressa.dev/pricing"
}

With an API key, a document past your plan's page or source-size limit is not rejected. It is compiled, and you receive the first pages your plan allows together with the page count of the whole document and the smallest plan that covers it. The PDF at pdf_url is exactly those pages. Agents: show the user the preview and relay warning; do not silently drop pages.

200 preview (document over the plan limit)
{
"job_id": "b7c1...",
"pdf_url": "https://pressa.dev/api/v1/pdfs/b7c1...?sig=...&exp=...",
"pages": 5,
"total_pages": 38,
"preview": true,
"truncated": true,
"plan_required": "pro",
"warning": "Your document has 38 pages and 160 KB of source. The free plan delivers the first 5 pages; the whole document needs the pro plan. Upgrade at https://pressa.dev/pricing.",
"upgrade_url": "https://pressa.dev/pricing",
"usage": { "plan": "free", "compilations_this_month": 3, "monthly_limit": 50 }
}