# AI Data Extraction (`obaskly/ai-data-extraction-api`) Actor

Extract custom data from PDF, DOCX, and images using AI. Turn invoices, forms, and other documents into structured, validated JSON. Simply provide document URLs and define the fields you need.

- **URL**: https://apify.com/obaskly/ai-data-extraction-api.md
- **Developed by:** [Amine Ch](https://apify.com/obaskly) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $10.00 / 1,000 document processeds

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

## Universal AI Document Extractor

Turn PDFs, Word documents and images into **structured JSON**. You decide which fields you want.

Give the Actor a list of document URLs and a list of fields (name, description, type). It downloads each document, reads it (native text, tables, OCR for scans, or visually), asks an AI model for exactly those fields, validates the answer, and returns one clean result per document. It is a document extraction engine, not an autonomous agent: it does one job and never browses, scrapes or acts on your behalf.

It works for any document type because **you** define the fields: invoices, receipts, contracts, CVs/resumes, purchase orders, financial statements, application forms, registration documents and other business documents.

### What you get

- **Your fields, your types.** `string`, `number`, `integer`, `boolean`, `date` (ISO 8601), `array`, `object`.
- **Honest nulls.** A field that is not in the document is `null` with status `not_found`. It is never guessed. An illegible one is `unreadable`.
- **Confidence estimates** per field (optional).
- **Source references** per field: page number and the supporting text (optional). Each reference says whether the quoted text was actually found in the document (`text_verified`), and page numbers are never invented.
- **Per-document results.** One failing document never stops the others.
- **Predictable cost.** Native text first, OCR only for scans, hard limits on size, pages and tokens, and exact token usage in every result.

### Supported documents

| Type | Extensions | How it is read |
|---|---|---|
| PDF | `.pdf` | Native text and tables (Markdown). Pages without usable text are OCR'd and sent to the AI as an image plus OCR text. Form-field values are included. |
| Word | `.docx` | Headings, paragraphs, tables (including merged cells), headers/footers, tracked insertions, text boxes. |
| Images | `.jpg` `.jpeg` `.png` `.webp` | EXIF orientation applied, transparency flattened, OCR plus the image itself. |

Not supported: legacy `.doc`, password-protected files, spreadsheets, presentations, web pages, and direct file uploads (documents must be reachable by **public HTTPS URL**).

### Quick start

#### Input

```json
{
  "documents": [
    { "url": "https://example.com/invoice.pdf", "filename": "invoice.pdf" }
  ],
  "fields": [
    { "name": "invoice_number", "description": "The unique invoice identifier", "type": "string" },
    { "name": "invoice_date", "description": "The invoice issue date", "type": "date" },
    { "name": "supplier_name", "description": "The legal name of the supplier", "type": "string" },
    { "name": "total_amount", "description": "The final amount including taxes", "type": "number" }
  ]
}
```

#### Output (one dataset item per document)

```json
{
  "index": 0,
  "success": true,
  "url": "https://example.com/invoice.pdf",
  "document": { "filename": "invoice.pdf", "type": "pdf", "pages": 1 },
  "extracted_data": {
    "invoice_number": "INV-2026-0417",
    "invoice_date": "2026-10-20",
    "supplier_name": "Northwind Synthetic Trading Ltd",
    "total_amount": 1011.5
  },
  "field_status": {
    "invoice_number": "found", "invoice_date": "found", "supplier_name": "found", "total_amount": "found"
  },
  "confidence": {
    "invoice_number": 0.98, "invoice_date": 0.97, "supplier_name": 0.9, "total_amount": 0.97
  },
  "sources": {
    "invoice_number": { "page": 1, "text": "Invoice No: INV-2026-0417", "location": null, "text_verified": true },
    "total_amount": { "page": 1, "text": "Total Due: 1,011.50 EUR", "location": null, "text_verified": true }
  },
  "charges": { "document-processed": 1 },
  "usage": {
    "pages_processed": 1, "ocr_pages": 0, "vision_pages": 0,
    "ai_requests": 1, "ai_input_tokens": 1828, "ai_output_tokens": 793
  }
}
```

A failed document looks like this (the other documents are unaffected):

```json
{
  "index": 3,
  "success": false,
  "url": "https://example.com/missing.pdf",
  "document": { "filename": "missing.pdf", "type": null, "pages": null },
  "usage": { "pages_processed": 0, "ocr_pages": 0, "vision_pages": 0, "ai_requests": 0, "ai_input_tokens": 0, "ai_output_tokens": 0 },
  "error": { "code": "DOWNLOAD_FAILED", "message": "The document was not found at the given URL." }
}
```

The run also saves a `SUMMARY` record in the key-value store: documents succeeded and failed, failures by error code, pages, AI token totals and billing totals (events charged, documents skipped by a limit). It contains counts only, never document contents.

### Input reference

| Field | Type | Default | Description |
|---|---|---|---|
| `documents` | array, **required** | – | 1–100 items: `{ "url": "https://…", "filename": "optional" }`. HTTPS only. |
| `fields` | array, **required** | – | 1–100 items: `{ "name", "description", "type" }`. |
| `language` | string | `auto` | `auto` or a code like `en`, `fr`, `de`. Improves OCR for scans. |
| `include_confidence` | boolean | `true` | Per-field confidence estimate. |
| `include_source_references` | boolean | `true` | Per-field page and supporting text. |
| `vision_mode` | `auto` | `never` | `always` | `auto` | See [How reading works](#how-reading-works). |
| `max_file_size_mb` | integer 1–50 | `20` | Larger files are rejected. |
| `max_pages` | integer 1–100 | `30` | PDFs with more pages are rejected (not truncated). |
| `max_output_tokens` | integer 256–16000 | automatic | Leave empty to size it from the number of fields (about 120 tokens per field). |
| `output_format` | `json` | `json` | Only JSON is supported. |

**Field definitions.** `name` must start with a letter or underscore and contain only letters, digits and underscores (max 64, unique ignoring case). `description` (max 500 characters, optional but strongly recommended) tells the AI what you mean, and it is the single biggest lever on accuracy. `type` defaults to `string`. For `array` and `object` fields, describe the structure in the description:

```json
{ "name": "line_items", "type": "array",
  "description": "Array of objects with description, quantity, unit_price and amount for each invoice line" }
```

Type rules: numbers have currency symbols and thousands separators removed (`"1.234,50 €"` → `1234.5`); dates are normalised to `YYYY-MM-DD` (or `YYYY-MM` / `YYYY` when the document gives less). If a date cannot be normalised it is returned as written and a warning is added; the AI never assumes a currency that the document does not state.

### Pricing and limits

This Actor uses Apify's **pay-per-event** pricing. The *Pricing* section of the Actor page always shows the current prices; at launch they are:

| Event | Charged | Launch price |
|---|---|---|
| `document-processed` | once for every document that was **successfully extracted** (first page included) | $0.01 |
| `extra-page` | for every PDF page after the first | $0.002 |
| `vision-page` | for every page the AI read as an image (scans and photos) | $0.005 |

- **You pay only for results.** Failed documents (download errors, unsupported or corrupted files, blocked URLs, AI failures, documents skipped by a limit) are never charged. Platform usage is included in the price.
- **Every result shows what it cost**, in `charges`. A Word document counts as one document with no extra pages.
- Examples: a 1-page invoice PDF costs $0.01; a 14-page text PDF costs $0.01 + 13 x $0.002 = $0.036; a 3-page scan read as images costs $0.01 + 2 x $0.002 + 3 x $0.005 = $0.029; a photo costs $0.01 + $0.005 = $0.015.
- **Spending limit.** Set *maximum charge per run* in the run options. A document is only started if its full price still fits; otherwise it is skipped, not charged, and reported as `SPENDING_LIMIT_REACHED`. The run still finishes normally.
- **Free Apify plan.** To keep the Actor sustainable, runs on the free plan are limited to **3 documents and 10 pages per run** (processed in the order you list them). Anything beyond is skipped with `FREE_PLAN_LIMIT` and a clear status message. Paid plans have no such limit.

### How reading works

1. **Download** the document from its public HTTPS URL.
2. **Detect the real file type** from the file's content (not just its name).
3. **Extract content:**
   - PDF: native text in reading order, tables as Markdown. Pages with little or no text, or garbled font encoding, are treated as scans.
   - Scans and images: Tesseract OCR (English, French, German, Spanish, Italian, Portuguese, Dutch) **and**, in `auto` mode, the page image itself. We measured that OCR alone can silently drop table headers and whole columns even when its confidence score is high, so scanned pages go to the AI as image + OCR text. Up to 10 pages per document are sent as images; further scanned pages use OCR text only and the result carries a warning saying so.
   - DOCX: structure-preserving text.
4. **Ask the AI** for the requested fields only. The document is treated strictly as data.
5. **Validate** the reply against your field types. If the reply is malformed it is retried once with the validation errors (never with extracted values). Values are nulled unless the field is `found`.
6. **Check the sources.** Quotes are matched against the extracted text; a wrong page is corrected, a quote that cannot be found is flagged `text_verified: false`.

`vision_mode`: `auto` (recommended) sends scanned pages as image + OCR; `never` uses text and OCR only (cheapest, but may miss table content in scans); `always` also sends every text page as an image (highest accuracy and cost).

Confidence values are **the model's own estimates, not calibrated probabilities**, and are `null` unless a field was found. `text_verified: false` does not mean a value is wrong: image-only pages have no text to match against, and OCR can differ slightly from the quote.

### Error codes

Run-level problems (invalid input, a model that is not available) stop the run immediately with a clear status message **before anything is processed or billed**. Per-document errors appear in that document's `error` field:

| Code | Meaning |
|---|---|
| `UNSAFE_URL` | The URL is not allowed. Use a public `https://` link. |
| `DOWNLOAD_FAILED` | Not found, requires authentication, server error, timed out, empty. |
| `FILE_TOO_LARGE` | Over `max_file_size_mb` (or an image with an extreme pixel count). |
| `UNSUPPORTED_FILE_TYPE` | Not PDF/DOCX/JPG/PNG/WEBP, or the extension contradicts the content. |
| `CORRUPTED_DOCUMENT` | The file cannot be parsed. |
| `ENCRYPTED_DOCUMENT` | Password-protected PDF. |
| `PAGE_LIMIT_EXCEEDED` | More pages than `max_pages`. |
| `NO_READABLE_CONTENT` | No text and nothing the OCR/AI could read (e.g. a DOCX that only contains pictures). |
| `DOCUMENT_TOO_LARGE` | Too much text for one AI request (about 400,000 characters). |
| `AI_OUTPUT_TRUNCATED` | The reply hit `max_output_tokens`. Raise it or request fewer fields. |
| `AI_INVALID_RESPONSE` | The AI reply was still invalid after one correction request. |
| `AI_REFUSED` | The AI declined to process the document. |
| `AI_RATE_LIMITED` / `AI_TIMEOUT` / `AI_UNAVAILABLE` / `AI_REQUEST_REJECTED` / `AI_PROVIDER_ERROR` | Provider-side problems. |
| `FREE_PLAN_LIMIT` | Free Apify plan: more than 3 documents or 10 pages in one run. The document was not processed. See [Pricing and limits](#pricing-and-limits). |
| `SPENDING_LIMIT_REACHED` | The maximum charge you set for the run was reached; the document was not processed and not charged. |
| `INTERNAL_ERROR` | Unexpected error; no internal detail is exposed. |

### Run it from the API

Start it from the Apify Console (Input tab) or call it through the API:

**cURL** (synchronous run, returns the dataset items):

```bash
curl -X POST "https://api.apify.com/v2/acts/U9GpBbmIMUe5XH5sg/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "documents": [{ "url": "https://example.com/invoice.pdf" }],
    "fields": [
      { "name": "invoice_number", "description": "The unique invoice identifier", "type": "string" },
      { "name": "total_amount", "description": "The final amount including taxes", "type": "number" }
    ]
  }'
```

**Python** (`pip install apify-client`):

```python
from apify_client import ApifyClient

client = ApifyClient("<APIFY_TOKEN>")
run = client.actor("U9GpBbmIMUe5XH5sg").call(run_input={
    "documents": [{"url": "https://example.com/invoice.pdf"}],
    "fields": [
        {"name": "invoice_number", "description": "The unique invoice identifier", "type": "string"},
        {"name": "total_amount", "description": "The final amount including taxes", "type": "number"},
    ],
})

for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    if item["success"]:
        print(item["extracted_data"])
    else:
        print("failed:", item["error"]["code"], item["error"]["message"])
```

For many documents, start the run asynchronously (`.start()` / `POST /acts/{id}/runs`) and read the dataset when it finishes.

### Limitations

- Documents must be public HTTPS URLs (no authentication, no direct upload yet).
- DOCX: images, footnotes/endnotes and comments are not read. A DOCX containing only pictures is rejected with `NO_READABLE_CONTENT`; send the images themselves.
- OCR uses English, French, German, Spanish, Italian, Portuguese and Dutch data. Other scripts rely on the AI reading the page image (`vision_mode: auto` falls back to this when OCR is unreliable).
- Handwriting and very low-quality scans depend on what the model can read; check `confidence` and `warnings`.
- Tables are given to the AI as Markdown, whose first row looks like a header; mention the table structure in a field description if it matters ("first row below the column headings").
- Very large documents are rejected (not chunked) once they exceed the page, size or input-character limits; the model's context is large enough that splitting would add cost and lose cross-page context.
- Not a substitute for human review where errors are costly.

### Your data

Documents are processed only for the duration of the run and are not stored by the Actor. Their content is sent to a third-party AI service to extract the fields you ask for, so make sure you have the right to process the documents, especially if they contain personal data.

# Actor input Schema

## `documents` (type: `array`):

Documents to process. Each item needs a publicly accessible HTTPS `url` and may have an optional `filename`. Supported: PDF, DOCX, JPG, JPEG, PNG, WEBP. Free Apify plan: runs are limited to 3 documents and 10 pages in total (documents beyond that are skipped, not charged). You pay only for documents that are successfully extracted; see the Pricing section.

## `fields` (type: `array`):

The information you want from every document. Each item has a `name` (letters, digits, underscore), an optional `description` that tells the AI what to look for, and a `type`: string, number, integer, boolean, date, array or object. For array/object fields, describe the structure in the description.

## `language` (type: `string`):

Use `auto` to detect the language automatically, or give a code such as `en`, `fr`, `de`, `es`. It improves OCR for scanned documents.

## `include_confidence` (type: `boolean`):

Add a per-field confidence estimate (0 to 1). These are the model's own estimates, not statistically calibrated probabilities.

## `include_source_references` (type: `boolean`):

Add the page number and the supporting text for each extracted value. A reference is marked `text_verified` only when the quoted text was found in the document's extracted text.

## `vision_mode` (type: `string`):

`auto`: scanned pages are sent to the AI as an image plus OCR text (most accurate for tables), text pages as text only. `never`: text and OCR only (cheapest, may miss table content in scans). `always`: every page is also sent as an image (highest accuracy and cost).

## `max_file_size_mb` (type: `integer`):

Documents larger than this are rejected without being processed.

## `max_pages` (type: `integer`):

PDFs with more pages than this are rejected. Higher limits increase processing time and AI cost.

## `max_output_tokens` (type: `integer`):

Upper bound on the AI reply per document. Leave empty to size it automatically from the number of fields (about 120 tokens per field, up to 16,000). Reasoning tokens count towards this limit. If a document fails with `AI_OUTPUT_TRUNCATED`, raise this or request fewer fields.

## `output_format` (type: `string`):

Format of the extracted data. Only JSON is currently supported.

## Actor input object example

```json
{
  "documents": [
    {
      "url": "https://example.com/invoice.pdf",
      "filename": "invoice.pdf"
    }
  ],
  "fields": [
    {
      "name": "invoice_number",
      "description": "The unique invoice identifier",
      "type": "string"
    },
    {
      "name": "invoice_date",
      "description": "The invoice issue date",
      "type": "date"
    },
    {
      "name": "supplier_name",
      "description": "The legal name of the supplier",
      "type": "string"
    },
    {
      "name": "total_amount",
      "description": "The final amount including taxes",
      "type": "number"
    }
  ],
  "language": "auto",
  "include_confidence": true,
  "include_source_references": true,
  "vision_mode": "auto",
  "max_file_size_mb": 20,
  "max_pages": 30,
  "output_format": "json"
}
```

# Actor output Schema

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

One dataset item per input document, with the extracted fields and usage.

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

Documents processed, failures and AI token usage for the run.

# 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 = {
    "documents": [
        {
            "url": "https://example.com/invoice.pdf",
            "filename": "invoice.pdf"
        }
    ],
    "fields": [
        {
            "name": "invoice_number",
            "description": "The unique invoice identifier",
            "type": "string"
        },
        {
            "name": "invoice_date",
            "description": "The invoice issue date",
            "type": "date"
        },
        {
            "name": "supplier_name",
            "description": "The legal name of the supplier",
            "type": "string"
        },
        {
            "name": "total_amount",
            "description": "The final amount including taxes",
            "type": "number"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("obaskly/ai-data-extraction-api").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 = {
    "documents": [{
            "url": "https://example.com/invoice.pdf",
            "filename": "invoice.pdf",
        }],
    "fields": [
        {
            "name": "invoice_number",
            "description": "The unique invoice identifier",
            "type": "string",
        },
        {
            "name": "invoice_date",
            "description": "The invoice issue date",
            "type": "date",
        },
        {
            "name": "supplier_name",
            "description": "The legal name of the supplier",
            "type": "string",
        },
        {
            "name": "total_amount",
            "description": "The final amount including taxes",
            "type": "number",
        },
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("obaskly/ai-data-extraction-api").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 '{
  "documents": [
    {
      "url": "https://example.com/invoice.pdf",
      "filename": "invoice.pdf"
    }
  ],
  "fields": [
    {
      "name": "invoice_number",
      "description": "The unique invoice identifier",
      "type": "string"
    },
    {
      "name": "invoice_date",
      "description": "The invoice issue date",
      "type": "date"
    },
    {
      "name": "supplier_name",
      "description": "The legal name of the supplier",
      "type": "string"
    },
    {
      "name": "total_amount",
      "description": "The final amount including taxes",
      "type": "number"
    }
  ]
}' |
apify call obaskly/ai-data-extraction-api --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,obaskly/ai-data-extraction-api"
        }
    }
}
```

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/U9GpBbmIMUe5XH5sg/builds/uEfZLKkOZ0OWa3sIS/openapi.json
