Skip to main content
This guide walks through running a document (PDF) through the VLM Run Gateway, from picking a model to handling the response. Document requests fan out one page at a time and default to ocr for pp-ocrv6. For a quick copy-paste example, see Quickstart. For the cost/latency/accuracy rationale behind picking a model, method, and DPI, see Why the Gateway.

1. Pick a model

Start with paddleocr/pp-ocrv6 for general-purpose PDF OCR. See Models for the full catalog and Methods if you need a specific operation like text detection (detect) instead of full OCR. zai-org/glm-ocr and rednote-hilab/dots.mocr are also available for reading-order markdown output via method: "markdown".

2. Send the document

Pass the PDF as a document_url content part. Prefer a hosted URL over a base64 data URI. See Multimodal Inputs for size limits and Method Parameters for document_dpi and other knobs.
from openai import OpenAI

client = OpenAI(
    base_url="https://gateway.vlm.run/v1/openai",
    api_key="<VLMRUN_API_KEY>",
)

response = client.chat.completions.create(
    model="paddleocr/pp-ocrv6",
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "type": "document_url",
                    "document_url": {"url": "https://example.com/invoice.pdf"},
                }
            ],
        }
    ],
    extra_body={"method": "ocr", "document_dpi": 150},
)

Request knobs

FieldDefaultNotes
document_dpi150Rasterization DPI per page. See Method Parameters.
document_max_pages500Hard page cap. See Multimodal Inputs.
methodocrPer-page backend operation. See Methods.
streamfalseWhen true, emits ordered SSE page blocks. See Streaming vs non-streaming.

Document rasterization

When the request includes a document_url part, document_dpi controls the rasterization DPI for each page before OCR. Set it at the top level or inside method_params (either location works; if both are present, method_params wins).
DPITradeoff
150 (default)Good balance of legibility and per-page inference cost.
300+Preserves fine print and small text, at higher inference cost and latency per page.
Under 150Faster and cheaper, but risks losing small text or fine table borders.
Start at the default and only raise document_dpi if you see missed text on dense or small-font pages. Page count and file-size limits are documented under Multimodal Inputs.

3. Streaming vs non-streaming

Streaming is scoped to document requests today. Image-only and text-only requests do not support streaming on the VLM Run Gateway.
ModeWhen to use
Non-streaming (default)Short documents, batch pipelines, or when you need the full result before continuing.
Streaming (stream: true)Long documents where you want to render or process pages as they finish.
Set stream: true to receive each page over SSE as it completes, in ascending page order, instead of waiting for the entire document:
stream = client.chat.completions.create(
    model="paddleocr/pp-ocrv6",
    stream=True,
    messages=[
        {
            "role": "user",
            "content": [
                {"type": "document_url", "document_url": {"url": "https://example.com/report.pdf"}}
            ],
        }
    ],
    extra_body={"method": "ocr", "document_dpi": 150},
)

for chunk in stream:
    delta = chunk.choices[0].delta.content
    if delta:
        print(delta, end="")
Each delta.content value is one full page block (same shape as in the next section).

4. Parse the response

Every page arrives as a self-contained block:
<page index="0" dpi="150" width="1275" height="1650">
… page 0 markdown …
</page>
<page index="1" dpi="150" width="1275" height="1650">
… page 1 markdown …
</page>
Non-streaming: blocks are concatenated in order inside choices[0].message.content. Streaming: the VLM Run Gateway emits one OpenAI-style SSE chunk per page block. Each chunk’s delta.content holds a single block from the example above. Split on page open and close tags to recover per-page markdown, or treat the whole string as one document if you do not need page boundaries.

5. Track cost and handle errors

Non-streaming: read response.usage.cost for per-request metering during the alpha. Streaming: the last SSE event carries usage (including usage.cost) on the chunk instead of delta.content. Check for it before treating every chunk as page content:
usage = None
pages: list[str] = []

for chunk in stream:
    choice = chunk.choices[0]
    if choice.delta.content:
        pages.append(choice.delta.content)
    if chunk.usage:
        usage = chunk.usage

print(f"Cost: {usage.cost if usage else 'n/a'}")
For both modes:
  • Retry 429/500 with backoff; do not retry 400 capability violations. See Error Codes.
  • Keep the x-request-id response header if you need to contact support.

Multimodal Inputs

Content part types, document limits, and URL vs base64 tradeoffs.

Methods

Per-model method and method_params reference.

Error Codes

Status codes, error bodies, and retry guidance.

Models

Document and image OCR model catalog.