> ## Documentation Index
> Fetch the complete documentation index at: https://hub.hcompany.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Document OCR

> Turn a document page image into clean Markdown with one chat completion.

export const Notice = ({kind = "note", title, children}) => {
  const kinds = {
    warning: {
      label: "User notice",
      icon: <>
          <path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" />
          <path d="M12 9v4" />
          <path d="M12 17h.01" />
        </>
    },
    gotcha: {
      label: "Gotcha",
      icon: <>
          <circle cx="12" cy="12" r="10" />
          <path d="M12 16v-4" />
          <path d="M12 8h.01" />
        </>
    },
    note: {
      label: "Note",
      icon: <>
          <circle cx="12" cy="12" r="10" />
          <path d="M12 16v-4" />
          <path d="M12 8h.01" />
        </>
    }
  };
  const k = kinds[kind];
  return <div className="notice my-6 rounded-xl border border-zinc-200 bg-white p-5 dark:border-zinc-800 dark:bg-zinc-950">
      <div className={`${kind === "warning" ? "not-prose flex items-center gap-1.5 text-xs font-semibold uppercase tracking-wide text-red-400/80 dark:text-red-400/70" : kind === "gotcha" ? "not-prose flex items-center gap-1.5 text-xs font-semibold uppercase tracking-wide text-amber-500/80 dark:text-amber-400/70" : "not-prose flex items-center gap-1.5 text-xs font-semibold uppercase tracking-wide text-zinc-400 dark:text-zinc-500"}`}>
        <svg className="h-3.5 w-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
          {k.icon}
        </svg>
        {k.label}
      </div>
      {title && <div className="not-prose mt-2 text-base font-semibold text-zinc-900 dark:text-zinc-100">{title}</div>}
      <div className="notice-body mt-3 text-sm leading-6 text-zinc-700 dark:text-zinc-300">{children}</div>
    </div>;
};

Pass Holo a document page as an image and get clean Markdown back: headings, lists, tables, and equations, in reading order. There is no dedicated OCR endpoint: it is the same OpenAI-compatible `chat/completions` call with an image plus a transcription prompt, sent with `temperature=0.0` and `enable_thinking=False` so the model transcribes in one shot.

| Input                                                                   | Quality                                                         |
| ----------------------------------------------------------------------- | --------------------------------------------------------------- |
| English, digitally generated: exported PDFs, slides, web pages, reports | Strongest                                                       |
| Scanned pages and photos                                                | Best effort                                                     |
| Handwriting, non-Latin scripts                                          | Not a good fit; use a dedicated OCR system when stakes are high |

Set up the OpenAI client first by following the [Quickstart](/models-api/quickstart).

## Transcribe a page

Send one page image and read the Markdown from `message.content`.

<CodeGroup>
  ```python Python theme={"system"}
  IMAGE_URL = "https://your-host/page.png"  # or "data:image/png;base64,..."

  OCR_PROMPT = (
      "Transcribe this document page to Markdown, preserving the reading order, "
      "headings, lists, and tables. Render tables as Markdown tables and equations "
      "as LaTeX. Return only the transcription, with no commentary and no surrounding "
      "code fence. If the page has no readable text, return an empty string."
  )

  response = client.chat.completions.create(
      model="holo3-1-35b-a3b",
      messages=[{
          "role": "user",
          "content": [
              {"type": "image_url", "image_url": {"url": IMAGE_URL}},
              {"type": "text", "text": OCR_PROMPT},
          ],
      }],
      temperature=0.0,
      extra_body={"chat_template_kwargs": {"enable_thinking": False}},
  )

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

  ```typescript TypeScript theme={"system"}
  const IMAGE_URL = "https://your-host/page.png"; // or "data:image/png;base64,..."

  const OCR_PROMPT =
    "Transcribe this document page to Markdown, preserving the reading order, " +
    "headings, lists, and tables. Render tables as Markdown tables and equations " +
    "as LaTeX. Return only the transcription, with no commentary and no surrounding " +
    "code fence. If the page has no readable text, return an empty string.";

  const response = await client.chat.completions.create({
    model: "holo3-1-35b-a3b",
    messages: [
      {
        role: "user",
        content: [
          { type: "image_url", image_url: { url: IMAGE_URL } },
          { type: "text", text: OCR_PROMPT },
        ],
      },
    ],
    temperature: 0.0,
    // chat_template_kwargs is H-specific, passed through in the request body
    ...({ chat_template_kwargs: { enable_thinking: false } } as any),
  });

  console.log(response.choices[0].message.content);
  ```
</CodeGroup>

## Multi-page PDFs

Holo reads images, not PDFs, so rasterize each page to an image and transcribe them one per request, then stitch the results. One page per request keeps each image at full resolution and is the most reliable pattern.

<CodeGroup>
  ```python Python theme={"system"}
  import base64
  import pymupdf  # pip install pymupdf

  def ocr_page(png_bytes: bytes) -> str:
      data_uri = "data:image/png;base64," + base64.b64encode(png_bytes).decode()
      response = client.chat.completions.create(
          model="holo3-1-35b-a3b",
          messages=[{
              "role": "user",
              "content": [
                  {"type": "image_url", "image_url": {"url": data_uri}},
                  {"type": "text", "text": OCR_PROMPT},
              ],
          }],
          temperature=0.0,
          extra_body={"chat_template_kwargs": {"enable_thinking": False}},
      )
      return response.choices[0].message.content or ""

  with pymupdf.open("document.pdf") as doc:
      pages = [ocr_page(page.get_pixmap(dpi=200).tobytes("png")) for page in doc]

  markdown = "\n\n".join(pages)
  print(markdown)
  ```

  ```typescript TypeScript theme={"system"}
  import { pdf } from "pdf-to-img"; // npm install pdf-to-img

  async function ocrPage(png: Buffer): Promise<string> {
    const dataUri = "data:image/png;base64," + png.toString("base64");
    const response = await client.chat.completions.create({
      model: "holo3-1-35b-a3b",
      messages: [
        {
          role: "user",
          content: [
            { type: "image_url", image_url: { url: dataUri } },
            { type: "text", text: OCR_PROMPT },
          ],
        },
      ],
      temperature: 0.0,
      ...({ chat_template_kwargs: { enable_thinking: false } } as any),
    });
    return response.choices[0].message.content ?? "";
  }

  const pages: string[] = [];
  for await (const page of await pdf("document.pdf", { scale: 2 })) {
    pages.push(await ocrPage(page));
  }

  const markdown = pages.join("\n\n");
  console.log(markdown);
  ```
</CodeGroup>

<Notice kind="note" title="Resolution and throughput">
  Rasterize at roughly 150 to 200 DPI (or `scale: 2`). Lower resolution loses small text. Higher resolution wastes tokens without improving accuracy. Run pages concurrently to speed up long documents, within your [rate limit](/models-api/introduction#faq).
</Notice>

<Notice kind="gotcha" title="Dense pages can truncate">
  Output is capped at 8,192 tokens per request, and a dense page (large tables, small print) can exceed that. Check `finish_reason` and treat `length` as a truncated transcription. Split dense pages into two images.
</Notice>

## Next steps

<CardGroup cols={3}>
  <Card title="Element localization" icon="crosshairs" href="/models-api/element-localization">
    Get click coordinates from a screenshot.
  </Card>

  <Card title="Agent loop" icon="arrows-rotate" href="/models-api/agent-loop">
    How to use Holo in your computer-use harness.
  </Card>

  <Card title="API reference" icon="code" href="/models-api/api-reference">
    Endpoint, models, parameters, and limits.
  </Card>
</CardGroup>
