LaTeX API

Compile LaTeX in the cloud. One HTTP call.

Send a .tex file or a whole project; get back a PDF and every error as structured JSON.

sh
curl https://latex-api.crafter.run/v1/compile \
  -H "Authorization: Bearer $LATEX_API_KEY" \
  -H "Content-Type: text/x-tex" \
  --data-binary @main.tex \
  -o main.pdf

Any key issued by the operator works. The response body is the PDF; errors arrive as problem+json with line numbers.

Try it in the browser

This calls POST /v1/demo/compile on the public instance. Errors come back as diagnostics with line numbers.

No API key needed. 5 compiles a minute, 30 a day.

The PDF appears here. Edit the source and press Compile.

How it works

  1. Send files

    JSON, multipart, a zip or a raw .tex body. Pick the engine or let the % !TEX comment decide.

  2. Isolated container

    Each compile starts a fresh TeX Live 2026 container: no network, read-only root, CPU, memory and process limits.

  3. PDF + diagnostics

    latexmk runs as many passes as needed. The log is parsed into errors and warnings with file and line.

Everything a build server needs, nothing it does not

Sync and async
POST /v1/compile waits for the PDF. POST /v1/jobs returns at once; poll it, stream it over SSE or get a webhook.
Whole projects
JSON files, multipart uploads, a zip or a raw .tex body. Sections, .bib files, images, classes and fonts travel with the document.
Three engines
pdfLaTeX, XeLaTeX and LuaLaTeX from TeX Live 2026, driven by latexmk with biber and bibtex available.
Structured diagnostics
The log is parsed into errors, warnings and info with file, line and context. A failed compile is a 422 you can act on.
Content-addressed cache
Identical input on the same compiler version returns the previous PDF instantly, flagged with X-Compile-Cached.
Templates
Seven ready projects: IEEE papers, two résumés, a brochure, a poster and a Spanish report. Compile them with your own files on top.
Sandboxed compiles
A throwaway container per job: no network, read-only filesystem, seccomp profile, cgroup CPU, memory and process limits.
OpenAPI 3.1, SDK and CLI
A typed TypeScript SDK and a CLI with watch mode are generated from the same contract you can read at /openapi.json.
Self-hostable, MIT
Docker Compose with Postgres and the compiler image. Bring your own keys, limits and, if you like, gVisor.

Start from a template

Every template compiles inside the sandbox as shipped. Download the zip, or send only the files you changed to /v1/templates/:id/compile.

  • Résumépdflatex

    AltaCV résumé

    Two-column résumé based on LianTze Lim's AltaCV class: header with contact icons, experience, projects, a wheel chart, achievements, skill tags, star ratings, education and referees. Compiles with pdfLaTeX.

  • Posterpdflatex

    Infographic poster (A0)

    Three-column A0 portrait poster built with tikzposter: custom color theme, pgfplots bar and line charts, a TikZ flow diagram, an object tree, TikZ-drawn icons and highlighted inner blocks. Explains how a LaTeX document becomes a PDF.

  • Reportpdflatex

    Informe técnico (español)

    Informe técnico en español con portada a color, resumen, índices, tres capítulos en archivos separados, figura y gráfico con TikZ/pgfplots, tablas booktabs, listado de código y bibliografía apalike. Usa babel spanish con es-tabla.

  • Brochurepdflatex

    Tri-fold brochure

    Six-panel tri-fold brochure on landscape A4 built with the leaflet class: front and back covers, schedule, map, process diagram and feature panels, all drawn with TikZ (no external images). Advertises a fictional workshop series.

Call it from anywhere

Language of the example
curl https://latex-api.crafter.run/v1/compile \
  -H "Authorization: Bearer $LATEX_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/pdf" \
  -d '{
    "engine": "pdflatex",
    "files": [
      { "path": "main.tex", "content": "\\documentclass{article}\\begin{document}Hello\\end{document}" }
    ]
  }' \
  -o main.pdf
import { LatexApi } from "@jibaru/latex-api-sdk";
import { readFile, writeFile } from "node:fs/promises";

const api = new LatexApi({
  apiKey: process.env.LATEX_API_KEY,
  baseUrl: "https://latex-api.crafter.run",
});

// Synchronous: a PDF comes back as a Uint8Array.
const pdf = await api.compile({
  files: [{ path: "main.tex", content: await readFile("main.tex", "utf8") }],
});
await writeFile("main.pdf", pdf);

// Asynchronous: create a job, then wait for it.
const job = await api.jobs.create({ files: [{ path: "main.tex", content: "..." }] });
const done = await api.jobs.wait(job.id);
console.log(done.status, done.result?.pdfUrl);
# Install nothing; run it with npx (or: npm i -g @jibaru/latex-api-cli)
npx @jibaru/latex-api-cli login

# Compile a folder (zipped and uploaded for you) and write paper.pdf
npx @jibaru/latex-api-cli compile ./paper -o paper.pdf

# Recompile on every save, streaming the log over SSE
npx @jibaru/latex-api-cli compile ./paper --watch

# Start from a template
npx @jibaru/latex-api-cli templates init ieee-conference ./paper
import os
import requests

r = requests.post(
    "https://latex-api.crafter.run/v1/compile",
    headers={
        "Authorization": f"Bearer {os.environ['LATEX_API_KEY']}",
        "Accept": "application/pdf",
    },
    json={
        "engine": "xelatex",
        "files": [{"path": "main.tex", "content": open("main.tex").read()}],
    },
    timeout=90,
)

if r.status_code == 200:
    open("main.pdf", "wb").write(r.content)
elif r.status_code == 422:
    for d in r.json()["diagnostics"]:
        print(f"{d['severity']} {d.get('file')}:{d.get('line')} {d['message']}")
else:
    r.raise_for_status()

The SDK is @jibaru/latex-api-sdk, the CLI is @jibaru/latex-api-cli. Both are generated from the OpenAPI document and published under MIT.

per upload
50 MB
sync timeout
1 min
async, at most
10 min
RAM per compile
2 GB
artifacts kept
24 h

Limits are configured per instance and published at /v1/limits. Keys get 60 compiles an hour, 5 at once.