# OCR

> Read PDFs, scans and photos into text, Markdown and tables with Tarout OCR

# Tarout OCR

Tarout OCR reads PDFs, scanned pages and phone photos and returns clean
Markdown, plain text and tables for every page. It is built for Arabic and
English, including mixed pages, tashkeel, handwriting, tables and forms.

It is part of the AI Gateway: the same `sk_tarout_...` keys, the same prepaid
balance, and the same base URL.

## Choose a region

You choose where your pages are read with one field, `model`. Everything else
in the request stays the same.

| Region | Where pages are read | API `model` | CLI | MCP `ocr_process` |
| --- | --- | --- | --- | --- |
| **Local** (default) | In Saudi Arabia. Pages never leave the Kingdom. | `"tarout-ocr"` | `--region local` | `"region": "local"` |
| **Global** | Outside Saudi Arabia, by a supplier that keeps no copy of your data. | `"tarout-ocr-global"` | `--region global` | `"region": "global"` |

If you leave `model` out, Local is used. The response repeats the `model` that
read your document. In the OCR Studio, pick the region in **Region**: the code
under it changes to match. The `--region` flag needs Tarout CLI 1.16.0 or
later.

Documents are processed in memory and are not stored after the response.

```
POST https://tarout.sa/api/ai/v1/ocr
```

## Pricing

OCR is billed from your organization's prepaid balance, only for pages that
are read successfully. A page that fails is not billed.

OCR is billed by usage, for both `tarout-ocr` and `tarout-ocr-global`. The
price of a page depends on its size and on how much text it has.
 Every response reports what
it cost in `usage_info.billed_halalas` (100 halalas = 1 SAR). A page usually
costs less than one halala, so the value can be a fraction. Your wallet is
charged in whole halalas: fractions add up and are charged when they reach
one halala.

Your key must have the **OCR** scope. Create or edit keys in **Settings, API
keys**.

## Quick start

The examples read a public sample invoice in Arabic and English. Replace its
URL with your own document.

```sh
curl https://tarout.sa/api/ai/v1/ocr \
  -H "Authorization: Bearer $TAROUT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "tarout-ocr",
    "document": { "type": "document_url", "document_url": "https://tarout.sa/samples/ocr-invoice.pdf" }
  }'
```

To read in the Global region, change one line: `"model": "tarout-ocr-global"`.

From JavaScript or Python:

```js
// Node.js 18+ (save as ocr.mjs). Reads TAROUT_API_KEY.
const res = await fetch("https://tarout.sa/api/ai/v1/ocr", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.TAROUT_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    model: "tarout-ocr",
    document: { type: "document_url", document_url: "https://tarout.sa/samples/ocr-invoice.pdf" },
  }),
});
const result = await res.json();
if (!res.ok) throw new Error(result.error.message);

for (const page of result.pages) {
  console.log(page.status === "ok" ? page.markdown : `Page ${page.index} failed: ${page.error.message}`);
}
```

```python
import os, requests  # pip install requests

res = requests.post(
    "https://tarout.sa/api/ai/v1/ocr",
    headers={"Authorization": f"Bearer {os.environ['TAROUT_API_KEY']}"},
    json={
        "model": "tarout-ocr",
        "document": {"type": "document_url", "document_url": "https://tarout.sa/samples/ocr-invoice.pdf"},
    },
)
result = res.json()
if not res.ok:
    raise RuntimeError(result["error"]["message"])

for page in result["pages"]:
    print(page["markdown"] if page["status"] == "ok" else f"Page {page['index']} failed: {page['error']['message']}")
```

From the CLI, with a local file or a URL:

```sh
tarout ai ocr ./contract.pdf --format md --out contract.md
tarout ai ocr https://tarout.sa/samples/ocr-invoice.pdf --format json
tarout ai ocr ./scan.jpg --region global --format txt
```

Agents connected over MCP get the `ocr_process` tool.

## Request

