# Arabic Image OCR & Saudi Invoice QR Reader (`saif-tools/arabic-image-ocr`) Actor

Fast Arabic + English OCR for screenshots, documents and images (normalised and raw text, lines with confidence, RTL order), plus exact fields from Saudi ZATCA e-invoice QR codes (signature not checked). For receipts and Arabic-Indic digits use Arabic Receipt Reader.

- **URL**: https://apify.com/saif-tools/arabic-image-ocr.md
- **Developed by:** [saif alnuaimi](https://apify.com/saif-tools) (community)
- **Categories:** AI, Developer tools, Agents
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Arabic Image OCR & Saudi Invoice QR Reader

Fast Arabic + English OCR for screenshots, web pages, chat messages and typed or scanned documents, plus the exact fields from Saudi ZATCA e-invoice QR codes.

- **Plain-text mode (`text`)**: low-cost OCR that handles dense, small print well. Lines come with a confidence score and in right-to-left reading order.
- **Exact QR fields (`qr`)**: the seller name, VAT number, timestamp, invoice total and VAT total, decoded from the e-invoice QR. These are the values encoded in the QR, never guesses from OCR text.

Each image gives you the text exactly as read (`textRaw`) and a normalised copy (`text`). In the normalised copy, digits become 0-9, `٫` becomes `.`, `٬` becomes `,` and tatweel is removed.

> **Receipts, invoices, prices or Arabic-Indic digits?** Use **[Arabic Receipt Reader](https://apify.com/saif-tools/arabic-receipt-reader)** (`arabic-receipt-reader`) instead. It reads Arabic-Indic digits much better, gives every number a confidence score, finds the total, subtotal, VAT and line items, checks they add up, and tells you which receipts need a human check.

> This tool reads and reports the fields on an invoice. It doesn't issue, send or receive e-invoices, isn't an accredited service provider, and doesn't confirm compliance with ZATCA or UAE rules.

### Which mode should I use?

| You have | Use |
|---|---|
| Screenshots, chat messages, web pages, scanned or typed documents | `text` (this actor) |
| Only the Saudi e-invoice QR values | `qr` (this actor). Images without a ZATCA QR cost nothing. |
| Receipts, invoices, price lists, anything where **the numbers matter** | [Arabic Receipt Reader](https://apify.com/saif-tools/arabic-receipt-reader) |
| Photos of signs, shop fronts or menus | [Arabic Receipt Reader](https://apify.com/saif-tools/arabic-receipt-reader) (copes better with real-world photos) |

#### Plain-text mode (`text`)

This mode runs Tesseract (Arabic + English, `tessdata_best` models) with automatic upscaling, deskewing and QR masking. A second pass re-reads Arabic-Indic and Persian digits with a dedicated digit classifier.

**Warning: plain-text mode reads Arabic-Indic digits (٠-٩) poorly.** The digit pass improves them a lot on clean, printed images. On photos, low-resolution images or unusual fonts, digits are still often wrong or dropped. **For receipts, invoices, prices or any number you rely on, use [Arabic Receipt Reader](https://apify.com/saif-tools/arabic-receipt-reader).** Plain-text mode also handles photos of signs badly. Treat it as a tool for screenshots and documents.

**Image size:** every image is downscaled to at most 4 megapixels before OCR (and upscaling for small text never goes past 4 MP). `imageSize` in each row shows the original and processed sizes. Line boxes are always in original-image pixels.

#### Exact QR fields (`qr`, optional in `text` with `includeQrFields`)

When an image contains a Saudi e-invoice QR (ZATCA TLV, base64), you get `exactQrFields`:

| Tag | Field |
|---|---|
| 1 | `sellerName` |
| 2 | `vatNumber` (plus `vatNumberFormatValid`: 15 digits, starts and ends with 3; this is a flag, never a rejection) |
| 3 | `timestamp` (plus `timestampIso`) |
| 4 | `invoiceTotal` (with VAT) |
| 5 | `vatTotal` |
| 6–9 | reported as present, with their lengths (hash, signature, public key, certificate signature) |

`tagsFound` lists the tags in the QR. `qrFormat` is an informational label: `phase1` (tags 1–5, before 2023), `phase2-standard` (tags 1–7 or 1–8), `phase2-simplified` (tags 1–9) or `other`.

**We decode the QR TLV but do NOT verify its cryptographic signature/stamp** (`signatureChecked` is always `false`). A QR that decodes cleanly can still be forged or belong to another invoice. These are the exact fields stored in the QR, not proof that the invoice is genuine or registered.

`qr.status` is `zatca_tlv` (decoded), `not_zatca_tlv` (a QR that isn't a ZATCA invoice QR, e.g. a menu link; its text is in `qr.codes[].rawText`) or `no_qr_found`. The QR fields are never filled in from the OCR text.

### Input

| Field | Default | Notes |
|---|---|---|
| `mode` | `text` | `text` or `qr` |
| `imageUrls` | | Public image links (JPEG, PNG, WebP, GIF, BMP, TIFF) |
| `keyValueStoreRecords` | | Uploaded images: `record-key` for this run's store, or `store-name-or-id/record-key` |
| `includeQrFields` | `false` | Text mode: also read ZATCA QR fields |
| `languages` | `ara+eng` | `ara`, `eng` or `ara+eng` |
| `pageLayout`, `upscale`, `deskew`, `threshold` | sensible defaults | Image preprocessing |
| `digitSecondPass` | `true` | Arabic-Indic digit classifier |
| `maskQrCodes` | `true` | Blanks QR codes before OCR so they don't become junk text |
| `outputLines`, `outputWords`, `stripTatweel` | `true`, `false`, `true` | Output detail |
| `maxImageSizeMb`, `maxConcurrency` | 20, 2 | Limits |

**No images in the input?** The actor reads a small bundled sample image (an Arabic/English text sample in `text` mode, a sample ZATCA QR in `qr` mode) so you can see the output. The same happens if the example link in the input form can't be downloaded. Sample rows have `sample: true` and are free. The run's status message and `OUTPUT.note` say so too (for example "Input image failed to download; returned built-in sample instead").

Example: QR fields only, from files uploaded to a key-value store:

```json
{ "mode": "qr", "keyValueStoreRecords": ["my-invoices/inv-0001.png", "my-invoices/inv-0002.jpg"] }
```

### Output

One row per image. The views are Overview and Exact QR fields. A shortened `text` row:

```json
{
  "source": "https://example.com/screenshot.png",
  "sample": false,
  "status": "ok",
  "mode": "text",
  "text": "فاتورة ضريبية Tax Invoice\nCustomer service: support@example.com\nالعنوان: شارع الملك فهد، الرياض",
  "textRaw": "فاتورة ضريبية Tax Invoice\n...",
  "meanConfidence": 88.4,
  "primaryScript": "arabic",
  "digitSystems": [],
  "lines": [{"text": "فاتورة ضريبية Tax Invoice", "textRaw": "فاتورة ضريبية Tax Invoice", "confidence": 91.2, "direction": "rtl", "bbox": [212, 38, 476, 46]}],
  "width": 900, "height": 320,
  "imageSize": {"original": [900, 320], "processed": [1800, 640], "downscaledBy": 1.0, "maxProcessedMegapixels": 4.0},
  "qr": {"status": "no_qr_found", "count": 0, "codes": []},
  "exactQrFields": null,
  "chargedEvents": ["plain-text-ocr"]
}
```

A decoded QR adds `"exactQrFields": {"sellerName": "...", "vatNumber": "310122393500003", "vatNumberFormatValid": true, "timestamp": "...", "invoiceTotal": "172.50", "vatTotal": "22.50", "qrFormat": "phase1", "tagsFound": [1, 2, 3, 4, 5], "signatureChecked": false}`.

Images that can't be read (a broken link, a file that isn't an image, a file that is too large) get a row with `status: "error"` and an `error` message. **Error rows are free.** The `OUTPUT` record summarises the run: images processed, failed and skipped, sample rows, the events charged and how long it took.

### Pricing (pay per event)

| Event | When | Price |
|---|---|---|
| `plain-text-ocr` | Each image read in `text` mode | $0.003 |
| `exact-qr-fields` | Each invoice whose ZATCA QR was decoded, in `qr` mode or in `text` mode with `includeQrFields` | $0.01 |

- `chargedEvents` in each row lists only the events actually charged for that row.
- One `exact-qr-fields` charge per invoice, never a separate "QR read" charge. In `qr` mode, images without a ZATCA QR are free. Error rows and sample rows are always free.
- **Spending limit:** if you set a maximum cost per run, the actor checks before each image that the image's maximum possible price still fits. If it doesn't, the actor stops cleanly and keeps every row saved so far (`OUTPUT.stoppedByChargeLimit`, `skippedSources`). You are never charged for a row that wasn't saved. In `text` mode with `includeQrFields`, each image needs $0.013 of headroom, because we only know whether it has a QR after reading it.

### Limitations

- **OCR is not perfect.** On our synthetic test set, plain-text mode with the digit pass read about 84% of Arabic-Indic digits correctly (plain Tesseract: about 23%). Real photos score lower. For numbers, use the Receipt Reader.
- Handwriting, decorative calligraphy, heavy diacritics (tashkeel) and stretched display lettering are read poorly.
- Lines that mix Arabic and Latin are put into reading order with heuristics. Word order around the script change can be off.
- Only Saudi ZATCA QR codes are decoded into fields. Other QR codes are reported as `not_zatca_tlv` with their raw text.
- Only the first frame of multi-page TIFF and GIF files is read.

### Privacy

Images are downloaded into the run's own container, processed in memory and not sent to any third-party service. All OCR runs inside the actor. Results are stored only in your run's dataset and key-value store, under your Apify account and its retention settings. Invoices can contain personal data (names, VAT numbers), so make sure you have the right to process the images you submit, and delete runs you no longer need.

# Changelog

This Actor's version history is a separate document: https://apify.com/saif-tools/arabic-image-ocr/changelog.md

# Actor input Schema

## `mode` (type: `string`):

<b>text</b>: plain-text OCR (fast, cheapest) for screenshots, documents and clean images. Weak on Arabic-Indic digits (٠-٩): for receipts, invoices and prices use the <b>Arabic Receipt Reader</b> actor.<br><b>qr</b>: exact fields from a Saudi ZATCA e-invoice QR code only (no OCR). Images without a ZATCA QR are free.

## `imageUrls` (type: `array`):

Public links to JPEG, PNG, WebP, GIF, BMP or TIFF images. Images over 4 megapixels are downscaled to 4 MP before OCR. Leave empty to try the bundled sample image (free).

## `keyValueStoreRecords` (type: `array`):

Images already uploaded to an Apify key-value store. Write <code>record-key</code> for this run's default store, or <code>store-name-or-id/record-key</code>.

## `includeQrFields` (type: `boolean`):

Text mode only. Decodes a ZATCA QR if one is present. Adds one exact-qr-fields event per image where such a QR is found; images without one cost nothing extra.

## `languages` (type: `string`):

Text mode only. Installed: ara, eng. Use ara+eng for mixed images, eng for English only.

## `pageLayout` (type: `string`):

How Tesseract splits the image into text blocks.

## `upscale` (type: `string`):

Auto enlarges images whose text is too small for OCR.

## `deskew` (type: `boolean`):

Detects and corrects rotation up to ±10°.

## `threshold` (type: `string`):

Converts the image to black and white before OCR. Try adaptive for photos with uneven lighting.

## `digitSecondPass` (type: `boolean`):

Re-reads Arabic-Indic and Persian digits with a dedicated digit classifier.

## `maskQrCodes` (type: `boolean`):

Blanks detected QR codes before OCR so they don't turn into junk text.

## `stripTatweel` (type: `boolean`):

The normalised fields drop tatweel and map Arabic-Indic and Persian digits to 0-9 (٫ → . and ٬ → ,). The raw fields keep the text as read.

## `outputLines` (type: `boolean`):

One entry per text line with its confidence, bounding box and direction.

## `outputWords` (type: `boolean`):

Adds the words of each line with their bounding boxes. Makes rows much larger.

## `maxImageSizeMb` (type: `integer`):

Larger files are skipped with a free error row.

## `maxConcurrency` (type: `integer`):

How many images are processed at the same time.

## Actor input object example

```json
{
  "mode": "text",
  "imageUrls": [
    {
      "url": "https://upload.wikimedia.org/wikipedia/commons/6/61/Arabic-wikipedia-screenshot-oct2018.png"
    }
  ],
  "includeQrFields": false,
  "languages": "ara+eng",
  "pageLayout": "single-column",
  "upscale": "auto",
  "deskew": true,
  "threshold": "none",
  "digitSecondPass": true,
  "maskQrCodes": true,
  "stripTatweel": true,
  "outputLines": true,
  "outputWords": false,
  "maxImageSizeMb": 20,
  "maxConcurrency": 2
}
```

# Actor output Schema

## `results` (type: `string`):

One row per image: normalised and raw text, lines with confidence, image sizes, QR status and exact QR fields.

## `summary` (type: `string`):

Images processed, failed and skipped at the spending limit, and the events charged.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "mode": "text",
    "imageUrls": [
        {
            "url": "https://upload.wikimedia.org/wikipedia/commons/6/61/Arabic-wikipedia-screenshot-oct2018.png"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("saif-tools/arabic-image-ocr").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "mode": "text",
    "imageUrls": [{ "url": "https://upload.wikimedia.org/wikipedia/commons/6/61/Arabic-wikipedia-screenshot-oct2018.png" }],
}

# Run the Actor and wait for it to finish
run = client.actor("saif-tools/arabic-image-ocr").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "mode": "text",
  "imageUrls": [
    {
      "url": "https://upload.wikimedia.org/wikipedia/commons/6/61/Arabic-wikipedia-screenshot-oct2018.png"
    }
  ]
}' |
apify call saif-tools/arabic-image-ocr --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,saif-tools/arabic-image-ocr"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/vtMrqkNlQtcwww1rD/builds/hkuaaFFaAvlP1pRJk/openapi.json
