Search documentation

Search across all sections and endpoints

FormaTeX
Docs

API Reference

Complete reference for the FormaTeX REST API. Compile LaTeX documents to PDF with a single HTTP call.

Quick Example

bash
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.

MethodHeaderUsed For
JWT TokenAuthorization: Bearer <token>User management, billing, admin
API KeyX-API-Key: <key>Compilation endpoints

Errors

All errors return a consistent JSON format with an error message and optional context.

json
{
  "error": "human-readable error message",
  "plan": "free",
  "limit": 100,
  "used": 100
}

Status Codes

CodeMeaning
400Invalid request body
401Missing or invalid authentication
403Plan limit violated or account deactivated
404Resource not found
409Conflict (e.g., email already registered)
422Compilation failed
429Rate limit exceeded
503Billing not configured

Rate Limits

Endpoint GroupLimit
Auth20 req/min
User management60 req/min
Billing20 req/min
Admin60 req/min
Compilation100 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 the filename template
json
{
  "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:

text
&  →  \&                   %  →  \%
$  →  \$                   #  →  \#
_  →  \_                   {  →  \{
}  →  \}                   ~  →  \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

A template is a LaTeX document you save in your account with <<variable>> placeholders. To produce a PDF you send the values; FormaTeX substitutes them and compiles the result. The people and systems that call the API never need to write LaTeX.

Templates are available on Pro and above. A template compile is a normal compilation: it counts against your monthly API compilations and follows the same rate limits and engine rules as /compile.

PlanSaved templates
FreeNone
Pro3
Max25
EntrepriseUnlimited

How it works

  1. Create the template in the dashboard (Templates, New template), from your pipeline with POST /api/v1/templates, or fork one from the template library. Forking copies the library template into your account and counts toward your limit.
  2. Mark the dynamic values in the source with <<key>> and describe each variable in the schema: type, required, default.
  3. Compile with variables. Your backend sends POST /api/v1/templates/user/:id/compile with an API key and a JSON object of values, and receives the PDF.

Who can do what

Everything works with an API key. There is no separate credential for managing templates.

ActionEndpointCredential
List your templatesGET /api/v1/templatesAPI key (X-API-Key)
Create a templatePOST /api/v1/templatesAPI key
Read, update, delete a templateGET, PUT, DELETE /api/v1/templates/user/:idAPI key
Compile a templatePOST /api/v1/templates/user/:id/compileAPI key
Upload, list, remove assets/api/v1/templates/user/:id/assets...API key

The same operations also exist under /api/v1/users/me/templates... with the dashboard's session token (Authorization: Bearer <JWT> from POST /api/v1/auth/login). Both reach the same templates. A key is refused on the session routes and a session token is refused on the key routes, each with 401.

Management calls (everything except compile and list) are limited to 60 per minute per account. Over the limit you get 429 with a Retry-After header.

Placeholders

  • A placeholder is <<key>>. A key starts with a letter or underscore and continues with letters, digits and underscores. Keys are case sensitive.
  • Every occurrence in the source is replaced, including inside commands and environments.
  • A placeholder with no value, no default and no schema entry is left in the document exactly as written.
  • Variables you send that are not in the schema are still substituted wherever a matching placeholder exists.
latex
\documentclass{article}
\begin{document}
Dear <<name>>,

Your order <<order_id>> totals <<amount>> and was placed on <<date>>.
\end{document}

Values are inserted as LaTeX. FormaTeX does not escape them. If a value can contain user text, escape these characters before you send it: \ & % $ # _ { } ~ ^. The substituted document goes through the same validation as any other compile request.

CharacterSend as
&\&
%\%
$\$
#\#
_\_
{ and }\{ and \}
~\textasciitilde{}
^\textasciicircum{}
\\textbackslash{}

Variable schema

The schema is an array with one entry per variable.

FieldTypeDescription
keystringName used in <<key>>. At most 50 characters.
typestring | number | dateValidation applied to the value. Any other type is treated as string.
requiredbooleanThe compile fails with 400 when the value is missing and there is no default.
defaultstringUsed when the request does not send the variable. At most 10,000 characters.
descriptionstringHuman readable label, shown in the dashboard.
  • number must parse as a number, for example 42 or 129.90.
  • date must be YYYY-MM-DD or the word today. today is replaced by the current date when the template is compiled. A default of today works the same way.
  • A required variable that has a default is satisfied by the default.
  • A template can declare up to 50 variables.

Compile a template

bash
# 1. Find the template id
curl https://api.formatex.io/api/v1/templates \
  -H "X-API-Key: $FORMATEX_API_KEY"

# 2. Compile it with values
curl -X POST https://api.formatex.io/api/v1/templates/user/$TEMPLATE_ID/compile \
  -H "X-API-Key: $FORMATEX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"variables": {"name": "Alice Martin", "amount": "129.90"}}' \
  --output invoice.pdf

The response body is the raw PDF. Send Accept: application/json to receive it base64 encoded in a JSON object instead.

Send every value as a JSON string, numbers included: "amount": "129.90". A JSON number or object is rejected with 400.

Errors

StatusWhenBody
400A required variable is missing, or a value fails its type{ "error": "required variable \"name\" is missing" } or { "error": "variable \"amount\" must be a number, got \"ten\"" }
400engine is not a supported engine{ "error": "unsupported engine: nope" }
400The body is not valid JSON, or a value is not a string{ "error": "invalid request: ..." }
401No API key, or a session token sent to a key route{ "error": "missing API key" }
403The engine or a limit is not allowed on your plan{ "error": "..." }
404The template does not exist or belongs to another account{ "error": "template not found" }
422The LaTeX failed to compile{ "success": false, "error": "...", "log": "...", "duration": 410 }
429More than 60 management calls in a minuteRetry-After header
503The engine is temporarily disabled{ "error": "engine_unavailable", "message": "..." }

Manage templates

Push a template from a pipeline with nothing but an API key:

bash
curl -X POST https://api.formatex.io/api/v1/templates \
  -H "X-API-Key: $FORMATEX_API_KEY" \
  -H "Content-Type: application/json" \
  -d @template.json

A template that belongs to another account is 404, never 403, so ids cannot be probed.

Assets

A template can carry files that every compile needs: a logo, a .bib, a .cls or a font. Stored assets are placed next to the source when the template compiles, so the source refers to them by path.

Allowed file types: images (.jpg, .jpeg, .png, .pdf, .eps, .svg, .bmp, .tiff, .tif), fonts (.ttf, .otf, .woff, .woff2, .pfb, .pfm, .tfm), LaTeX support files (.sty, .cls, .bst, .bib, .def, .cfg, .fd, .enc, .map) and data files (.csv, .json).

Limits per template, by plan:

PlanFilesLargest fileTotal size
Pro3015 MB100 MB
Max10030 MB300 MB
Entreprise30050 MB1 GB

Uploading a path that already exists returns 409. A path that tries to leave the template folder, or a file type that is not allowed, returns 400. Going over a limit returns 403.

Use templates from an AI assistant

The MCP server gives an AI assistant five template tools: list_templates returns IDs, names and variables, get_template returns one template with its full source, create_template and update_template save a template or change one, and compile_template takes a template ID and a variables object. They use the same API key, the same Pro requirement and the same template limit as the endpoints above. The tools check the variable schema before saving, and update_template changes only the fields it is given. A saved version is replaced and cannot be recovered. Assets are not available through the MCP server: upload them in the dashboard or with the asset endpoints above.

Plans

View available plans and their limits. Public endpoint.

Plan Comparison

FeatureFreeProMaxEnterprise
Compilations/mo155002,00015,000
Timeout30s120s300s300s
Max Size1 MB10 MB25 MB50 MB
Compilation Runs13510
API Keys251550
EnginesAll 2All 4All 4All 4
At LimitHard blockSoft limitSoft limitSoft 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.

EventTrigger
compilation.successCompilation completed successfully
compilation.failedCompilation failed (LaTeX error or timeout)
quota.warningUsage reached 80% of monthly quota
quota.exceededUsage reached 100% — compilations are blocked
plan.changedPlan upgraded, downgraded, or revoked

Event Payload

json
{
  "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, Claude Code, Cursor, VS Code, Codex, n8n, and any other MCP client — with zero local TeX Live installation. The server authenticates with your FormaTeX API key and works on every plan, including Free. Compilations made through MCP count toward the same monthly API quota as REST calls.

There are two ways to connect:

  • Hosted server at https://mcp.formatex.io/mcp (Streamable HTTP). Nothing to install.
  • Local binary formatex-mcp (stdio). Your client starts it and passes your key in the FORMATEX_API_KEY environment variable.

Available Tools

PDF output: Compile tools return text: job metadata and a Download PDF link. They do not inline the file, which keeps the conversation small. Open the link to download the PDF. When your agent needs the bytes, for example to save the file to disk, it calls fetch_pdf_bytes with the job ID. That is the only tool that returns the PDF as base64.

fix_latex

Prompt

Analyzes compilation errors, finds root causes, and returns structured instructions for fixing the LaTeX source. Pass it your LaTeX source and the error log; the engine is optional.

“Use the fix_latex prompt to help me fix this error: [paste error log]”

The server also exposes two resources for clients that read MCP resources: formatex://engines (available engines) and formatex://usage (this month's usage and plan limits).

Setup

How it works

Your client connects to mcp.formatex.io over Streamable HTTP. Nothing to install.

Hosted server

https://mcp.formatex.io/mcp

Connect your client to the hosted endpoint with your API key.

Claude Desktop and claude.ai custom connectors Claude Desktop and claude.ai add remote servers as custom connectors in the app, not in a configuration file.

  1. Open Customize > Connectors in Claude Desktop or at claude.ai. On Team and Enterprise plans, an owner adds it under Organization settings > Connectors.

  2. Choose Add custom connector and enter:

    NameFormaTeX
    URLhttps://mcp.formatex.io/mcp
  3. Authenticate with FormaTeX. Either option works:

    • Sign in. Claude opens a FormaTeX page where you paste your API key once. There is nothing else to copy.

    • Request headers. Add this header instead of signing in:

      HeaderAuthorization: Bearer YOUR_FORMATEX_API_KEY
  4. Turn FormaTeX on for your conversation, then run the prompt below.

Good to know

  • Custom connectors are available on every plan. Free accounts can add one.

Health check

bash
curl https://mcp.formatex.io/health
# {"status":"ok","service":"formatex-mcp","version":"1.1.0"}

Keep the key out of git

Project files such as .mcp.json, .cursor/mcp.json and .vscode/mcp.json are usually committed. Use the environment variable form, a user-level file, or add the file to .gitignore. If a key ever reaches a repository, revoke it and create a new one.

Try it: In your client, run this prompt to verify the integration:

Use FormaTeX to compile this LaTeX to PDF and give me the download link: \documentclass{article}\begin{document}Hello, FormaTeX!\end{document}

A working setup shows a tool call such as compile_smart and a Download PDF link in the answer.

Client configuration reference

ClientWhere to configureNotes
Claude (claude.ai, Claude Desktop)Customize, Connectors, Add custom connectorURL https://mcp.formatex.io/mcp. Sign in with OAuth; the authorization page asks for your API key. claude_desktop_config.json is only for local stdio servers.
Claude Code.mcp.json at the project root, or claude mcp addHTTP entries need "type": "http". ${VAR} expansion works in .mcp.json.
Cursor.cursor/mcp.json or ~/.cursor/mcp.jsonRemote entries take url and headers. Use ${env:FORMATEX_API_KEY} for the key.
VS Code.vscode/mcp.jsonThe top-level key is servers, not mcpServers. Remote entries need "type": "http".
Codex~/.codex/config.toml[mcp_servers.formatex] with url and bearer_token_env_var = "FORMATEX_API_KEY", or command = "formatex-mcp" for the local binary.

For Claude Code, the CLI registration for the hosted server is:

bash
claude mcp add --transport http formatex https://mcp.formatex.io/mcp \
  --header "Authorization: Bearer $FORMATEX_API_KEY"

For the local binary:

bash
go install github.com/formatexio/mcp/cmd/formatex-mcp@latest
claude mcp add --env FORMATEX_API_KEY=fex_your_key --transport stdio formatex -- /path/to/formatex-mcp

Prebuilt binaries are on the releases page.

Authentication

  • Hosted server: send your API key (it starts with fex_) as Authorization: Bearer <key> or X-API-Key: <key>. A request without a key is rejected with 401.
  • Claude custom connectors: the connector signs in with OAuth. The authorization page asks for your API key.
  • Local binary: the key comes from the FORMATEX_API_KEY environment variable.

The hosted server speaks Streamable HTTP at /mcp. There is no /sse endpoint, so a client that supports only the older SSE transport should use the local binary. GET /health reports the server status.

Plans

  • Free: pdflatex and xelatex.
  • Pro and above: adds lualatex and latexmk, and saved templates (list_templates, get_template, create_template, update_template, compile_template).

Use from the Anthropic Messages API

The Messages API MCP connector can call the hosted server directly. It is a beta feature: send the mcp-client-2025-11-20 beta header, list the server under mcp_servers, and enable its tools with an mcp_toolset entry. The connector supports tool calls only, so resources and the fix_latex prompt are available in MCP clients rather than through the API.

python
import os

import anthropic

client = anthropic.Anthropic()

response = client.beta.messages.create(
    model="claude-sonnet-5-5",
    max_tokens=2048,
    betas=["mcp-client-2025-11-20"],
    mcp_servers=[
        {
            "type": "url",
            "url": "https://mcp.formatex.io/mcp",
            "name": "formatex",
            "authorization_token": os.environ["FORMATEX_API_KEY"],
        }
    ],
    tools=[{"type": "mcp_toolset", "mcp_server_name": "formatex"}],
    messages=[
        {
            "role": "user",
            "content": "Write a one-page LaTeX note on gradient descent, compile it, and give me the PDF link.",
        }
    ],
)

Troubleshooting

The tools do not appear in my client

Quit and reopen the client completely after changing its configuration. Check the file for JSON errors, and for a local server use the absolute path to formatex-mcp. In Claude, make sure the FormaTeX connector is turned on for the conversation.

I get 401 or an authentication error

The server needs your API key (it starts with fex_) in an Authorization: Bearer header or an X-API-Key header. If your configuration reads the key from an environment variable, make sure the variable is set in the environment the client starts in, then restart the client.

How do I get the PDF?

Compile tools return the compilation details and an authenticated Download PDF link, not the file itself. Open the link, or ask your agent to call fetch_pdf_bytes with the job ID when it needs the raw bytes to save or inspect the file.

lualatex or latexmk fails on the Free plan

The Free plan includes pdflatex and xelatex. lualatex and latexmk need Pro or higher. Ask for pdflatex or xelatex, or upgrade your plan.

A template tool returns a plan error

Saved templates are available on Pro and higher, up to a template limit per plan. Upgrade your plan, delete a template you no longer need in the dashboard, or send the LaTeX source with compile_latex or compile_smart instead.

Claude Desktop ignores the URL in claude_desktop_config.json

That file only configures local servers. Add the hosted endpoint as a custom connector in Customize > Connectors instead, using https://mcp.formatex.io/mcp.

How does signing in to the Claude connector work?

When you choose Sign in, Claude opens a FormaTeX page that asks for your API key. Paste the full key once and approve. If it fails, remove the connector and add it again, or use the Request headers option with Authorization: Bearer and your key.

Claude Code reports an invalid MCP configuration

HTTP servers must set "type": "http" in .mcp.json, and an entry with only a url is rejected. The project file is .mcp.json at the project root, not .claude/mcp.json.

VS Code does not show the server

VS Code reads the top-level "servers" key in .vscode/mcp.json, not "mcpServers". Rename the key, then run MCP: List Servers to confirm it loads.

My client asks for an SSE URL

FormaTeX has no SSE endpoint. Use Streamable HTTP at https://mcp.formatex.io/mcp. In Gemini CLI the field is httpUrl, in Cline set type to streamableHttp, and in n8n choose HTTP Streamable.

My key ended up in a repository

Revoke it on the API keys page and create a new one. Then use an environment variable or a user-level file so the key stays out of git.

CLI

Compile LaTeX documents, watch for changes, format source files, and convert between formats — all from the terminal, with no local TeX Live installation.

bash
formatex compile thesis.tex

Installation

macOS / Linux

bash
curl -fsSL https://raw.githubusercontent.com/formatexio/cli/main/install.sh | sh

macOS (Homebrew)

bash
brew install formatexio/tap/formatex

Windows (PowerShell)

powershell
iwr https://raw.githubusercontent.com/formatexio/cli/main/install.ps1 | iex

Go

bash
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

bash
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.

bash
formatex compile thesis.tex
formatex compile thesis.tex --engine xelatex --output dist/thesis.pdf
formatex compile project.zip --main chapters/main.tex
FlagDefaultDescription
--engine, -epdflatexpdflatex, xelatex, lualatex, latexmk
--output, -o<input>.pdfOutput PDF path
--runsautoCompiler passes (1–5)
--timeoutplan defaultMax compile time in seconds
--mainauto-detectedEntry-point .tex inside a ZIP
--openfalseOpen PDF after compilation

On failure, structured errors are printed with file, line number, and AI explanation (if enabled on your plan):

text
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.

bash
formatex watch thesis.tex
formatex watch thesis.tex --engine xelatex --open
FlagDefaultDescription
--engine, -epdflatexLaTeX engine
--output, -o<input>.pdfOutput PDF path
--runsautoNumber of compiler passes
--timeoutplan defaultMax compile time in seconds
--openfalseOpen 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.

bash
formatex format main.tex           # print to stdout
formatex format main.tex --write   # overwrite in-place
formatex format main.tex -o out.tex
FlagDefaultDescription
--write, -wfalseOverwrite the source file in-place
--output, -o—Write formatted output to a different file

formatex convert

Convert a LaTeX document to another format.

bash
formatex convert thesis.tex                    # → thesis.docx
formatex convert thesis.tex --format html      # → thesis.html
formatex convert thesis.tex --format odt
FlagDefaultDescription
--format, -fdocxdocx, html, odt, epub, markdown, txt
--output, -o<input>.<ext>Output file path

Global flags

FlagDescription
--api-keyAPI key (overrides config and env var)
--base-urlOverride the API base URL

CI / CD

Set FORMATEX_API_KEY as a secret and call the CLI directly:

yaml
- 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

bash
pip install formatex

Requires Python 3.9+.

Quick start

python
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

python
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:

python
with FormaTexClient(api_key="fex_...") as client:
    result = client.compile(latex)

Compilation

compile

python
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:

python
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.

python
result = client.compile_smart(latex, engine="auto")

compile_zip

Compile a multi-file project from a ZIP archive.

python
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.

python
nb_bytes = Path("notebook.ipynb").read_bytes()
result = client.compile_ipynb(nb_bytes, engine="pdflatex")

Markup sources

python
# 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.

python
# 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

python
# 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

python
result = client.convert(
    latex,
    format="docx",  # docx | html | odt | epub | markdown | txt
)
with open("thesis.docx", "wb") as f:
    f.write(result.file)

Linting

python
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

python
# 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

python
# 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

python
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

bash
npm install formatex-sdk
# or
pnpm add formatex-sdk

Requires Node.js 18+.

Quick start

ts
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?)

ts
const client = new FormaTexClient(
  apiKey: string,                  // required
  options?: FormaTexClientOptions, // { baseUrl?, timeout? }
);

Compilation

compile

ts
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:

ts
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.

ts
const result = await client.compileSmart(latex, { engine: "auto" });

compileZip

Compile a multi-file project from a ZIP archive.

ts
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.

ts
const nb = readFileSync("notebook.ipynb");
const result = await client.compileIpynb(nb);

Markup sources

ts
// 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

ts
// 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

ts
// 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

ts
const result = await client.convert(latex, { format: "docx" });
// format: "docx" | "html" | "odt" | "epub" | "markdown" | "txt"
await fs.writeFile("thesis.docx", result.file);

Linting

ts
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

ts
// 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

ts
// 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

ts
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.

yaml
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.

yaml
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:

yaml
- 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:

yaml
- 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)

yaml
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 }}

Ready to start compiling?

Get your API key in 30 seconds. 15 free compilations every month.