| Field | Type | Description |
| --- | --- | --- |
| `model` | string | `tarout-ocr` (default, Local) or `tarout-ocr-global` (Global). See [Choose a region](#choose-a-region). |
| `document` | object | `{ "type": "document_url", "document_url": "..." }` or `{ "type": "image_url", "image_url": "..." }`. Each takes a public URL or a base64 data URL such as `data:application/pdf;base64,...`. |
| `pages` | number[] | Optional. 0-based page indexes to read. Every page when omitted. |
| `normalize` | object | Optional. `{ "digits": "latin" }` converts Arabic-Indic digits to 0-9. By default every digit stays exactly as printed. |
| `stream` | boolean | Optional. `true` streams each page as soon as it is ready (see below). |

Supported files: PDF, PNG, JPEG, WebP, TIFF, GIF and AVIF, up to 10 MB and
100 pages per request. HEIC photos are not supported: export them as JPEG.
Password-protected PDFs are refused.

The request shape is compatible with Mistral OCR: if you already call that API,
change the base URL and the key.

## Response

```json
{
  "id": "ocr_8f2c...",
  "object": "ocr.document",
  "model": "tarout-ocr",
  "pages": [
    {
      "index": 0,
      "status": "ok",
      "markdown": "# فاتورة ضريبية\n\n| الصنف | الكمية | السعر |\n| --- | --- | --- |\n| قهوة | 2 | 15.00 |",
      "text": "فاتورة ضريبية\n\nالصنف\tالكمية\tالسعر\nقهوة\t2\t15.00",
      "tables": [{ "format": "markdown", "rows": [["الصنف", "الكمية", "السعر"], ["قهوة", "2", "15.00"]] }],
      "dimensions": { "width": 1131, "height": 1600 },
      "truncated": false
    }
  ],
  "usage_info": { "pages_processed": 1, "pages_failed": 0, "doc_size_bytes": 182311, "billed_halalas": 0.39 },
  "latency_ms": 2140
}
```

- `markdown` keeps headings, lists and tables. Tables with merged cells come as
  HTML `<table>` blocks inside the Markdown.
- `text` is the same content as plain text; table cells are tab-separated.
- `tables` lists every table on the page as rows of cells.
- A page that could not be read has `"status": "failed"` and an `error`; the
  other pages are still returned, and only successful pages are billed.
- `truncated: true` means the page reached the output limit and its end may be
  missing.

## Streaming

With `"stream": true` the response is Server-Sent Events. Pages arrive in the
order they finish, each with its `index`:

```
data: {"type":"start","id":"ocr_8f2c...","pages":[0,1,2]}
data: {"type":"page","id":"ocr_8f2c...","page":{"index":1,"status":"ok","markdown":"..."}}
data: {"type":"page","id":"ocr_8f2c...","page":{"index":0,"status":"ok","markdown":"..."}}
data: {"type":"done","id":"ocr_8f2c...","usage_info":{...},"latency_ms":3120}
data: [DONE]
```

If you close the connection early, the remaining pages stop and only the pages
already delivered are billed.

## Errors

Errors use the AI Gateway envelope `{ "error": { "message", "type", "code" } }`.

| Status | Meaning |
| --- | --- |
| 400 | The body is invalid, or a page in `pages` does not exist. |
| 401 | Missing or invalid key. |
| 403 | The key does not have the OCR scope. Add it in Settings, API keys. |
| 402 | Your prepaid balance is too low. Top it up in the dashboard. |
| 413 | The document is larger than 10 MB or asks for more than 100 pages. |
| 415 | Unsupported file type. |
| 422 | The PDF is damaged or password protected. |
| 429 | Rate limit: 60 requests and 120 pages per minute per key. |
| 503 | OCR is busy or temporarily unavailable (`OCR_BUSY`, `OCR_UNAVAILABLE`). Retry shortly; busy pages are not billed. |

## OCR Studio

Try OCR without code in the dashboard: **AI, OCR Studio**. Drop a file,
watch each page appear next to the original, and download the result as
Markdown, text or JSON.

---

## Every Tarout agent guide

- [Start Here (Agents)](https://tarout.sa/docs/for-ai/start.md)
- [Overview](https://tarout.sa/docs/for-ai.md)
- [Deploying an app](https://tarout.sa/docs/for-ai/deploy.md)
- [Databases](https://tarout.sa/docs/for-ai/database.md)
- [Object storage](https://tarout.sa/docs/for-ai/storage.md)
- [Custom domains](https://tarout.sa/docs/for-ai/domains.md)
- [Plans and upgrades](https://tarout.sa/docs/for-ai/billing.md)
- [Troubleshooting](https://tarout.sa/docs/for-ai/troubleshoot.md)
- [Agent Onboarding](https://tarout.sa/docs/for-ai/onboarding.md)
- [CLI Reference](https://tarout.sa/docs/for-ai/cli-reference.md)
- [CLI JSON Schema](https://tarout.sa/docs/for-ai/cli-json-schema.md)

Whole corpus in one file: https://tarout.sa/llms-full.txt · index: https://tarout.sa/llms.txt
Any docs page is raw markdown at the same URL + `.md`. Short link to the entry point: https://tarout.sa/deploy.md
