{
  "openapi": "3.1.0",
  "info": {
    "title": "Pressa API",
    "version": "1.5.0",
    "summary": "Compile LaTeX into publication-quality PDFs over HTTP.",
    "description": "Pressa is document infrastructure for AI agents. Send LaTeX source, receive a PDF URL.\n\nPOST /api/v1/compile works with NO credentials, 3 successful compilations per day per IP. Failed compilations do not consume that quota, so an agent may iterate on its LaTeX until it compiles. Every other endpoint requires an API key.\n\nPressa is a LaTeX compiler, not a text-to-PDF converter. Callers are expected to convert markdown, plain text or structured data into LaTeX themselves. Compilation failures return a `diagnosis` object with a concrete suggested fix so a caller can correct the source and retry without human involvement.",
    "contact": {
      "url": "https://pressa.dev"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://pressa.dev/terms"
    }
  },
  "servers": [
    {
      "url": "https://api.pressa.dev",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "compile",
      "description": "LaTeX to PDF. Usable without an API key, like the public template render."
    },
    {
      "name": "templates",
      "description": "Stored LaTeX templates."
    },
    {
      "name": "render",
      "description": "Render a stored template with JSON data."
    },
    {
      "name": "account",
      "description": "Usage and plan limits."
    },
    {
      "name": "mcp",
      "description": "Model Context Protocol endpoint (Streamable HTTP). JSON-RPC 2.0, stateless, no key required."
    },
    {
      "name": "public templates",
      "description": "The public template gallery at pressa.dev/templates."
    }
  ],
  "paths": {
    "/api/v1/compile": {
      "post": {
        "tags": [
          "compile"
        ],
        "operationId": "compile",
        "summary": "Compile LaTeX source into a PDF",
        "description": "Authentication is OPTIONAL. Without credentials the request is served by the anonymous tier: pdflatex only, 3 pages, 30 KB of source, no assets, 3 successful compilations per day per IP, and PDF links that expire after 1 hour.\n\nAn absent key selects the anonymous tier. An invalid or revoked key returns 401 rather than downgrading, so a broken configuration fails loudly instead of appearing to work intermittently.\n\nWith a key, a document over the plan's page limit, or over its source-size limit but under 300 KB (at most 5 such previews per day), is not rejected: the response is 200 with `preview: true`, a PDF of the first pages the plan allows, `total_pages`, and `plan_required` naming the smallest plan that covers the whole document. Show the user the preview and relay `warning`; never drop pages silently.",
        "security": [
          {},
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CompileRequest"
              },
              "examples": {
                "minimal": {
                  "summary": "Smallest working document",
                  "value": {
                    "latex": "\\documentclass{article}\\begin{document}Hello\\end{document}"
                  }
                },
                "withAssets": {
                  "summary": "With an inline image (API key required)",
                  "value": {
                    "latex": "\\documentclass{article}\\usepackage{graphicx}\\begin{document}\\includegraphics[width=3cm]{logo.png}\\end{document}",
                    "compiler": "pdflatex",
                    "assets": {
                      "logo.png": "iVBORw0KGgoAAAANSUhEUgAA..."
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "PDF produced.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CompileSuccess"
                }
              }
            }
          },
          "401": {
            "description": "A key was supplied but is invalid or revoked. Absent credentials do NOT produce this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Requested compiler is not available on the caller's plan (`compiler_not_available`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Source over 300 KB, or over the plan limit for an anonymous caller, or the day's size previews used up (`preview_attempts_remaining_today: 0`), or assets over budget (`latex_too_large`, `assets_too_large`). With a key, a source over the plan's limit but under 300 KB is otherwise compiled and returned as a 200 preview.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "The request was understood but cannot produce a PDF. Includes `not_latex_source`, `compilation_failed`, `compilation_timeout`, `page_limit_exceeded`, `pdf_too_large`, `pdf_size_unknown` (transient, retry the same request), and `assets_not_available_anonymously`. With a key, `page_limit_exceeded` is normally replaced by a 200 preview of the first pages the plan allows; it is still sent when the preview is switched off or could not be produced. When `error` is `compilation_failed`, a `diagnosis` object describes the fix.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CompileFailure"
                },
                "examples": {
                  "missingPackage": {
                    "summary": "Missing \\usepackage, machine-fixable",
                    "value": {
                      "error": "compilation_failed",
                      "message": "\\toprule is undefined because the 'booktabs' package was not loaded.",
                      "line": 14,
                      "retryable": true,
                      "diagnosis": {
                        "error_class": "missing_package",
                        "summary": "\\toprule is undefined because the 'booktabs' package was not loaded.",
                        "suggested_fix": "Add \\usepackage{booktabs} to the preamble, before \\begin{document}.",
                        "suggested_package": "booktabs",
                        "symbol": "\\toprule",
                        "line": 14,
                        "retryable": true
                      },
                      "agent_hint": "Add \\usepackage{booktabs} to the preamble, before \\begin{document}. Apply this fix to the LaTeX source and call compile again yourself - do not ask the user to fix LaTeX."
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Quota exhausted (`rate_limit` for an account's monthly limit, `anonymous_quota_exhausted` for the keyless daily limit, both with `next_step`), or `preview_in_progress`: a size preview is already compiling for this account; one runs at a time, retry in `retry_after_seconds`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/QuotaError"
                }
              }
            }
          },
          "503": {
            "description": "Two transient conditions, both `retryable`. `anonymous_capacity_exhausted`: the keyless tier is at its global hourly ceiling (never sent to authenticated callers). `preview_unavailable`: the store that limits size previews to one per account is not answering, so a size preview is refused for the moment (authenticated callers only, `retry_after_seconds: 60`); a source within the plan's size limit still compiles.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/QuotaError"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/usage": {
      "get": {
        "tags": [
          "account"
        ],
        "operationId": "getUsage",
        "summary": "Current usage, plan limits and API key metadata",
        "responses": {
          "200": {
            "description": "Usage snapshot.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key."
          }
        }
      }
    },
    "/api/v1/templates": {
      "get": {
        "tags": [
          "templates"
        ],
        "operationId": "listTemplates",
        "summary": "List saved templates",
        "responses": {
          "200": {
            "description": "Templates owned by the caller."
          },
          "401": {
            "description": "Missing or invalid API key."
          }
        }
      },
      "post": {
        "tags": [
          "templates"
        ],
        "operationId": "saveTemplate",
        "summary": "Create or update a template by name",
        "description": "Idempotent on `name`: an existing template with the same name is updated and `created` is false. `placeholder_engine: \"liquid\"` saves a placeholder template for the render endpoint, and converts an existing raw LaTeX template in place (`promoted` is true). A source containing Liquid placeholders sent without `placeholder_engine` is refused with `liquid_placeholders_in_v1_template`, because raw LaTeX would print them literally.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 100
                  },
                  "latex_content": {
                    "type": "string",
                    "description": "Required for a new template. Optional on an update, and when converting the saved source with placeholder_engine \"liquid\"."
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 500
                  },
                  "instructions": {
                    "type": "string"
                  },
                  "placeholder_engine": {
                    "type": "string",
                    "enum": [
                      "liquid",
                      "none"
                    ],
                    "description": "\"liquid\": the source contains Liquid placeholders, filled by the render endpoint. \"none\": raw LaTeX whose braces only look like placeholders. Omit to keep the template's current kind."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Existing template updated."
          },
          "201": {
            "description": "Template created."
          },
          "401": {
            "description": "Missing or invalid API key."
          },
          "422": {
            "description": "`validation_failed` (with a `fields` list naming each field, rule and limit), `liquid_placeholders_in_v1_template`, `liquid_syntax_error`, `template_security_violation`, `engine_downgrade_not_supported` or `invalid_placeholder_engine`. Every refusal leaves the template unchanged."
          }
        }
      }
    },
    "/api/v1/templates/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": "Numeric template id or the template name."
        }
      ],
      "get": {
        "tags": [
          "templates"
        ],
        "operationId": "getTemplate",
        "summary": "Fetch one template",
        "responses": {
          "200": {
            "description": "Template detail."
          },
          "401": {
            "description": "Missing or invalid API key."
          },
          "404": {
            "description": "No such template for this account."
          }
        }
      },
      "delete": {
        "tags": [
          "templates"
        ],
        "operationId": "deleteTemplate",
        "summary": "Delete a template",
        "responses": {
          "200": {
            "description": "Deleted."
          },
          "401": {
            "description": "Missing or invalid API key."
          },
          "404": {
            "description": "No such template for this account."
          }
        }
      }
    },
    "/api/v2/templates/{id}/render": {
      "post": {
        "tags": [
          "render"
        ],
        "operationId": "renderTemplate",
        "summary": "Render a placeholder template with JSON data",
        "description": "Requires a paid plan and a V2 (placeholder-aware) template. Placeholders use Liquid syntax, for example {{ customer_name }}. The whole call is capped at 60 seconds.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Numeric template id or template name."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "data"
                ],
                "properties": {
                  "data": {
                    "type": "object",
                    "description": "Values for the template's placeholders, keyed by placeholder name.",
                    "additionalProperties": true
                  }
                }
              },
              "example": {
                "data": {
                  "customer_name": "Acme Ltd",
                  "invoice_total": "1240.00"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "PDF rendered. Same shape as compile, plus a `template` block with id, name and version."
          },
          "401": {
            "description": "Missing or invalid API key."
          },
          "403": {
            "description": "`plan_required` - render is a paid feature."
          },
          "404": {
            "description": "No such template for this account."
          },
          "422": {
            "description": "`template_engine_mismatch` (convert the template with POST /api/v2/templates/{id}/promote), missing required placeholder data, or compilation failure."
          },
          "429": {
            "description": "Monthly quota or per-minute render limit exceeded."
          },
          "504": {
            "description": "The render exceeded the 60 second wall clock."
          }
        }
      }
    },
    "/api/v2/templates/{id}/promote": {
      "post": {
        "tags": [
          "templates"
        ],
        "operationId": "promoteTemplate",
        "summary": "Convert a raw LaTeX template into a placeholder template",
        "description": "Keeps the id, name, instructions and creation date. Without `latex_with_placeholders` the saved source is promoted as it is. The source must pass the template security policy and parse as Liquid.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Numeric template id or template name."
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "latex_with_placeholders": {
                    "type": "string",
                    "description": "LaTeX with Liquid placeholders. Defaults to the saved source."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Template converted. Includes the derived `schema` and `sample_data`, and `promoted: true`."
          },
          "401": {
            "description": "Missing or invalid API key."
          },
          "404": {
            "description": "No such template for this account."
          },
          "422": {
            "description": "`already_v2`, `liquid_syntax_error` or `template_security_violation`. The template is left unchanged."
          }
        }
      }
    },
    "/mcp": {
      "post": {
        "tags": [
          "mcp"
        ],
        "operationId": "mcp",
        "summary": "Model Context Protocol endpoint (Streamable HTTP)",
        "description": "JSON-RPC 2.0 over HTTP. Stateless: no initialize handshake is required and no session header is used, though `initialize` is still answered for clients on older protocol revisions. Tool visibility depends on credentials: with no Authorization header only the tools that work without an account are advertised: `compile` and the public template gallery (`list_public_templates`, `get_public_template`, `render_public_template`). With a header present the full set is advertised (the keyless tools plus render, usage, list/get/save/delete_template, list/get/save/delete_asset). Visibility keys off the header being PRESENT, not valid, so a mistyped key surfaces as a clear 401 from the tool rather than as missing features.\n\nConnect with: claude mcp add --transport http pressa https://api.pressa.dev/mcp\n\nAuthentication is optional and handled by the compile action underneath: no credentials selects the anonymous tier, an invalid key returns an error inside the tool result.\n\nSupported methods: initialize, server/discover, tools/list, tools/call, ping, resources/list, prompts/list.",
        "security": [
          {},
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "A JSON-RPC 2.0 request object, or an array of them for a batch."
              },
              "examples": {
                "listTools": {
                  "summary": "List tools with no handshake",
                  "value": {
                    "jsonrpc": "2.0",
                    "id": 1,
                    "method": "tools/list"
                  }
                },
                "compile": {
                  "summary": "Compile LaTeX",
                  "value": {
                    "jsonrpc": "2.0",
                    "id": 2,
                    "method": "tools/call",
                    "params": {
                      "name": "compile",
                      "arguments": {
                        "latex": "\\\\documentclass{article}\\\\begin{document}Hello\\\\end{document}"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A JSON-RPC response. Tool failures are reported inside `result` with `isError: true` and the compile error body in `structuredContent`, not as a JSON-RPC error."
          },
          "202": {
            "description": "The request contained only notifications, which carry no id and get no reply."
          }
        }
      },
      "get": {
        "tags": [
          "mcp"
        ],
        "operationId": "mcpStream",
        "summary": "Not supported",
        "description": "This server is stateless and never sends server-initiated messages, so there is nothing to stream. Use POST.",
        "responses": {
          "405": {
            "description": "Method not allowed. The Allow header names POST."
          }
        }
      }
    },
    "/api/v1/public_templates": {
      "get": {
        "tags": [
          "public templates"
        ],
        "operationId": "listPublicTemplates",
        "summary": "List the public template gallery",
        "description": "No authentication. The same templates as https://pressa.dev/templates (invoices, purchase orders, packing slips, delivery notes, receipts, reports, certificates, resumes, CVs, cover letters). Each entry also names its `document` (designs of one document, such as the five resume designs, take the same data), its `design` and the `colors` render accepts. Source is MIT licensed at github.com/pressa-dev/templates.",
        "security": [
          {}
        ],
        "responses": {
          "200": {
            "description": "Every public template with slug, title, category, summary, document, design, colors, page URL, preview image and render URL.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "templates": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/public_templates/{slug}": {
      "get": {
        "tags": [
          "public templates"
        ],
        "operationId": "getPublicTemplate",
        "summary": "Fetch one public template: schema, sample data, instructions and source",
        "description": "No authentication. `schema` is the JSON the render endpoint expects, `sample_data` a complete valid example, `instructions` how to fill it, `latex_with_placeholders` the Liquid-templated source.",
        "security": [
          {}
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "classic-resume"
          }
        ],
        "responses": {
          "200": {
            "description": "The template.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "404": {
            "description": "No public template with that slug.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/public_templates/{slug}/render": {
      "post": {
        "tags": [
          "public templates"
        ],
        "operationId": "renderPublicTemplate",
        "summary": "Fill a public template with JSON data and compile it to a PDF",
        "description": "Fills the template, then runs exactly the compile endpoint: authentication is OPTIONAL with the same anonymous tier and daily limit, the same plan limits, and the same success and failure bodies. The designs of one document take the same data (the five resume designs, as do the packing slip and the delivery note). Data that does not match the schema is a 422 `schema_validation_failed` listing `missing_fields` and `type_errors`; it never reaches the compiler and costs no quota.",
        "security": [
          {},
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "modern-resume"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "data"
                ],
                "properties": {
                  "data": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Values for the template's fields; see GET /api/v1/public_templates/{slug}."
                  },
                  "color": {
                    "type": "string",
                    "description": "Optional: one of the template's `colors` from the list (e.g. navy). Left out, the template's default colour. An unknown colour is a 422 `invalid_color` listing the valid ones."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "PDF produced.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CompileSuccess"
                }
              }
            }
          },
          "404": {
            "description": "No public template with that slug.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "`schema_validation_failed` or `invalid_data` (nothing compiled), or a compile failure with the usual diagnosis.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Anonymous daily limit or plan limit reached, as on the compile endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/QuotaError"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Authorization: Bearer pressa_..."
      },
      "apiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key"
      }
    },
    "schemas": {
      "CompileRequest": {
        "type": "object",
        "required": [
          "latex"
        ],
        "properties": {
          "latex": {
            "type": "string",
            "description": "Complete LaTeX source. Must contain \\documentclass, \\begin{document}, \\input{} or \\include{}. Plain text, markdown or JSON is rejected with `not_latex_source`."
          },
          "compiler": {
            "type": "string",
            "enum": [
              "pdflatex",
              "xelatex",
              "lualatex"
            ],
            "default": "pdflatex",
            "description": "Anonymous callers may use pdflatex only. xelatex requires any account; lualatex requires Pro or Business."
          },
          "assets": {
            "type": "object",
            "description": "Filename to base64-encoded bytes. Requires an API key.",
            "additionalProperties": {
              "type": "string",
              "contentEncoding": "base64"
            }
          },
          "use_stored_assets": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Names of assets from the account's asset library. Requires a paid plan."
          }
        }
      },
      "CompileSuccess": {
        "type": "object",
        "required": [
          "job_id",
          "pdf_url",
          "pages"
        ],
        "properties": {
          "job_id": {
            "type": "string",
            "format": "uuid"
          },
          "pdf_url": {
            "type": "string",
            "format": "uri",
            "description": "Signed URL. Fetchable with no credentials until it expires."
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "pages": {
            "type": "integer",
            "description": "Pages in the PDF at `pdf_url`. For a preview this is what was delivered; `total_pages` is what the document has."
          },
          "compilation_time_ms": {
            "type": "integer"
          },
          "assets_count": {
            "type": "integer"
          },
          "studio_url": {
            "type": "string",
            "format": "uri",
            "description": "Browser editor link. Absent for anonymous compiles."
          },
          "anonymous": {
            "type": "boolean",
            "description": "Present and true when the compile used the keyless tier."
          },
          "remaining_today": {
            "type": "integer",
            "description": "Anonymous tier only."
          },
          "note": {
            "type": "string",
            "description": "Anonymous tier only. Human- and agent-readable upgrade guidance."
          },
          "next_step": {
            "$ref": "#/components/schemas/NextStep"
          },
          "usage": {
            "type": "object",
            "description": "Monthly counters for accounts; daily counters for the anonymous tier."
          },
          "quota_grace": {
            "type": "boolean",
            "description": "Present and true when this compilation exceeded the plan's monthly limit and drew on the paid-plan grace buffer. Grace compilations are not charged."
          },
          "grace_remaining": {
            "type": "integer",
            "description": "Compilations left in the grace buffer before requests start failing."
          },
          "warning": {
            "type": "string",
            "description": "Present with `quota_grace` (capacity is about to run out) and with `preview` (what was delivered and which plan covers the whole document). Human- and agent-readable; relay it."
          },
          "preview": {
            "type": "boolean",
            "description": "Present and true when the document exceeded the plan's page or source-size limit. The PDF at `pdf_url` is what the plan delivers of it. Accounts only; anonymous callers get 413/422 instead."
          },
          "truncated": {
            "type": "boolean",
            "description": "With `preview`: true when the PDF holds only the first `pages` of a `total_pages` document, false when the source was over the plan's size limit but every page fit."
          },
          "total_pages": {
            "type": "integer",
            "description": "With `preview`: how many pages the whole document has. `pages` is how many were delivered."
          },
          "plan_required": {
            "type": "string",
            "nullable": true,
            "enum": [
              "free",
              "starter",
              "pro",
              "business"
            ],
            "description": "With `preview`: the smallest plan whose page and source-size limits cover the whole document. Null when even Business does not."
          },
          "upgrade_url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "Diagnosis": {
        "type": "object",
        "description": "Structured cause of a compilation failure. Absent when the parser could not classify the log with confidence - a confident wrong suggestion costs the caller a wasted retry, so silence is preferred.",
        "properties": {
          "error_class": {
            "type": "string",
            "enum": [
              "missing_package",
              "missing_document_class",
              "missing_asset",
              "undefined_command",
              "undefined_environment",
              "mismatched_environment",
              "missing_math_mode",
              "missing_begin_document",
              "unsupported_unicode",
              "unbalanced_braces",
              "runaway_argument",
              "fatal_error"
            ]
          },
          "summary": {
            "type": "string"
          },
          "suggested_fix": {
            "type": "string"
          },
          "suggested_package": {
            "type": "string",
            "description": "Present when the fix is a specific \\usepackage."
          },
          "symbol": {
            "type": "string",
            "description": "The offending command, environment, filename or codepoint."
          },
          "line": {
            "type": "integer"
          },
          "retryable": {
            "type": "boolean"
          }
        }
      },
      "CompileFailure": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "log": {
            "type": "string",
            "description": "Raw compiler output. Prefer `diagnosis` for programmatic handling."
          },
          "line": {
            "type": "integer"
          },
          "retryable": {
            "type": "boolean"
          },
          "diagnosis": {
            "$ref": "#/components/schemas/Diagnosis"
          },
          "agent_hint": {
            "type": "string",
            "description": "Instruction addressed to an automated caller."
          },
          "plan": {
            "type": "string"
          },
          "usage": {
            "type": "object"
          }
        }
      },
      "NextStep": {
        "type": "object",
        "description": "What the caller should do to get past this response.",
        "properties": {
          "action": {
            "type": "string",
            "enum": [
              "obtain_api_key",
              "retry_without_assets"
            ]
          },
          "signup_url": {
            "type": "string",
            "format": "uri"
          },
          "env_var": {
            "type": "string"
          },
          "header": {
            "type": "string"
          },
          "remove_fields": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "card_required": {
            "type": "boolean"
          }
        }
      },
      "QuotaError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "rate_limit",
              "anonymous_quota_exhausted",
              "anonymous_capacity_exhausted"
            ]
          },
          "message": {
            "type": "string"
          },
          "limit": {
            "type": "integer"
          },
          "used": {
            "type": "integer"
          },
          "resets_at": {
            "type": "string",
            "format": "date-time"
          },
          "plan": {
            "type": "string"
          },
          "retryable": {
            "type": "boolean"
          },
          "upgrade_url": {
            "type": "string",
            "format": "uri"
          },
          "next_step": {
            "$ref": "#/components/schemas/NextStep"
          },
          "retry_after_seconds": {
            "type": "integer"
          },
          "grace_allowance": {
            "type": "integer",
            "description": "Size of the plan's grace buffer, which was also exhausted."
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "plan": {
            "type": "string"
          },
          "upgrade_url": {
            "type": "string",
            "format": "uri"
          },
          "limit": {
            "type": "integer",
            "description": "The limit that was applied, in the unit the error names (bytes, pages)."
          },
          "plan_limit": {
            "type": "integer",
            "description": "With `pdf_too_large`: the plan's own PDF cap, when a larger compile ceiling was applied to the whole document before a preview slice."
          },
          "retryable": {
            "type": "boolean"
          },
          "retry_after_seconds": {
            "type": "integer"
          },
          "preview_attempts_per_day": {
            "type": "integer",
            "description": "With `latex_too_large` after the day's size previews are used: how many an account gets per rolling day."
          },
          "preview_attempts_remaining_today": {
            "type": "integer",
            "description": "With `latex_too_large`: 0 when the day's size previews are used; retry tomorrow rather than shrinking the document."
          }
        }
      }
    }
  }
}
