Overview
hotdoc is a document-processing API. You send files and text instructions (prompts), and you get back the recognized text and the model’s answers for each file.
Under the hood, hotdoc does three things: it unpacks archives and normalizes your files, runs them through OCR, then sends the recognized text to a language model along with your prompts. You don’t stand up any infrastructure, you don’t configure an OCR pipeline, and you don’t pay a markup on tokens — the model runs on your provider key (BYOK).
Processing is asynchronous. You create a job, get its ID, and poll its status until the job finishes. A single job can hold several files, including files nested inside archives.
hotdoc is a good fit when:
- you need to turn a mix of documents (PDFs, scans, Office files, archives) into text and extract data from them via API;
- you don’t want to run your own recognition infrastructure;
- you don’t want to overpay for tokens through a middleman — you pay the model provider directly.
What hotdoc doesn’t do: it doesn’t convert one file format to another as an end in itself (it’s not a converter), and it doesn’t guarantee schema-valid output (see “Prompts and data extraction”).
How the API works in 1 minute
One base URL (https://api.hotdoc.io), one auth header, asynchronous processing: you create a job, get its id, and poll the status until the job finishes.
The methods fall into three groups (full list with paths in “API reference”):
| Group | Methods | What for |
|---|---|---|
| Processing (Jobs) | 5 | create a job, status, result, list jobs, upload a file |
| Account and keys | 6 | profile, rename account, members, create/list/revoke API key |
| Billing | 4 | quota status, plans, checkout and subscription portal |
The core flow is three steps:
POST /v1/jobs → create a job (status JOB_STATUS_NEW)
GET /v1/jobs/{id} → poll the status (NEW → FILE_PROCESSING → OCR → LLM → terminal)
GET /v1/jobs/{id}/result → fetch the result: recognized text (ocr[]) and model answers (llm[])
There’s no separate logs endpoint: per-stage data and errors (OCR/LLM) live in /result; the job list is GET /v1/jobs; remaining quota is GET /v1/billing.
Core concepts
Job. The unit of processing. It holds one or more source files, moves through the processing stages, and has a status. Limits apply to a job’s total size (see “Limits”). On the free plan, usage is counted in jobs.
Source. A file to be processed. You specify it either as a public URL in sourceUrls or by uploading it first (see “Uploading files”). If a source is an archive, it’s unpacked recursively (default depth ≤ 3), and each nested file is processed separately.
Processing stages. Each file goes through: unpacking/conversion → OCR (text recognition) → LLM (sending the text to the model with your prompts).
Prompt. A text instruction for the model. You pass a prompts array; the model applies it to each file’s recognized text and returns a text answer.
BYOK / neural. The model configuration whose key the LLM stage runs on. It’s passed in the neural field when you create a job. The provider key is never stored in plaintext — only encrypted — and it’s deleted along with the job (see “Security and data”).
BYOK / ocr. The configuration for the OCR provider that the recognition (OCR) stage runs on. It’s passed in the ocr field when you create a job and is always required, alongside neural. You choose the provider via ocr.provider (e.g. NEURAL_CLIENT_TYPE_MISTRAL), the model via ocr.model (e.g. mistral-ocr-latest), and supply your key in ocr.providerKey. This is a separate key from neural.apiKey: neural.apiKey is the language-model provider key (LLM stage), while ocr.providerKey is the OCR-provider key for text recognition (OCR stage). The converter decides on its own whether a given file needs OCR; for plain-text files the key may go unused, but you must send it either way. The key is stored only encrypted, is never returned in responses, and is deleted along with the job.