Convert a printed, scanned or handwritten PDF or image to Markdown with the Munito AI OCR API, in the EU or the US, by URL or as base64.
The Munito AI OCR API converts a PDF or an image to Markdown, with the processing in the EU or the US. Send the document to POST /ocr by URL or as base64. The answer holds the Markdown of each page.
A short document answers at once. A long one answers 202 with a location to poll.
The base URL is https://api.munito.ai/inference/v1, and the key is in MUNITO_API_KEY:
export MUNITO_API_KEY="munitoai_..."Send a PDF by URL
curl https://api.munito.ai/inference/v1/ocr \
-H "Authorization: Bearer $MUNITO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "lightonai/lightonocr-2-1b",
"document": {"type": "url", "url": "https://arxiv.org/pdf/1706.03762"},
"pages": [0, 1]
}'Use a public https URL on port 443. Munito AI follows up to 3 redirects. A URL that Munito AI cannot fetch gets 422 with Could not fetch the document from the provided URL.
The answer has one entry in pages for each page, and the totals in usage:
{
"request_id": "01a165c4-00db-7c89-8558-034e9b948463",
"model": "lightonai/lightonocr-2-1b",
"service_tier": "standard",
"pages": [
{
"index": 0,
"markdown": "# Attention Is All You Need\n\n...",
"header": null,
"footer": null,
"page_number": null,
"aside": null,
"images": [],
"tables": [],
"dimensions": {
"width": 1190,
"height": 1540,
"dpi": 140
},
"confidence_scores": null,
"error": null,
"error_code": null,
"price_usd": 0.004
}
],
"usage": {
"pages_processed": 2,
"pages_failed": 0,
"doc_size_bytes": 0,
"cost_usd": 0.008,
"price_per_page_usd": 0.004,
"inference_region": "eu"
}
}A page that fails has error and error_code set, and it is not billed. usage.inference_region names the region that read the pages.
Send a local file as base64
Use "type": "base64" with the bytes of the file in data. Add filename to help Munito AI detect the format.
This script sends a local PDF, polls until the request ends, and writes the Markdown to a .md file beside the PDF:
# pip install requests
import base64
import os
import sys
import time
import requests
HOST = "https://api.munito.ai"
HEADERS = {"Authorization": f"Bearer {os.environ['MUNITO_API_KEY']}"}
def submit(path: str) -> str:
"""Send the PDF as base64 and return the location to poll."""
with open(path, "rb") as f:
data = base64.b64encode(f.read()).decode()
response = requests.post(
f"{HOST}/inference/v1/ocr",
headers={**HEADERS, "Prefer": "respond-async"},
json={
"model": "munito/ocr-auto",
"document": {"type": "base64", "data": data, "filename": os.path.basename(path)},
},
timeout=120,
)
response.raise_for_status()
return response.json()["location"]
def wait(location: str) -> dict:
"""Poll the request until its status is final."""
while True:
response = requests.get(HOST + location, headers=HEADERS, timeout=60)
if response.status_code == 503:
time.sleep(int(response.headers.get("Retry-After", "2")))
continue
response.raise_for_status()
body = response.json()
if body["status"] != "running":
return body
print(f"{body.get('completed', 0)} of {body.get('total', '?')} pages done", file=sys.stderr)
time.sleep(2)
if __name__ == "__main__":
pdf = sys.argv[1]
result = wait(submit(pdf))
if result["status"] != "completed":
sys.exit(f"OCR ended with status {result['status']}")
markdown = "\n\n".join(page["markdown"] for page in result["pages"])
out = os.path.splitext(pdf)[0] + ".md"
with open(out, "w") as f:
f.write(markdown)
usage = result["usage"]
print(f"Wrote {out}: {usage['pages_processed']} pages, {usage['pages_failed']} failed")python pdf_to_markdown.py paper.pdfChoose the pages
Send pages with zero-based page numbers to read only those pages. Each page counts one time.
| Request | Pages read |
|---|---|
No pages field | Every page |
"pages": [0] | The first page |
"pages": [0, 1, 2] | The first three pages |
At most 1000 pages go in one request. A negative or repeated number gets 422. A number past the last page gets 422 with a text such as pages [20] are past the last page: the document has 15 pages, numbered from 0.
Long documents: 202 and polling
By default, POST /ocr waits up to 120 seconds for the result. When the work takes longer, the answer is 202 with a Location header and a body:
{
"request_id": "01a165c4-d049-78e1-b194-ddd4016c515c",
"status": "accepted",
"location": "/inference/v1/ocr/01a165c4-d049-78e1-b194-ddd4016c515c"
}Send Prefer: respond-async to get the 202 at once, as the script above does. Then poll GET /ocr/{request_id} at https://api.munito.ai plus the location:
status | Meaning |
|---|---|
running | The work continues. total and completed count the pages, and pages holds the pages that are done. |
completed | The full result, in the same shape as a 200 answer. |
failed, cancelled, timed_out | The request ended with no result. |
Munito AI deletes the result when it delivers it. After the first answer with the full result, and the next poll gets
410 with The result expired or was already retrieved. Save the result when you get it.
To stop a request that runs, send DELETE /ocr/{request_id}. A poll that gets 503 can try again after the Retry-After time.
Choose a model
| Model | What it does | Input |
|---|---|---|
munito/ocr-auto | The default when you omit model. Reads the text layer of a page that is mostly text, and sends other pages, such as scans, to an OCR model. | PDF, image |
munito/ocr-fast | Reads only the text layer of a PDF, with no OCR model. A page with no text layer, such as a scan, gives empty Markdown. Models and pricing lists its page price. | PDF only |
lightonai/lightonocr-2-1b | Reads each page with the LightOnOCR-2-1B model. | PDF, image |
Use munito/ocr-fast for PDFs that software made, such as exports and reports. Use munito/ocr-auto or lightonai/lightonocr-2-1b for scans and photos.
An image sent to munito/ocr-fast gets 422 with munito/ocr-fast accepts only PDF documents. Use munito/ocr-auto for this document.
See Models and pricing for the price of each page.
Other options
| Field | Effect |
|---|---|
tables | {"mode": "extracted"} moves each table from the page text to tables. format is markdown (the default) or html. |
images | annotate reads the text of each image into alt_text. include_base64 adds each image as base64. |
confidence_scores | true returns a confidence for each page. |
preferred_region | global, eu or us: where the request can run. |
See the OCR reference for each field.
Formats and limits
- A PDF, or an image such as JPEG, PNG, WebP, TIFF or HEIC. Another type gets
422withThis document type is not supported. Send a PDF or an image. - At most 50 MiB. A larger file gets
413withDocument exceeds the configured size limit. - An OCR request keeps data while it runs, so send it to the global host or to the host of your data region. The other regional host answers
421. See Regions.
See Errors for each status and its body.