\begin{article}
LaTeX API Error Codes: Complete Guide
Understand every error code returned by a LaTeX compilation API — from 422 compile failures to 429 rate limits — and fix them fast.

When you POST LaTeX source to a compilation API and something goes wrong, you need to know exactly what failed and why — quickly. This LaTeX API error codes guide covers every HTTP status code FormatEx can return, what each one means in practice, how to parse LaTeX compiler logs to extract the root cause, and which retry strategies actually work. Bookmark it and keep it open while you build.
HTTP Error Codes at a Glance
Before digging into each code, here is a reference table of everything FormatEx can return:
| HTTP Status | Meaning | Retryable? |
|---|---|---|
| 400 | Bad request — malformed JSON or missing required field | No |
| 401 | Missing or invalid API key | No |
| 403 | Valid key, insufficient plan permissions | No |
| 413 | Request body exceeds plan size limit | No |
| 422 | LaTeX compilation failed — see log in response | No (fix source) |
| 429 | Rate limit or monthly quota exceeded | Yes (with backoff) |
| 500 | Internal server error | Yes |
| 503 | Compilation worker unavailable | Yes |
400 Bad Request
A 400 means the FormatEx API rejected your request before it even tried to compile anything. The JSON body was malformed, a required field was absent, or a field value was an unrecognized type.
Common triggers:
- Sending
sourceinstead of the correctlatexfield name - Omitting the
latexfield entirely - Passing an engine string the API does not recognize (e.g.
"pdftex"instead of"pdflatex") - Sending a non-JSON
Content-Typeheader
Fix: Always validate your request body against the API schema before sending. Here is the minimal valid request:
const response = await fetch("https://api.formatex.io/api/v1/compile", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": process.env.FORMATEX_API_KEY!,
},
body: JSON.stringify({
latex: `\\documentclass{article}\\begin{document}Hello\\end{document}`,
engine: "pdflatex", // "pdflatex" | "xelatex" | "lualatex" | "latexmk"
}),
});If you receive a 400, read the error field in the JSON response — it will tell you which field failed validation.
401 Unauthorized
A 401 means the X-API-Key header was missing, empty, or contained a key that does not exist in FormatEx's database. Proper API key management and rotation practices can help you avoid stale or misconfigured keys in your environment.
Common triggers:
- Forgetting to attach the header at all
- Using a key prefix (e.g.
fx_live_...) but truncating it when storing in an environment variable - The key was deleted from the dashboard but the environment variable was not updated
Fix: Confirm your key is present and complete:
curl -s -o /dev/null -w "%{http_code}" \
-H "X-API-Key: $FORMATEX_API_KEY" \
https://api.formatex.io/api/v1/compile \
-d '{"latex":"\\documentclass{article}\\begin{document}test\\end{document}","engine":"pdflatex"}'If this returns 401, go to your FormatEx dashboard, verify the key is active, and re-copy it. The raw key is shown only at creation time — if you lost it, rotate it and update your environment.
403 Forbidden
A 403 means the API key is valid but the action you requested is not permitted on your current plan.
Common triggers:
- Using
xelatex,lualatex, orlatexmkon the Free plan (pdflatex only) - Requesting a compilation timeout above your plan's limit
- Sending a file larger than your plan's max input size
FormatEx engine availability by plan:
| Plan | Engines Available |
|---|---|
| Free | pdflatex only |
| Developer ($12/mo) | pdflatex, xelatex |
| Pro ($49/mo) | pdflatex, xelatex, lualatex, latexmk |
| Scale ($199/mo) | all engines, priority queue |
To understand the differences between engines before choosing one, see the complete guide to LaTeX engines — it covers when to use pdfLaTeX, XeLaTeX, LuaLaTeX, and latexmk.
Fix: Either upgrade your plan at formatex.io or switch to an engine your plan supports:
import os
import httpx
def compile_latex(source: str, engine: str = "pdflatex") -> bytes:
resp = httpx.post(
"https://api.formatex.io/api/v1/compile",
headers={"X-API-Key": os.environ["FORMATEX_API_KEY"]},
json={"latex": source, "engine": engine},
timeout=120,
)
if resp.status_code == 403:
detail = resp.json().get("error", "")
raise PermissionError(f"Plan restriction: {detail}")
resp.raise_for_status()
return resp.content422 Unprocessable Entity — LaTeX Compilation Failed
This is the most important status code to understand. A 422 means the API successfully received your request, spun up the LaTeX engine, attempted to compile — and the engine exited with errors. Your LaTeX source is the problem, not the API.
The response body includes a log field containing the full compiler stdout/stderr. Parsing this correctly is the skill that separates fast debugging from hours of frustration. For teams that want to automate this process, AI-powered LaTeX error fixing can detect and patch common compilation errors before they ever reach you.
Reading the Compiler Log
The log is verbose. Focus on lines that begin with ! — those are fatal errors:
! Undefined control sequence.
l.14 \textbf{\highligh
t{important}}The l.14 tells you the line number. The caret shows where the parser stopped. In this case, \highlight is not defined — either a typo or a missing package.
Most Common LaTeX Errors and Their Fixes
Undefined control sequence
! Undefined control sequence.You used a command that does not exist or requires a package that is not loaded. Fix: add the package in your preamble.
% Before: fails with "Undefined control sequence \href"
\href{https://formatex.io}{FormatEx}
% After: works
\usepackage{hyperref}
\href{https://formatex.io}{FormatEx}Missing $ inserted / Missing \endgroup
These usually mean you used a math symbol outside of math mode, or vice versa:
% Wrong — underscore outside math mode
The variable x_1 is important.
% Correct
The variable $x_1$ is important.File not found / Package not found
! LaTeX Error: File 'coolpackage.sty' not found.The package does not exist in TeX Live, or you misspelled it. FormatEx ships a full TeX Live installation — if a package exists on CTAN, it is available. Double-check the exact package name on CTAN.
Font not found (xelatex/lualatex)
! Font \zf@basefont="NonexistentFont" at 10pt not loadableXeLaTeX and LuaLaTeX support system fonts via fontspec. FormatEx's compilation environment has the standard TeX Live fonts. If you need a custom font, you must embed it in your source as a base64-encoded file or use a TeX Live font by name. See the XeLaTeX vs pdfLaTeX comparison for a full breakdown of Unicode support and font handling differences between engines.
% Works — uses a TeX Live font
\usepackage{fontspec}
\setmainfont{TeX Gyre Pagella}
% Fails — not available in the container
\setmainfont{Helvetica Neue}Extracting Errors Programmatically
Parse the log in your application to surface clean error messages to users:
function extractLatexErrors(log: string): string[] {
const lines = log.split("\n");
const errors: string[] = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line.startsWith("!")) {
// Grab the error message and the offending line reference
const errorMsg = line.slice(2).trim();
const locationLine = lines.slice(i + 1, i + 4).find(l => /^l\.\d+/.test(l));
const location = locationLine ? locationLine.match(/^l\.(\d+)/)?.[1] : null;
errors.push(location ? `Line ${location}: ${errorMsg}` : errorMsg);
}
}
return errors;
}
// Usage after a 422 response:
const { log } = await response.json();
const errors = extractLatexErrors(log);
console.error("Compilation errors:", errors);
// ["Line 14: Undefined control sequence.", "Line 22: Missing $ inserted."]429 Too Many Requests
A 429 means you have hit either a per-minute rate limit or your plan's monthly compilation quota.
Two distinct cases:
- Rate limit — you sent too many requests in a short window. The
Retry-Afterresponse header tells you how many seconds to wait. - Monthly quota exhausted — you have used all compilations for this billing period. The response body will say so explicitly.
Retry strategy for rate limits:
import time
import httpx
def compile_with_retry(source: str, max_retries: int = 3) -> bytes:
for attempt in range(max_retries):
resp = httpx.post(
"https://api.formatex.io/api/v1/compile",
headers={"X-API-Key": os.environ["FORMATEX_API_KEY"]},
json={"latex": source, "engine": "pdflatex"},
timeout=60,
)
if resp.status_code == 429:
retry_after = int(resp.headers.get("Retry-After", 2 ** attempt))
if "quota" in resp.json().get("error", "").lower():
raise RuntimeError("Monthly quota exhausted — upgrade plan")
time.sleep(retry_after)
continue
resp.raise_for_status()
return resp.content
raise RuntimeError(f"Failed after {max_retries} retries")For quota exhaustion, the only fix is upgrading your plan. The Free plan allows 15 compilations per month. The Developer plan gives you 200, Pro gives 500, and Scale gives 2,000. For a deeper treatment of exponential backoff, jitter, and request queuing strategies when handling 429s at scale, see the dedicated rate limiting guide.
503 Service Unavailable
A 503 means the compilation worker pool is temporarily unavailable — the API received your request but could not hand it off to a worker. This is transient and always retryable.
Use exponential backoff with jitter:
async function compileWithBackoff(
latex: string,
engine = "pdflatex",
maxAttempts = 4
): Promise<ArrayBuffer> {
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const response = await fetch("https://api.formatex.io/api/v1/compile", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": process.env.FORMATEX_API_KEY!,
},
body: JSON.stringify({ latex, engine }),
});
if (response.ok) return response.arrayBuffer();
if (response.status === 503 || response.status === 500) {
if (attempt === maxAttempts - 1) throw new Error("Service unavailable after retries");
const delay = Math.min(1000 * 2 ** attempt + Math.random() * 500, 15000);
await new Promise((r) => setTimeout(r, delay));
continue;
}
// Non-retryable — throw immediately
const { error } = await response.json();
throw new Error(`Compilation failed (${response.status}): ${error}`);
}
throw new Error("Unreachable");
}Retry Decision Tree
Not all errors should be retried. Use this logic in your error handler:
- 4xx (except 429) — do NOT retry. Fix the request first.
- 429 with rate limit — retry after
Retry-Afterseconds. - 429 with quota — do NOT retry. Upgrade plan.
- 500 — retry up to 3 times with exponential backoff.
- 503 — retry up to 4 times with exponential backoff.
- Network timeout — retry up to 3 times. If the engine is running a large document, increase your client timeout before retrying (Pro plan allows up to 120s, Scale allows 300s).
Debugging Workflow
When a compilation fails, follow this order:
- Check the HTTP status code first.
- Read the
errorfield in the JSON response body. - If status is 422, extract
!-prefixed lines from thelogfield. - Identify the line number from the
l.Nreference. - Check if the error is a missing package, undefined command, or syntax issue.
- Fix the LaTeX source and recompile.
Most LaTeX errors resolve immediately once you find the offending line. The FormatEx log field gives you the exact same output you would see from running pdflatex locally — which means every Stack Overflow answer and TeX Stack Exchange thread applies directly. If you are just getting started with the API, the FormatEx getting started guide walks through your first API key, first compilation, and basic error handling end to end.
If you are not yet using FormatEx, sign up at formatex.io — the Free plan gives you 15 compilations per month with no credit card required. When you are ready to scale, the Developer plan at $12/month removes the engine restriction and raises the limit to 200 compilations. Use the playground at formatex.io/playground to test your LaTeX source and inspect the full compiler log before integrating into your application.
Related Articles
- LaTeX API Rate Limiting and Retry Logic — Deep dive into exponential backoff, jitter, and request queuing strategies for handling 429s in production
- LaTeX API Authentication Best Practices — API key management, rotation, and secrets manager integration to prevent 401 errors
- The Complete Guide to LaTeX Engines — Understand pdfLaTeX, XeLaTeX, LuaLaTeX, and latexmk so you never hit a 403 engine restriction unexpectedly
- Smart Compile: AI-Powered LaTeX Error Fixing — Automate 422 error resolution with an AI pipeline that detects and fixes compilation errors
- Getting Started with FormaTeX — First API key, first compilation, and foundational error handling for new integrations
\end{article}
\related{posts}




