FormaTeX

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

·10 min read·
LaTeX API Error Codes: Complete Guide

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 StatusMeaningRetryable?
400Bad request — malformed JSON or missing required fieldNo
401Missing or invalid API keyNo
403Valid key, insufficient plan permissionsNo
413Request body exceeds plan size limitNo
422LaTeX compilation failed — see log in responseNo (fix source)
429Rate limit or monthly quota exceededYes (with backoff)
500Internal server errorYes
503Compilation worker unavailableYes

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 source instead of the correct latex field name
  • Omitting the latex field entirely
  • Passing an engine string the API does not recognize (e.g. "pdftex" instead of "pdflatex")
  • Sending a non-JSON Content-Type header

Fix: Always validate your request body against the API schema before sending. Here is the minimal valid request:

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

bash
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, or latexmk on 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:

PlanEngines Available
Freepdflatex 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:

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

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

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

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

latex
% 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:

latex
% Wrong — underscore outside math mode
The variable x_1 is important.

% Correct
The variable $x_1$ is important.

File not found / Package not found

text
! 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)

text
! Font \zf@basefont="NonexistentFont" at 10pt not loadable

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

latex
% 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:

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

  1. Rate limit — you sent too many requests in a short window. The Retry-After response header tells you how many seconds to wait.
  2. Monthly quota exhausted — you have used all compilations for this billing period. The response body will say so explicitly.

Retry strategy for rate limits:

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

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

  1. 4xx (except 429) — do NOT retry. Fix the request first.
  2. 429 with rate limit — retry after Retry-After seconds.
  3. 429 with quota — do NOT retry. Upgrade plan.
  4. 500 — retry up to 3 times with exponential backoff.
  5. 503 — retry up to 4 times with exponential backoff.
  6. 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:

  1. Check the HTTP status code first.
  2. Read the error field in the JSON response body.
  3. If status is 422, extract !-prefixed lines from the log field.
  4. Identify the line number from the l.N reference.
  5. Check if the error is a missing package, undefined command, or syntax issue.
  6. 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.

\end{article}

Back to blog

\related{posts}

One quick thing

We track anonymous usage — page views, feature usage, compilation events — to understand what works and what doesn't. No ads, no personal data, no third-party sharing.

Cookie policy