PDF to Markdown

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

Choose the pages

Send pages with zero-based page numbers to read only those pages. Each page counts one time.

RequestPages read
No pages fieldEvery 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:

statusMeaning
runningThe work continues. total and completed count the pages, and pages holds the pages that are done.
completedThe full result, in the same shape as a 200 answer.
failed, cancelled, timed_outThe 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

ModelWhat it doesInput
munito/ocr-autoThe 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-fastReads 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-1bReads 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

FieldEffect
tables{"mode": "extracted"} moves each table from the page text to tables. format is markdown (the default) or html.
imagesannotate reads the text of each image into alt_text. include_base64 adds each image as base64.
confidence_scorestrue returns a confidence for each page.
preferred_regionglobal, 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 422 with This document type is not supported. Send a PDF or an image.
  • At most 50 MiB. A larger file gets 413 with Document 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.