API Reference
Complete reference for the FormaTeX REST API. Compile LaTeX documents to PDF with a single HTTP call.
Quick Example
curl -X POST https://api.formatex.io/api/v1/compile \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{"latex": "\\documentclass{article}\\begin{document}Hello!\\end{document}"}' \
--output document.pdf
Authentication
Two auth methods: JWT tokens for account management, API keys for compilation.
| Method | Header | Used For |
|---|---|---|
| JWT Token | Authorization: Bearer <token> | User management, billing, admin |
| API Key | X-API-Key: <key> | Compilation endpoints |
Errors
All errors return a consistent JSON format with an error message and optional context.
{
"error": "human-readable error message",
"plan": "free",
"limit": 100,
"used": 100
}
Status Codes
| Code | Meaning |
|---|---|
400 | Invalid request body |
401 | Missing or invalid authentication |
403 | Plan limit violated or account deactivated |
404 | Resource not found |
409 | Conflict (e.g., email already registered) |
422 | Compilation failed |
429 | Rate limit exceeded |
503 | Billing not configured |
Rate Limits
| Endpoint Group | Limit |
|---|---|
| Auth | 20 req/min |
| User management | 60 req/min |
| Billing | 20 req/min |
| Admin | 60 req/min |
| Compilation | 100 req/min |
API Keys
Create and manage API keys for compilation. All endpoints require JWT.
Compilation
Compile LaTeX to PDF. All compilation endpoints require an API Key.
Rendering
Render equations, TikZ diagrams, and compile full documents to rasterized images — all without touching your monthly compilation quota. All endpoints require an API key.
Document Analysis
Static analysis endpoints for LaTeX and BibTeX. None of these endpoints compile a document or count against your monthly quota. All require an API key.
Batch Generation
Generate many PDFs from a single LaTeX template in one API call. Supply your template and data rows — FormaTeX compiles each row concurrently and returns a ZIP archive containing all PDFs plus a manifest.json summary.
Typical uses: personalised certificates, invoices, letters, report cards, conference badges, mail-merge documents.
- Up to 50 rows per request
- Up to 5 concurrent compilations (auto-throttled)
- Per-row timeout: 30 s
- Overall request timeout: 4 min
- Rate limit: 10 requests per minute
ZIP Archive Format
The returned ZIP always contains:
manifest.json— compilation summary (always the first entry)<filename>.pdf— one PDF per successful row, named by thefilenametemplate
{
"total": 3,
"success": 2,
"failed": 1,
"results": [
{ "index": 0, "filename": "report-Martin-1.pdf", "success": true },
{ "index": 1, "filename": "report-Chen-2.pdf", "success": true },
{ "index": 2, "filename": "report-López-3.pdf", "success": false, "error": "Undefined control sequence \\badmacro" }
]
}
Duplicate filenames after template rendering are automatically deduplicated by appending -2, -3, etc.
LaTeX Escaping
By default all substituted values are LaTeX-escaped, so special characters in your data are safe:
& → \& % → \%
$ → \$ # → \#
_ → \_ { → \{
} → \} ~ → \textasciitilde{}
^ → \textasciicircum{} \ → \textbackslash{}
Use {{field | raw}} to suppress escaping when the field already contains valid LaTeX markup.
Conversions & PDF Tools
Convert LaTeX documents to other formats and manipulate existing PDFs. All endpoints require an API key.
Developer Tooling
Static tooling endpoints for LaTeX. None of these count against your monthly compilation quota. All require an API key.
Templates
Parameterized LaTeX documents with <<variable>> placeholders. Send data, get PDF — no LaTeX knowledge required for your API consumers.
How it works
- Browse the template library or create your own
- Send a compile request with variable values
- Receive a compiled PDF with all placeholders substituted
Variable schema
| Field | Type | Description |
|---|---|---|
key | string | Variable name used in placeholders |
type | string | number | date | Type validation applied to the value |
required | boolean | Whether the variable must be provided |
default | string | Default value if not provided |
description | string | Human-readable description |
Quick example
curl -X POST https://api.formatex.io/api/v1/templates/cover-letter/compile \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"variables": {"name": "Alice Martin", "company": "Acme Corp"}}' \
--output cover-letter.pdf
Without the Accept: application/json header, the response is a raw PDF binary.
Public Templates
Curated templates available to all users. No authentication required to list or view. Compilation requires an API key.
User Templates
Create and manage your own parameterized templates. Requires JWT authentication. Available on Pro plan and above.
Plans
View available plans and their limits. Public endpoint.
Plan Comparison
| Feature | Free | Pro | Max | Enterprise |
|---|---|---|---|---|
| Compilations/mo | 15 | 500 | 2,000 | 15,000 |
| Timeout | 30s | 120s | 300s | 300s |
| Max Size | 1 MB | 10 MB | 25 MB | 50 MB |
| Compilation Runs | 1 | 3 | 5 | 10 |
| API Keys | 2 | 5 | 15 | 50 |
| Engines | All 2 | All 4 | All 4 | All 4 |
| At Limit | Hard block | Soft limit | Soft limit | Soft limit |
Billing
Subscription management via Polar.sh. Requires JWT authentication.
Users
Manage your profile and view usage statistics. All endpoints require JWT.
Webhooks
Register HTTP callbacks to receive real-time notifications on compilation, quota, and plan events. Requires JWT. Failed deliveries are automatically retried up to 5 times with exponential backoff.
| Event | Trigger |
|---|---|
compilation.success | Compilation completed successfully |
compilation.failed | Compilation failed (LaTeX error or timeout) |
quota.warning | Usage reached 80% of monthly quota |
quota.exceeded | Usage reached 100% — compilations are blocked |
plan.changed | Plan upgraded, downgraded, or revoked |
Event Payload
{
"event": "compilation.success",
"data": {
"compilationId": "uuid",
"engine": "pdflatex",
"durationMs": 1240,
"inputSizeBytes": 2048,
"apiKeyPrefix": "a2e67d28...",
"timestamp": "2026-02-20T14:30:00Z"
}
}
If a secret is set, the payload is signed with HMAC-SHA256 delivered in the X-FormaTeX-Signature header.
MCP Server
Compile LaTeX directly from AI agents — Claude Desktop, Cursor, Claude Code, n8n, and more — with zero local TeX Live installation. Uses your FormaTeX API key for authentication.
Available Tools
PDF output: Compiled PDFs are returned as base64 text in the tool result. Ask your agent to decode and save it: “Save the PDF to document.pdf”. Claude Code writes the file directly; Claude Desktop provides a one-liner decode script.
fix_latexPrompt
Analyzes compilation errors, finds root causes, and returns corrected LaTeX source. Pass it your LaTeX source and error log.
“Use the fix_latex prompt to help me fix this error: [paste error log]”
Setup
How it works
Connect via URL to the hosted MCP server — no binary download. Ideal for teams sharing one MCP server, no-code platforms (n8n, Make.com), and cloud-based AI agents.
Hosted server
https://mcp.formatex.io/mcpConfigure your AI client
No binary needed — just add the URL and your API key:
{
"mcpServers": {
"formatex": {
"url": "https://mcp.formatex.io/mcp",
"headers": {
"Authorization": "Bearer fex_your_api_key_here"
}
}
}
}Health check
curl https://mcp.formatex.io/health
# → {"status":"ok","service":"formatex-mcp","version":"1.0.0"}Troubleshooting
CLI
Compile LaTeX documents, watch for changes, format source files, and convert between formats — all from the terminal, with no local TeX Live installation.
formatex compile thesis.tex
Installation
macOS / Linux
curl -fsSL https://raw.githubusercontent.com/formatexio/cli/main/install.sh | sh
macOS (Homebrew)
brew install formatexio/tap/formatex
Windows (PowerShell)
iwr https://raw.githubusercontent.com/formatexio/cli/main/install.ps1 | iex
Go
go install github.com/formatexio/cli@latest
Or download a pre-built binary from the releases page for Linux, macOS, or Windows (amd64 and arm64).
Authentication
formatex login
Enter your API key when prompted — it will be verified and saved to ~/.config/formatex/config.json. You can also set FORMATEX_API_KEY as an environment variable, or pass --api-key to any command.
Commands
formatex compile
Compile a .tex file or .zip project to PDF.
formatex compile thesis.tex
formatex compile thesis.tex --engine xelatex --output dist/thesis.pdf
formatex compile project.zip --main chapters/main.tex
| Flag | Default | Description |
|---|---|---|
--engine, -e | pdflatex | pdflatex, xelatex, lualatex, latexmk |
--output, -o | <input>.pdf | Output PDF path |
--runs | auto | Compiler passes (1–5) |
--timeout | plan default | Max compile time in seconds |
--main | auto-detected | Entry-point .tex inside a ZIP |
--open | false | Open PDF after compilation |
On failure, structured errors are printed with file, line number, and AI explanation (if enabled on your plan):
Compilation failed.
Errors:
main.tex:42: Undefined control sequence \foo
AI: The command \foo is not defined. You may have meant \footnote,
or add \newcommand{\foo}{...} to your preamble.
formatex watch
Watch a .tex file and recompile on every save.
formatex watch thesis.tex
formatex watch thesis.tex --engine xelatex --open
| Flag | Default | Description |
|---|---|---|
--engine, -e | pdflatex | LaTeX engine |
--output, -o | <input>.pdf | Output PDF path |
--runs | auto | Number of compiler passes |
--timeout | plan default | Max compile time in seconds |
--open | false | Open PDF on first successful compile |
Uses filesystem events (not polling) for instant detection. Debounced at 300ms to handle editor swap files.
formatex format
Format a LaTeX source file using latexindent.
formatex format main.tex # print to stdout
formatex format main.tex --write # overwrite in-place
formatex format main.tex -o out.tex
| Flag | Default | Description |
|---|---|---|
--write, -w | false | Overwrite the source file in-place |
--output, -o | — | Write formatted output to a different file |
formatex convert
Convert a LaTeX document to another format.
formatex convert thesis.tex # → thesis.docx
formatex convert thesis.tex --format html # → thesis.html
formatex convert thesis.tex --format odt
| Flag | Default | Description |
|---|---|---|
--format, -f | docx | docx, html, odt, epub, markdown, txt |
--output, -o | <input>.<ext> | Output file path |
Global flags
| Flag | Description |
|---|---|
--api-key | API key (overrides config and env var) |
--base-url | Override the API base URL |
CI / CD
Set FORMATEX_API_KEY as a secret and call the CLI directly:
- name: Compile LaTeX
run: formatex compile thesis.tex --output dist/thesis.pdf
env:
FORMATEX_API_KEY: ${{ secrets.FORMATEX_API_KEY }}
For a dedicated GitHub Actions integration with artifact upload, see the FormaTeX GitHub Action.
Python SDK
The official Python client for the FormaTeX API. Supports compilation, rendering, conversion, linting, PDF tools, async jobs, and more.
Installation
pip install formatex
Requires Python 3.9+.
Quick start
from formatex import FormaTexClient
client = FormaTexClient(api_key="fex_your_key_here")
result = client.compile(r"""
\documentclass{article}
\begin{document}
Hello, FormaTeX!
\end{document}
""")
with open("output.pdf", "wb") as f:
f.write(result.pdf)
FormaTexClient
FormaTexClient(
api_key: str, # required — get yours at formatex.io/dashboard/api-keys
*,
timeout: float = 120.0,
)
Use as a context manager to close the underlying HTTP session automatically:
with FormaTexClient(api_key="fex_...") as client:
result = client.compile(latex)
Compilation
compile
result = client.compile(
latex: str,
*,
engine: str = "pdflatex", # pdflatex | xelatex | lualatex | latexmk
runs: int = 0, # 0 = auto (1–5, capped to plan)
timeout: int = 0, # 0 = plan default
files: list[dict] | None = None, # companion files (images, .bib, etc.)
)
# → CompileResult(pdf: bytes, engine: str, duration_ms: int, size_bytes: int, job_id: str, log: str)
Attach companion files using the file_entry helper:
from formatex import FormaTexClient, file_entry
from pathlib import Path
result = client.compile(latex, files=[
file_entry("logo.png", Path("assets/logo.png").read_bytes()),
file_entry("refs.bib", Path("refs.bib").read_bytes()),
])
compile_smart
Automatic engine detection with an 18-step auto-fix pipeline. Pass engine="auto" or omit it.
result = client.compile_smart(latex, engine="auto")
compile_zip
Compile a multi-file project from a ZIP archive.
zip_bytes = Path("project.zip").read_bytes()
result = client.compile_zip(
zip_bytes,
main="chapters/main.tex", # optional — auto-detected if omitted
engine="xelatex",
)
compile_ipynb
Compile a Jupyter notebook to PDF.
nb_bytes = Path("notebook.ipynb").read_bytes()
result = client.compile_ipynb(nb_bytes, engine="pdflatex")
Markup sources
# Compile from Markdown
result = client.compile_markdown(markdown_str)
# Compile from HTML
result = client.compile_html(html_str)
# Compile from reStructuredText
result = client.compile_rst(rst_str)
Async compilation
For long-running documents, submit a job and poll for completion.
# Submit
job = client.async_compile(latex, engine="lualatex")
print(job.job_id, job.status) # pending
# Poll manually
result_info = client.get_job(job.job_id)
# Or wait with exponential back-off (recommended)
result = client.wait_for_job(job.job_id, poll_interval=2, max_wait=300)
pdf_bytes = result.pdf
Rendering
# Render a LaTeX equation to PNG/SVG
img = client.render_equation(
r"E = mc^2",
format="png", # png | svg
dpi=150,
display=True, # display mode (vs inline)
)
with open("eq.png", "wb") as f:
f.write(img.image)
# Batch render multiple equations
results = client.render_equations([
{"latex": r"E = mc^2", "format": "png"},
{"latex": r"\int_0^\infty e^{-x}\,dx", "format": "svg"},
])
Conversion
result = client.convert(
latex,
format="docx", # docx | html | odt | epub | markdown | txt
)
with open("thesis.docx", "wb") as f:
f.write(result.file)
Linting
lint = client.lint(latex)
print(lint.error_count, lint.warning_count)
for d in lint.diagnostics:
print(f"{d.line}:{d.column} [{d.severity}] {d.message}")
PDF tools
# Extract text from page 0
extracted = client.pdf_extract(pdf_bytes, page=0)
print(extracted.text)
# Render pages as images
pages = client.pdf_pages(pdf_bytes, dpi=150)
# Compress
compressed = client.pdf_compress(pdf_bytes)
# Merge
merged = client.pdf_merge([pdf_a, pdf_b, pdf_c])
# Split into individual pages
split = client.pdf_split(pdf_bytes)
# Convert to PDF/A
pdfa = client.pdf_pdfa(pdf_bytes)
Document analysis
# Word count
wc = client.word_count(latex)
# Extract \usepackage dependencies
deps = client.extract_dependencies(latex)
# Check which packages are available on the server
status = client.check_packages(["tikz", "pgfplots", "fontawesome5"])
# Extract title, author, date, etc.
meta = client.extract_metadata(latex)
# Analyse a .bib file
bib = client.analyse_bibliography(bib_str)
Error handling
from formatex.exceptions import FormaTexError, CompilationError, RateLimitError
try:
result = client.compile(latex)
except CompilationError as e:
print("LaTeX errors:", e.log)
except RateLimitError:
print("Rate limit hit — back off and retry")
except FormaTexError as e:
print("API error:", e)
Node.js SDK
The official Node.js client for the FormaTeX API. Fully typed with TypeScript. Supports compilation, rendering, conversion, linting, PDF tools, async jobs, and more.
Installation
npm install formatex-sdk
# or
pnpm add formatex-sdk
Requires Node.js 18+.
Quick start
import { FormaTexClient } from "formatex-sdk";
const client = new FormaTexClient("fex_your_key_here");
const result = await client.compile(`
\\documentclass{article}
\\begin{document}
Hello, FormaTeX!
\\end{document}
`);
await fs.writeFile("output.pdf", result.pdf);
new FormaTexClient(apiKey, options?)
const client = new FormaTexClient(
apiKey: string, // required
options?: FormaTexClientOptions, // { baseUrl?, timeout? }
);
Compilation
compile
const result = await client.compile(latex: string, options?: CompileOptions);
// CompileOptions: { engine?, runs?, timeout?, files? }
// Returns: CompileResult { pdf: Buffer, engine, durationMs, sizeBytes, jobId, log }
Attach companion files using the fileEntry helper:
import { FormaTexClient, fileEntry } from "formatex-sdk";
import { readFileSync } from "fs";
const result = await client.compile(latex, {
engine: "xelatex",
files: [
fileEntry("logo.png", readFileSync("assets/logo.png")),
fileEntry("refs.bib", readFileSync("refs.bib")),
],
});
compileSmart
Automatic engine detection with an 18-step auto-fix pipeline.
const result = await client.compileSmart(latex, { engine: "auto" });
compileZip
Compile a multi-file project from a ZIP archive.
const zip = readFileSync("project.zip");
const result = await client.compileZip(zip, {
main: "chapters/main.tex", // optional
engine: "xelatex",
});
compileIpynb
Compile a Jupyter notebook to PDF.
const nb = readFileSync("notebook.ipynb");
const result = await client.compileIpynb(nb);
Markup sources
// Compile from Markdown
const result = await client.compileMarkdown(markdownStr);
// Compile from HTML
const result = await client.compileHtml(htmlStr);
// Compile from reStructuredText
const result = await client.compileRst(rstStr);
Async compilation
// Submit
const job = await client.asyncCompile(latex, { engine: "lualatex" });
console.log(job.jobId, job.status); // pending
// Wait with exponential back-off (recommended)
const result = await client.waitForJob(job.jobId, { maxWait: 300_000 });
await fs.writeFile("output.pdf", result.pdf);
// Or poll manually
const status = await client.getJob(job.jobId);
const pdf = await client.getJobPdf(job.jobId);
Rendering
// Render a LaTeX equation
const img = await client.renderEquation(r`E = mc^2`, {
format: "png", // "png" | "svg"
dpi: 150,
display: true,
});
await fs.writeFile("eq.png", img.image);
// Batch render
const results = await client.renderEquations([
{ latex: r`E = mc^2`, format: "png" },
{ latex: r`\int_0^\infty e^{-x}\,dx`, format: "svg" },
]);
Conversion
const result = await client.convert(latex, { format: "docx" });
// format: "docx" | "html" | "odt" | "epub" | "markdown" | "txt"
await fs.writeFile("thesis.docx", result.file);
Linting
const lint = await client.lint(latex);
console.log(lint.errorCount, lint.warningCount);
for (const d of lint.diagnostics) {
console.log(`${d.line}:${d.column} [${d.severity}] ${d.message}`);
}
PDF tools
// Extract text from a page
const extracted = await client.pdfExtract(pdfBuffer, 0);
console.log(extracted.text);
// Render pages as images
const pages = await client.pdfPages(pdfBuffer, { dpi: 150 });
// Compress
const compressed = await client.pdfCompress(pdfBuffer);
// Merge
const merged = await client.pdfMerge([pdfA, pdfB, pdfC]);
// Split into individual pages
const split = await client.pdfSplit(pdfBuffer);
// Convert to PDF/A
const pdfa = await client.pdfPDFA(pdfBuffer);
Document analysis
// Word count
const wc = await client.wordCount(latex);
// Extract \usepackage dependencies
const deps = await client.extractDependencies(latex);
// Check which packages are available
const status = await client.checkPackages(["tikz", "pgfplots", "fontawesome5"]);
// Extract title, author, date, etc.
const meta = await client.extractMetadata(latex);
// Analyse a .bib file
const bib = await client.analyzeBibliography(bibStr);
Error handling
import {
FormaTexError,
CompilationError,
RateLimitError,
AuthenticationError,
} from "formatex-sdk";
try {
const result = await client.compile(latex);
} catch (e) {
if (e instanceof CompilationError) {
console.error("LaTeX errors:", e.log);
} else if (e instanceof RateLimitError) {
console.error("Rate limited — back off and retry");
} else if (e instanceof AuthenticationError) {
console.error("Invalid API key");
} else if (e instanceof FormaTexError) {
console.error("API error:", e.message);
}
}
GitHub Actions
Two ways to compile LaTeX in your GitHub Actions workflows: the dedicated FormaTeX Action, or the CLI directly.
FormaTeX Action
The simplest integration. Compiles your document and uploads the PDF as a build artifact automatically.
name: Compile LaTeX
on:
push:
branches: [main]
pull_request:
jobs:
compile:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: formatexio/action@v1
with:
file: thesis.tex
engine: pdflatex # optional — default: pdflatex
output: dist/thesis.pdf # optional — default: <file>.pdf
env:
FORMATEX_API_KEY: ${{ secrets.FORMATEX_API_KEY }}
- uses: actions/upload-artifact@v4
with:
name: thesis-pdf
path: dist/thesis.pdf
Set FORMATEX_API_KEY as a repository secret under Settings → Secrets and variables → Actions.
CLI in CI
Install the CLI in your workflow and call it directly for more control.
name: Compile LaTeX
on:
push:
branches: [main]
jobs:
compile:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install FormaTeX CLI
run: curl -fsSL https://raw.githubusercontent.com/formatexio/cli/main/install.sh | sh
- name: Compile
run: formatex compile thesis.tex --output dist/thesis.pdf
env:
FORMATEX_API_KEY: ${{ secrets.FORMATEX_API_KEY }}
- uses: actions/upload-artifact@v4
with:
name: thesis-pdf
path: dist/thesis.pdf
Multi-file projects
Pass a ZIP archive when your document spans multiple files:
- name: Archive project
run: zip -r project.zip . -x "*.git*"
- name: Compile
run: formatex compile project.zip --main chapters/main.tex --output dist/thesis.pdf
env:
FORMATEX_API_KEY: ${{ secrets.FORMATEX_API_KEY }}
PR preview comments
Post a link to the compiled PDF as a pull request comment:
- name: Compile
id: compile
run: formatex compile thesis.tex --output dist/thesis.pdf
env:
FORMATEX_API_KEY: ${{ secrets.FORMATEX_API_KEY }}
- uses: actions/upload-artifact@v4
id: upload
with:
name: thesis-pdf
path: dist/thesis.pdf
- name: Comment on PR
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `PDF compiled successfully. [Download artifact](${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId})`,
})
Matrix builds (multiple engines)
strategy:
matrix:
engine: [pdflatex, xelatex, lualatex]
steps:
- uses: actions/checkout@v4
- name: Compile with ${{ matrix.engine }}
run: formatex compile thesis.tex --engine ${{ matrix.engine }} --output dist/thesis-${{ matrix.engine }}.pdf
env:
FORMATEX_API_KEY: ${{ secrets.FORMATEX_API_KEY }}

