Gemini OCR

Gemini OCR API guide

The Gemini API calls behind this app, with working JavaScript for images and PDFs and what we learned running them.

There is no separate OCR endpoint: you send the document to a Gemini model and ask for the text. This page shows the requests Gemini OCR makes, taken from the app's own code, using the official @google/genai JavaScript SDK. To do the same without code, use Gemini OCR in your browser.

Extract text from an image

Images go inline as base64. The prompt below is the one the app uses; gemini-3.8-flash is its default model. Install the SDK with npm install @google/genai and set GEMINI_API_KEY.

import { GoogleGenAI } from '@google/genai';
import { readFile } from 'node:fs/promises';

const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
const data = (await readFile('invoice.png')).toString('base64');

const response = await ai.models.generateContent({
  model: 'gemini-3.8-flash',
  contents: [{
    role: 'user',
    parts: [
      // The document first, the instruction after it.
      { inlineData: { mimeType: 'image/png', data } },
      { text: 'Based on the preceding document, extract all text content from this document. ' +
        'Format the output as clean markdown with proper headings and structure. ' +
        'Write any mathematical formula in LaTeX wrapped in $…$, including inline formulas; never use a single $ for math.' },
    ],
  }],
  config: {
    mediaResolution: 'MEDIA_RESOLUTION_HIGH',
    thinkingConfig: { thinkingLevel: 'medium' },
  },
});

const finish = response.candidates?.[0]?.finishReason;
if (finish && finish !== 'STOP') throw new Error(`Extraction stopped early: ${finish}`);
console.log(response.text);

Extract text from a PDF

For PDFs the app uses Google's Files API: upload, wait until the file is ready, reference it by URI, then delete it. Google deletes uploaded files after 48 hours anyway. Long PDFs are split into 10-page chunks, one request each.

let file = await ai.files.upload({
  file: 'report.pdf',
  config: { mimeType: 'application/pdf' },
});
while (file.state === 'PROCESSING') {
  await new Promise(resolve => setTimeout(resolve, 1000));
  file = await ai.files.get({ name: file.name });
}

try {
  const response = await ai.models.generateContent({
    model: 'gemini-3.8-flash',
    contents: [{
      role: 'user',
      parts: [
        { fileData: { mimeType: 'application/pdf', fileUri: file.uri } },
        { text: 'Based on the preceding document, extract all text content from this document. ' +
          'Format the output as clean markdown with proper headings and structure.' },
      ],
    }],
  });
  console.log(response.text);
} finally {
  await ai.files.delete({ name: file.name });
}

What we learned running it

  • Put the document before the instruction. Gemini follows the request more reliably when the ask comes last.
  • Ask for formulas in $…$ only. With single $ delimiters, prices such as "$5 and $10" get rendered as math.
  • High media resolution reads small print and dense tables better. It costs more tokens, so the app lets you lower it.
  • Check the finish reason. MAX_TOKENS means the text was cut off, and thinking tokens count against the output limit. Gemini 4 Argon needs an output limit of at least 32,768 tokens.
  • Split long PDFs. Ten-page chunks keep each request well inside the output limit and let a failed run resume where it stopped instead of starting over.
  • Use generateContentStream instead of generateContent to show text as it arrives.
  • Keep batches gentle on free-tier keys. Bulk OCR runs 2 requests at a time and backs off on rate limits, retrying up to 3 times.
  • Read usageMetadata on every response if you want to show cost. The app does this for every billed call.

Web pages and online PDFs

For links, the app uses Gemini's URL context tool, at most 20 URLs per request. Only accept a result when the response's retrieval metadata confirms each URL was fetched; otherwise the model may answer from the URL alone. The app runs these through the Interactions API with store: false, because Interactions are stored by default.

From the terminal or an AI agent

The open-source OCR CLI and MCP server does the same from a terminal or inside an agent, with Gemini and other providers.