# Document to JSON Extractor (Arabic and English) (`omargnagy/document-to-json-extractor`) Actor

PDF, DOCX, image or plain text in, structured JSON out, with the page and paragraph anchor for every value so a person can verify it. Arabic and English: Hijri dates, Arabic-Indic digits, right to left order and Arabic clause numbering all handled. Pass a JSON Schema to get exactly your fields.

- **URL**: https://apify.com/omargnagy/document-to-json-extractor.md
- **Developed by:** [Omar Nagy](https://apify.com/omargnagy) (community)
- **Categories:** Developer tools, AI, Automation
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 page processeds

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Document to JSON Extractor (Arabic and English)

A PDF, DOCX, image or plain text file goes in. Structured JSON comes out, and every value carries the page and paragraph it was read from, so a person can check it.

### What it does

- **Reads the text layer** of a PDF with pdf.js, the body of a DOCX straight from OOXML, and plain text as it stands.
- **Reads images with OCR** using Tesseract data that ships as pinned npm dependencies and is baked into the image at build time, so a run never waits on a language model CDN. English and Arabic.
- **Anchors everything.** Every paragraph gets a stable handle such as `P17`. Every clause gets one such as `C10`. Every extracted value reports the paragraph anchor, the clause anchor and the page it came from, plus the verbatim sentence it was cut out of.
- **Treats Arabic as a first class citizen.** Presentation form glyphs are mapped back to base letters, lines that a PDF stored in visual order are put back into logical order, Arabic-Indic digits are normalised before any number is parsed, Hijri dates are converted and the conversion is disclosed, and Arabic clause numbering conventions are segmented alongside the English ones.
- **Extracts to your schema.** Pass a JSON Schema and get back exactly its properties, no more and no fewer, each one anchored.
- **Never invents a value.** The engine is deterministic and rule based, with no language model in the path. A value is only ever returned together with a verbatim evidence span and the anchor that span came from. If a field cannot be anchored it comes back as `found: false` with a null value.

### Who it is for

An **AI agent that has to read a document and then cite it.** Contract review, invoice and receipt intake, bilingual Arabic and English legal or compliance files, RAG ingestion that needs paragraph level provenance rather than one undifferentiated blob of text.

The concrete use case this was built around: an agent is handed a services agreement, asked "when does this renew and how much notice do I have to give", and has to answer with a number **and** with the clause a human can open and read. `mode: "extract"` returns both in one call.

It also runs in **Apify standby mode**, which keeps a container warm and answers over HTTP, so an agent can convert a document mid task without paying a cold start.

### Modes

| Mode | Standby route | What you get |
|---|---|---|
| `analyze` | `GET /analyze` | The normalised text, every paragraph with its anchor and direction, the clause segmentation, the detected language mix, and any repairs or warnings. |
| `extract` | `GET` or `POST /extract` | Exactly the fields your JSON Schema names, plus a report per field with the anchor, the evidence span, the method and a confidence. |

Any other mode is refused with a 400 that lists the allowed ones. Any other standby route is a 404. A caller mistake never comes back as a 500.

### Input

Give **exactly one** document source. Naming none, or naming two, is refused.

| Field | Type | Notes |
|---|---|---|
| `mode` | string | `analyze` or `extract`. Required. Defaults to `analyze`. |
| `sample` | string | Run against a document bundled in the actor: `english-services-agreement`, `arabic-services-agreement`, `english-invoice-scan`. Use `none` when you supply your own document. |
| `documentUrl` | string | Direct http or https link to the file. A key-value store record URL is an ordinary URL and works here. |
| `keyValueStoreRecordKey` | string | Key of a record in **this run's own** default key-value store, for a file uploaded to the run. |
| `documentText` | string | Paste the text directly. |
| `documentBase64` | string | The file itself, base64 encoded. |
| `fileName` | string | Optional. Helps format detection when the source carries no name. The bytes are trusted before the name. |
| `schema` | object | **Required when `mode` is `extract`.** Ignored when `mode` is `analyze`. |
| `ocr` | boolean | Default `true`. When `false`, an image input is refused rather than answered with nothing. |
| `ocrLanguages` | array | Default `["eng", "ara"]`. Only those two are bundled, anything else is refused. Used only for image input. |

#### The extraction schema

A JSON Schema object. Property types supported: `string`, `number`, `integer`, `boolean`, and `array` of `string`. Nested objects are refused with a message rather than answered with nulls, so flatten them into separate fields.

Two optional extensions per property:

- `x-keywords` is a list of extra terms to look for, in either script. This is the single biggest lever on accuracy. Quote the phrase the document itself uses.
- `x-pattern` is a regular expression. It runs against a fold of the text where Arabic-Indic digits and letter variants are normalised, and its first capture group becomes the value. The value is always cut from the original text, so it comes back exactly as the document writes it.

```json
{
  "type": "object",
  "properties": {
    "effective_date": { "type": "string", "format": "date", "description": "the date the agreement takes effect" },
    "annual_fee": { "type": "number", "description": "the fixed annual fee payable by the client" },
    "notice_period_days": {
      "type": "integer",
      "description": "days of written notice required for non renewal",
      "x-keywords": ["notice of non-renewal"]
    },
    "auto_renews": {
      "type": "boolean",
      "description": "whether the agreement renews automatically",
      "x-keywords": ["automatically renew"]
    }
  },
  "required": ["effective_date", "annual_fee"]
}
```

### Output

One record per document. The dataset from a run has no column that is null in every row: fields that belong only to one path, such as the OCR confidence columns, exist only on runs that took that path.

#### `mode: "extract"`, real output

Input: `mode: "extract"`, `sample: "english-services-agreement"`, and the schema above. This is the actual record, abbreviated only where marked.

```json
{
  "mode": "extract",
  "sourceType": "sample",
  "sourceRef": "english-services-agreement",
  "fileName": "services-agreement-en.pdf",
  "format": "pdf",
  "textSource": "pdf-text-layer",
  "byteSize": 71407,
  "pageCount": 5,
  "pagesCharged": 5,
  "ocrPagesCharged": 0,
  "paragraphCount": 67,
  "clauseCount": 50,
  "characterCount": 8225,
  "language": "latin",
  "direction": "ltr",
  "arabicShare": 0,
  "latinShare": 1,
  "arabicChars": 0,
  "latinChars": 6415,
  "normalisations": [],
  "warnings": [],
  "extractedAt": "2026-09-05T00:44:00.361Z",
  "fields": {
    "effective_date": "2025-03-14",
    "annual_fee": 240000,
    "notice_period_days": 60,
    "auto_renews": true
  },
  "fieldReports": [
    {
      "field": "effective_date",
      "type": "string",
      "required": true,
      "found": true,
      "value": "2025-03-14",
      "valueRaw": "14 March 2025",
      "evidence": "This Professional Services Agreement (the \"Agreement\") is made and entered into on 14 March 2025 (the \"Effective Date\")",
      "anchors": [{ "paragraphAnchor": "P2", "clauseAnchor": "C1", "page": 1 }],
      "paragraphAnchor": "P2",
      "clauseAnchor": "C1",
      "page": 1,
      "matchedTerms": ["effective", "date", "agreement", "effect"],
      "method": "date-parse",
      "confidence": 0.9,
      "notes": []
    },
    {
      "field": "notice_period_days",
      "type": "integer",
      "required": false,
      "found": true,
      "value": 60,
      "valueRaw": "60",
      "evidence": "2.2 Upon expiry of the Initial Term, this Agreement shall automatically renew for successive periods of twelve (12) months each (each a \"Renewal Term\") unless either Party gives written notice of non- renewal to the other Party not less than sixty (60) days prior to the end of the then current term.",
      "anchors": [{ "paragraphAnchor": "P18", "clauseAnchor": "C10", "page": 2 }],
      "paragraphAnchor": "P18",
      "clauseAnchor": "C10",
      "page": 2,
      "matchedTerms": ["notice", "period", "days", "notice of non-renewal", "written", "non", "renewal"],
      "method": "number-parse-nearest-term",
      "confidence": 0.92,
      "notes": []
    }
  ],
  "fieldsRequested": 4,
  "fieldsFound": 4,
  "missingRequired": [],
  "complete": true
}
```

`evidence` is a verbatim substring of the paragraph named by `paragraphAnchor`. That is checked by the test suite, not just asserted here. Note the `non- renewal` in that evidence span: the source PDF wrapped a hyphenated word across a line, and the text is handed back with the artefact rather than tidied up. Keyword matching closes the gap internally, which is why `notice of non-renewal` still appears in `matchedTerms`.

#### `mode: "analyze"`, real output from the Arabic sample

```json
{
  "mode": "analyze",
  "format": "docx",
  "textSource": "docx-body",
  "language": "arabic",
  "direction": "rtl",
  "arabicShare": 0.988,
  "latinShare": 0.012,
  "paragraphCount": 62,
  "clauseCount": 49,
  "characterCount": 6199,
  "paragraphs": [
    { "anchor": "P2", "index": 2, "page": null, "text": "تحرر هذا العقد في مدينة القاهرة بتاريخ ١٥ رمضان ١٤٤٦هـ الموافق ١٤ مارس ٢٠٢٥م", "direction": "rtl", "charCount": 76 }
  ],
  "clauses": [
    {
      "anchor": "C5",
      "index": 5,
      "label": "المادة (١)",
      "normalisedLabel": "1",
      "numbering": "arabic-article",
      "heading": "التمهيد وملحقات العقد",
      "paragraphAnchors": ["P9", "P10"],
      "page": null,
      "direction": "rtl",
      "parentAnchor": null,
      "depth": 0
    }
  ]
}
```

Running `extract` on that same Arabic document for its execution date returns `"2025-03-15"` with `method: "hijri-date-parse"` and this note attached to the value:

> Converted with the tabular civil Hijri calendar. The sighting based calendar a court or a ministry applies can differ by a day, so treat this value as an approximation and check any Gregorian equivalent the document states for itself.

#### Field reference

| Field | Meaning |
|---|---|
| `format` | `pdf`, `docx`, `image` or `text`, decided from the bytes first and the file name second. |
| `textSource` | `pdf-text-layer`, `docx-body`, `plain-text` or `ocr`. Says how the text was obtained. |
| `pageCount` | Pages, or `null` for a format that carries no pagination such as a DOCX with no page breaks. |
| `pagesCharged` | Pages read from a text layer, charged as `page`. Always an integer, `0` on an OCR run. |
| `ocrPagesCharged` | Pages read by OCR, charged as `ocr-page`. Always an integer, `0` on a text layer run. `pagesCharged + ocrPagesCharged` is the number of pages read, which equals `pageCount` when there is one and otherwise counts one page per 3000 characters. |
| `language` / `direction` | `arabic`, `latin`, `mixed` or `undetermined`, measured by letter count with digits and punctuation ignored. |
| `normalisations` | Repairs applied to the text, each one an encoding defect in the source rather than an editorial change. Empty when the source was clean. |
| `warnings` | Things that should change how much you trust the output, for example a PDF whose font has a broken ToUnicode map. |
| `paragraphs[]` | `anchor`, `index`, `page`, `text`, `direction`, `charCount`, and `ocrConfidence` on OCR runs only. |
| `clauses[]` | `anchor`, `label` as the document writes it, `normalisedLabel` in ASCII, `numbering`, `heading`, `text`, `paragraphAnchors`, `page`, `direction`, `parentAnchor`, `depth`. |
| `fieldReports[].method` | How the value was recovered: `date-parse`, `hijri-date-parse`, `number-parse`, `number-parse-nearest-term`, `number-parse-monetary`, `enum-match`, `x-pattern`, `boolean-assertion`, `boolean-negation-cue`, `sentence-match` or `none`. |
| `fieldReports[].confidence` | A stated formula, not a feeling: it starts from how much of the field's own vocabulary the paragraph carried and is lifted when a typed parser recovered the value rather than a sentence fallback. Capped at 0.95, because a rule engine is never certain. |

### Standby HTTP API

```
GET  /                                             service info and the route list
GET  /analyze?sample=english-services-agreement
GET  /analyze?documentUrl=https://example.com/contract.pdf
POST /extract   {"documentUrl": "...", "schema": {...}}
GET  /extract?documentUrl=...&schema=<url encoded JSON>
```

Every response is `{ ok, tookMs, result }` or `{ ok: false, error }`. An unknown route is a 404 with the allowed routes named.

### Pay-per-event

The actor charges pay-per-event with three named events, and **there is no run start fee**, so a call costs only the events it actually consumes. The current rates are on the Pricing tab of this listing. Every count is reported in the record itself, so a bill can be reconciled against the output line by line, and every charge is wrapped so a billing failure can never break a response that was already produced correctly.

| Event | Charged | Why it is a separate event |
|---|---|---|
| `page` | Once per page read from a text layer, so a PDF, a DOCX or plain text. Reported in every record as `pagesCharged`. | Reading is the cost that scales with the document. |
| `ocr-page` | Once per page read by OCR, so an image input. Reported in every record as `ocrPagesCharged`. | OCR costs about seven times what reading a text layer costs, measured on the platform. Charging one blended page price would overcharge the common case to subsidise the rare expensive one. |
| `extraction` | Once per schema constrained extraction, so once per `extract` call. | Extraction is work done on top of reading, and an `analyze` call does not do it. |

A document is never part text layer and part OCR: only an image goes through OCR, because a scanned PDF is refused with a warning rather than rasterised. So exactly one of the two page events fires on any one document, and `pagesCharged + ocrPagesCharged` always equals the pages read.

Charging is explicit rather than relying on Apify's built in dataset item event, because standby mode answers over HTTP and never pushes to a dataset, so the built in event would charge nothing and the standby API would be served for free.

### Limits

- One document per run, up to **25 MB**. Standby mode is the way to do many documents quickly.
- A dataset item has a size ceiling. A very long document sheds its full text, then its clause list, then its trailing paragraphs, and says in `warnings` exactly what it dropped rather than failing the push.
- OCR covers **English and Arabic** only, because those are the two language models bundled. Arabic OCR is materially less accurate than the text layer path, especially on Arabic-Indic digits, and the record says so in `warnings` whenever Arabic OCR runs.
- A schema may name at most **40 fields**.
- The actor runs with **limited permissions**. It reads this run's own default key-value store and makes HTTP requests, nothing else. To read a record in another store, pass its record URL as `documentUrl`.

### What it does not do

It does not summarise, interpret, translate, or give advice, and there is no language model anywhere in the path. It does not rasterise a scanned PDF, because that needs a native canvas build that cannot be installed in the Apify Node image, so a scanned PDF comes back with a warning telling you to send the page images through the image input instead. It does not guess: an unrecognised mode, an unknown sample, a nested schema object or a missing document source is refused with a 400 that says how to fix it, and a field it cannot anchor is reported as not found rather than filled in.

One honest limitation of the rule based approach: for `string` and `array` fields the engine returns the best matching sentence, ranked by how much of your field vocabulary it carried. On a document that mentions the same words in several places, it can land on a reference to a term rather than on the sentence that defines it. That is why every value ships with an anchor and an evidence span, and why `x-keywords` quoting the defining phrase is the fastest way to sharpen a result.

### Local development

```bash
npm ci
npm run smoke          # 19 checks over the bundled samples, including the paths that must fail
npm start              # one batch run, reading storage/key_value_stores/default/INPUT.json
npm run start:standby  # the HTTP API on :4321
```

The icon lives at `.actor/logo.png`. The bundled samples in `samples/` use invented companies and invented people, and are not real agreements. They are stored base64 encoded as text, because Apify's source file upload sniffs each file as text or binary and read the small DOCX as text, which round tripped it through UTF-8 and delivered a corrupt zip to the built image. Decode one with `base64 -d samples/services-agreement-en.pdf.b64 > contract.pdf`.

# Actor input Schema

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

analyze = return the normalised text, paragraph anchors, language mix and clause segmentation. extract = return exactly the fields named in your JSON Schema, each with an anchor. Any other value is refused.

## `sample` (type: `string`):

Run against a document bundled inside this actor, which needs no URL and no upload. Choose 'No bundled sample' when you supply your own document below. Exactly one document source must be given, so leave this on 'No bundled sample' if you set documentUrl, keyValueStoreRecordKey, documentText or documentBase64.

## `documentUrl` (type: `string`):

Direct http or https link to a PDF, DOCX, image or text file. Required when you are not using a bundled sample, a key-value store record, inline text or base64. A key-value store record URL is an ordinary URL and works here.

## `keyValueStoreRecordKey` (type: `string`):

Key of a record in this run's own default key-value store, for a file you uploaded to the run. Required when you are not using a bundled sample, a URL, inline text or base64. To read a record in a different store, pass its record URL as documentUrl instead, because this actor runs with limited permissions and does not read other stores.

## `documentText` (type: `string`):

Paste the document text directly. Useful for a quick test and for text you already extracted elsewhere. Required when you are not using a bundled sample, a URL, a record key or base64.

## `documentBase64` (type: `string`):

The document itself, base64 encoded, for callers that hold the bytes and have nowhere to host them. Required when you are not using a bundled sample, a URL, a record key or inline text.

## `fileName` (type: `string`):

Optional. Helps format detection when the source carries no name, for example with base64 input. The bytes are trusted first, so this only matters for an ambiguous file.

## `schema` (type: `object`):

Required when mode is extract, and ignored when mode is analyze. A JSON Schema object whose properties name the fields you want. Supported property types are string, number, integer, boolean and array of string. Two optional extensions per property: x-keywords is a list of extra terms to look for in either script, and x-pattern is a regular expression whose first capture group becomes the value. Nested objects are refused rather than answered with nulls.

## `ocr` (type: `boolean`):

When on, an image input is read with the bundled Tesseract data. When off, an image input is refused instead of returning nothing. This has no effect on PDF, DOCX or text input, and scanned PDF pages are never rasterised.

## `ocrLanguages` (type: `array`):

Which bundled language models OCR uses. Only eng and ara are bundled, and any other value is refused. Used only when the input is an image.

## Actor input object example

```json
{
  "mode": "analyze",
  "sample": "english-services-agreement",
  "documentUrl": "https://example.com/contract.pdf",
  "keyValueStoreRecordKey": "uploaded-contract.pdf",
  "fileName": "contract.pdf",
  "schema": {
    "type": "object",
    "properties": {
      "effective_date": {
        "type": "string",
        "format": "date",
        "description": "the date the agreement takes effect"
      },
      "annual_fee": {
        "type": "number",
        "description": "the fixed annual fee payable by the client"
      },
      "notice_period_days": {
        "type": "integer",
        "description": "days of written notice required for non renewal",
        "x-keywords": [
          "notice of non-renewal"
        ]
      },
      "governing_law": {
        "type": "string",
        "description": "the law governing the agreement",
        "x-keywords": [
          "governed by"
        ]
      }
    },
    "required": [
      "effective_date"
    ]
  },
  "ocr": true,
  "ocrLanguages": [
    "eng",
    "ara"
  ]
}
```

# Actor output Schema

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

No description

# 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": "analyze",
    "sample": "english-services-agreement",
    "schema": {
        "type": "object",
        "properties": {
            "effective_date": {
                "type": "string",
                "format": "date",
                "description": "the date the agreement takes effect"
            },
            "annual_fee": {
                "type": "number",
                "description": "the fixed annual fee payable by the client"
            },
            "notice_period_days": {
                "type": "integer",
                "description": "days of written notice required for non renewal",
                "x-keywords": [
                    "notice of non-renewal"
                ]
            },
            "governing_law": {
                "type": "string",
                "description": "the law governing the agreement",
                "x-keywords": [
                    "governed by"
                ]
            }
        },
        "required": [
            "effective_date"
        ]
    },
    "ocr": true,
    "ocrLanguages": [
        "eng",
        "ara"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("omargnagy/document-to-json-extractor").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": "analyze",
    "sample": "english-services-agreement",
    "schema": {
        "type": "object",
        "properties": {
            "effective_date": {
                "type": "string",
                "format": "date",
                "description": "the date the agreement takes effect",
            },
            "annual_fee": {
                "type": "number",
                "description": "the fixed annual fee payable by the client",
            },
            "notice_period_days": {
                "type": "integer",
                "description": "days of written notice required for non renewal",
                "x-keywords": ["notice of non-renewal"],
            },
            "governing_law": {
                "type": "string",
                "description": "the law governing the agreement",
                "x-keywords": ["governed by"],
            },
        },
        "required": ["effective_date"],
    },
    "ocr": True,
    "ocrLanguages": [
        "eng",
        "ara",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("omargnagy/document-to-json-extractor").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": "analyze",
  "sample": "english-services-agreement",
  "schema": {
    "type": "object",
    "properties": {
      "effective_date": {
        "type": "string",
        "format": "date",
        "description": "the date the agreement takes effect"
      },
      "annual_fee": {
        "type": "number",
        "description": "the fixed annual fee payable by the client"
      },
      "notice_period_days": {
        "type": "integer",
        "description": "days of written notice required for non renewal",
        "x-keywords": [
          "notice of non-renewal"
        ]
      },
      "governing_law": {
        "type": "string",
        "description": "the law governing the agreement",
        "x-keywords": [
          "governed by"
        ]
      }
    },
    "required": [
      "effective_date"
    ]
  },
  "ocr": true,
  "ocrLanguages": [
    "eng",
    "ara"
  ]
}' |
apify call omargnagy/document-to-json-extractor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,omargnagy/document-to-json-extractor"
        }
    }
}
```

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/0K4wIJjhWmgz03AA0/builds/5zONxG2iD4QnlMAev/openapi.json
