Skip to main content
Lesson 13 of 16 · Document AI: OCR and Document Intelligence

The Document QnA API

Lesson 13 of 16 Updated November 3, 2026 Developer
On this page
  1. Query your documents in natural language
  2. A different endpoint than OCR
  3. Three ways to send the document
  4. Which chat model for which question
  5. Chaining questions
  6. What QnA won’t do for you
  7. Key takeaways

Lesson updated November 3, 2026. Addendum November 3, 2026: To send a local PDF, transmit it as base64 in the format {"type": "document_url", "document_url": "data:application/pdf;base64,…"}. The document_base64 type found in older examples does not exist in the documentation or the mistralai SDK, and those examples would have failed. Sources: OCR Processor · Document QnA.

Query your documents in natural language

Document QnA combines OCR with a Mistral LLM, letting you ask questions directly about your document content. Instead of scanning fifty pages to find a specific clause or figure, you simply ask the question and read the answer. Internally, the service runs two steps: OCR extracts the text, structure, and formatting, then a Mistral chat model analyzes the extracted content to generate a response. This combination is transparent to you—a single API call handles everything.

A different endpoint than OCR

This is the part that often trips people up on the first try. Pure OCR uses client.ocr.process, whereas QnA uses the standard Chat endpoint client.chat.complete, with a message containing both a text block and a document_url block. In other words, the document becomes an attachment to your question, just like an image in a multimodal exchange.

import os
from mistralai.client import Mistral

client = Mistral(api_key=os.environ["MISTRAL_API_KEY"])

# Question about a public document
response = client.chat.complete(
    model="mistral-small-latest",
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "type": "text",
                    "text": "What is the total revenue mentioned?"
                },
                {
                    "type": "document_url",
                    "document_url": "https://example.com/annual-report.pdf"
                }
            ]
        }
    ]
)

print(response.choices[0].message.content)

Three ways to send the document

A public URL is the most direct route, ideal for documents already accessible online—such as a scientific publication, an institutional report, or a PDF hosted on your website.

messages = [
    {
        "role": "user",
        "content": [
            {"type": "text", "text": "Summarize the main points."},
            {
                "type": "document_url",
                "document_url": "https://arxiv.org/pdf/1805.04770"
            }
        ]
    }
]

response = client.chat.complete(
    model="mistral-small-latest",
    messages=messages
)

Local files, on the other hand, are sent as base64. This is the default mode whenever a document is confidential or stored behind your firewall; nothing needs to be published on the web to be queried.

import base64

with open("contract.pdf", "rb") as f:
    doc_base64 = base64.b64encode(f.read()).decode("utf-8")

messages = [
    {
        "role": "user",
        "content": [
            {
                "type": "text",
                "text": "What are the termination clauses?"
            },
            {
                "type": "document_url",
                "document_url": f"data:application/pdf;base64,{doc_base64}"
            }
        ]
    }
]

response = client.chat.complete(
    model="mistral-small-latest",
    messages=messages
)

Finally, there is the third option for files already uploaded via the Files API: you upload once with purpose="ocr", then reference the resulting ID as many times as needed. For a document you plan to query multiple times, this avoids re-uploading several megabytes with every question.

uploaded = client.files.upload(
    file=open("report.pdf", "rb"),
    purpose="ocr"
)

messages = [
    {
        "role": "user",
        "content": [
            {"type": "text", "text": "What is the planned budget?"},
            {"type": "file_id", "file_id": uploaded.id}
        ]
    }
]

response = client.chat.complete(
    model="mistral-small-latest",
    messages=messages
)

Which chat model for which question

The model parameter is up to you, and it impacts both answer quality and cost.

ModelProfileWhen to choose it
mistral-small-latestFast and economicalSufficient for most questions
mistral-medium-latestGood compromiseBalanced speed and accuracy
mistral-large-latestMore preciseComplex analysis and reasoning

Your choice depends on the complexity of your questions and your budget: extracting an amount from an invoice doesn’t require the same model as comparing liability regimes between two contracts.

Chaining questions

You don’t have to ask everything at once. You send the document in the first message, add the model’s response to the history, then ask your follow-up question as text only: the document context remains in the conversation. This is the natural way to explore a report—start with a summary, then dive deeper into the point that interests you.

history = [
    {
        "role": "user",
        "content": [
            {"type": "text", "text": "Summarize this document in 3 points."},
            {
                "type": "document_url",
                "document_url": "https://example.com/report.pdf"
            }
        ]
    }
]

# First call
response = client.chat.complete(
    model="mistral-small-latest",
    messages=history
)

# Add response to history
history.append({
    "role": "assistant",
    "content": response.choices[0].message.content
})

# Follow-up question
history.append({
    "role": "user",
    "content": "Detail the second point."
})

response = client.chat.complete(
    model="mistral-small-latest",
    messages=history
)

print(response.choices[0].message.content)

What QnA won’t do for you

Four limitations are worth knowing before building a service on top of this. First, size: 50 MB per file and a maximum of 1,000 pages, which rules out large archives without prior splitting. Second, cost, since long documents consume many input tokens with every question asked. Third, the lack of persistent memory means you must reconstruct the history yourself; each call is independent outside of a multi-turn conversation. The trickiest point is the last one: the LLM interprets as much as it extracts, which can introduce errors in precise figures. For an amount going into accounting, prefer structured annotation; reserve QnA for understanding and synthesis.

Key takeaways

  • Document QnA uses the Chat endpoint with document_url content: either a public URL or a base64-encoded PDF as a data URL
  • The internal workflow is OCR then LLM, but only one call is required
  • Three sending methods: URL, base64, uploaded file
  • Multi-turn conversations enable in-depth analysis
  • Choose your chat model based on the complexity of your questions