Your First OCR Call in Python
On this page
Updated November 2, 2026 — OCR. OCR 4.0 (
mistral-ocr-4-0) was retired on September 30, 2026. Use OCR 4.1 (mistral-ocr-4-1), which has been generally available since August 31 at the same price, or the aliasmistral-ocr-latest, which has pointed to it since July 16. OCR 4.1 adds confidence scores at the block level. Addendum November 3, 2026: A local PDF is transmitted in base64 as{"type": "document_url", "document_url": "data:application/pdf;base64,…"}; thedocument_base64type from older examples exists neither in the documentation nor in themistralaiSDK, and those examples would have failed. Sources: OCR Processor · Document QnA.
From Theory to Code
You now know the endpoint and its parameters. It’s time to write a complete script that extracts text from a PDF, from the first import line to the Markdown file saved on your disk. We’ll start with the simplest case—a public document—then move to what you’ll encounter in practice: local files, isolated images, and illustrated documents that need reconstruction.
Two prerequisites, already covered but worth verifying before you waste ten minutes on an authentication error:
pip install mistralai
export MISTRAL_API_KEY="your-api-key"
The Minimal Script
Here is the full extraction of a PDF accessible online. Three useful lines: instantiate the client, call process, and iterate through the pages.
import os
from mistralai.client import Mistral
# Initialize the client
client = Mistral(api_key=os.environ["MISTRAL_API_KEY"])
# OCR call on a public PDF
ocr_response = client.ocr.process(
model="mistral-ocr-latest",
document={
"type": "document_url",
"document_url": "https://arxiv.org/pdf/2201.04234"
}
)
# Display the number of pages
print(f"Number of pages: {len(ocr_response.pages)}")
# Display the content of each page
for page in ocr_response.pages:
print(f"\n--- Page {page.index + 1} ---")
print(page.markdown)
Note the page.index + 1: the index returned by the API starts at zero, while your users count from one. It’s a minor offset as long as you keep it in mind, but a source of lasting confusion when you discover it in a report delivered to a client.
Processing a File from Your Machine
In real life, documents rarely arrive via a public URL. You have them on disk, they are confidential, so you encode them in base64. The following script processes a supplier invoice and concatenates all pages into a single block of text:
import os
import base64
from mistralai.client import Mistral
client = Mistral(api_key=os.environ["MISTRAL_API_KEY"])
# Read and encode the local file
file_path = "supplier_invoice.pdf"
with open(file_path, "rb") as f:
document_base64 = base64.b64encode(f.read()).decode("utf-8")
# OCR call
ocr_response = client.ocr.process(
model="mistral-ocr-latest",
document={
"type": "document_url",
"document_url": f"data:application/pdf;base64,{document_base64}"
}
)
# Concatenate all text
full_text = "\n\n".join(
page.markdown for page in ocr_response.pages
)
print(full_text)
Concatenation via "\n\n".join(...) works fine for display or full-text search. However, it erases the boundary between pages: if you later need to cite a source or direct a user to the correct page of a contract, preserve the page-by-page structure rather than this single block.
Saving the Extraction
Every OCR call costs money and takes time. Re-running the extraction of the same two-hundred-page report because you forgot to save the result is a mistake you only make once. The function below writes the Markdown to a file, marking page breaks with a comment and a separator:
import os
import base64
from mistralai.client import Mistral
client = Mistral(api_key=os.environ["MISTRAL_API_KEY"])
def extract_pdf(pdf_path: str, output_path: str) -> None:
"""Extracts text from a PDF and saves it as Markdown."""
with open(pdf_path, "rb") as f:
document_base64 = base64.b64encode(f.read()).decode("utf-8")
ocr_response = client.ocr.process(
model="mistral-ocr-latest",
document={
"type": "document_url",
"document_url": f"data:application/pdf;base64,{document_base64}"
}
)
with open(output_path, "w", encoding="utf-8") as f:
for page in ocr_response.pages:
f.write(f"<!-- Page {page.index + 1} -->\n\n")
f.write(page.markdown)
f.write("\n\n---\n\n")
print(f"Extraction complete: {len(ocr_response.pages)} pages")
print(f"Result saved to: {output_path}")
# Usage
extract_pdf("annual_report.pdf", "annual_report.md")
The HTML comment <!-- Page N --> is invisible in the rendered view but remains present in the file: you can find it via regular expression to reconstruct pagination during indexing.
The Case of an Isolated Image
OCR 4 isn’t limited to PDFs. A photo of an invoice taken on a phone, a scanned receipt, a screenshot—all are sent the same way, using the image_base64 type:
import os
import base64
from mistralai.client import Mistral
client = Mistral(api_key=os.environ["MISTRAL_API_KEY"])
# Encode an image
with open("scanned_invoice.png", "rb") as f:
image_base64 = base64.b64encode(f.read()).decode("utf-8")
# OCR call on an image
ocr_response = client.ocr.process(
model="mistral-ocr-latest",
document={
"type": "image_base64",
"image_base64": image_base64
}
)
# A single "page" for an image
print(ocr_response.pages[0].markdown)
The response keeps the same form: a list of pages, which here contains only one. Your processing code therefore remains identical regardless of the input file type, which greatly simplifies mixed pipelines.
Reconstructing an Illustrated Document
The returned Markdown references images as , without embedding them. If you want a standalone file—an offline-readable report, a preview to send by email—request the images in base64 and replace each reference with a data URI:
import os
import base64
import re
from mistralai.client import Mistral
client = Mistral(api_key=os.environ["MISTRAL_API_KEY"])
ocr_response = client.ocr.process(
model="mistral-ocr-latest",
document={
"type": "document_url",
"document_url": "https://arxiv.org/pdf/2201.04234"
},
include_image_base64=True
)
for page in ocr_response.pages:
markdown = page.markdown
# Replace image references with base64
for image in page.images:
placeholder = f""
data_uri = f""
markdown = markdown.replace(placeholder, data_uri)
print(markdown[:500])
The resulting file grows quickly, since base64 weighs about a third more than the original binary. Reserve this reconstruction for documents you need to make standalone, and keep images as separate files in your indexing pipelines.
Before moving on, take a document you own—an invoice, a contract, a scanned page—and run it through the local extraction script. Compare the resulting Markdown to the original line by line: the discrepancies you find, a poorly reconstructed table or a header absorbed into the body text, are exactly the problems the next three lessons will teach you to handle.
Key Takeaways
- The OCR call takes 3 lines: client, process, read pages
- Use base64 for local files, URL for public documents
- The result is structured Markdown, ready to use
- Extracted images can be recombined with text
- Always save your extractions to avoid repeated API calls