Document and PDF to JSON, Arabic and English avatar

Document and PDF to JSON, Arabic and English

Pricing

from $2.10 / 1,000 text pages

Go to Apify Store
Document and PDF to JSON, Arabic and English

Document and PDF to JSON, Arabic and English

Returns JSON from a PDF, Word DOCX, image or text file: every paragraph and clause, or only the fields your JSON Schema names, each with the page, paragraph and quoted sentence it came from. English and Arabic, with Hijri dates, Arabic-Indic digits and right to left repair. No LLM, no API key.

Pricing

from $2.10 / 1,000 text pages

Rating

0.0

(0)

Developer

Omar Nagy

Omar Nagy

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

a month ago

Last modified

Security

Limited permissions

Share

Document and PDF to JSON (Arabic and English)

This actor converts a PDF to JSON, and does the same for Word (DOCX) files, images and plain text, in English or Arabic. You get either the whole document as anchored paragraphs and clauses, or only the fields your JSON Schema asks for, and every value comes back with the page, paragraph and quoted sentence it was read from, so a person or an AI agent can check it.

There is no LLM and no API key in the path. Extraction is rule based and deterministic, and a field it cannot find comes back as found: false with a null value instead of a guess.

What it does

  • Reads the document. The text layer of a PDF (pdf.js), the body of a DOCX, plain text as it stands, and images (PNG, JPG, WebP, TIFF, BMP) with bundled English and Arabic OCR.
  • Anchors everything. Every paragraph gets a handle such as P17 and every clause one such as C10. Every extracted value reports its paragraph, clause and page, plus the verbatim sentence it was cut from.
  • Extracts to your schema. Pass a JSON Schema and get back exactly its properties, typed: dates as YYYY-MM-DD, numbers as numbers, booleans as booleans.
  • Handles Arabic properly. Presentation-form glyphs are mapped back to letters, lines a PDF stored in visual order are put back in reading order, Arabic-Indic digits are normalised before any number is parsed, Hijri dates are converted with the conversion disclosed, and Arabic clause numbering (المادة, البند) is segmented like the English kind.

Who it is for

  • AI agents and RAG pipelines that must cite where an answer came from, not just return a blob of text.
  • Contract review: effective dates, fees, notice periods, governing law, renewal terms, each with the clause a lawyer can open.
  • Invoice intake: invoice number, invoice and due dates, total and the billed-to party, read from labelled lines such as Invoice Number: INV-3337. Each value is taken from the label right before it, also on a line that holds several pairs. A total is only read right after a total label (total, total due, amount due, balance due, grand total, amount payable, and the Arabic equivalents), never from a payment term such as "due within 30 days". An identifier stops at the first sentence break or the next known label. A value with no matching label comes back as not found. This is rule based on labelled text; unusual wording needs x-keywords.
  • Bilingual Arabic and English files from the Gulf, Egypt and the wider region, where generic PDF tools return reversed or broken Arabic.

How to convert a PDF to JSON

  1. Pick a mode. analyze returns the whole document as paragraphs and clauses. extract returns the fields your schema names.
  2. Give one document: a URL, a file upload, pasted text or base64. Or leave a bundled sample selected to try it first.
  3. In extract mode, describe your fields as a JSON Schema.
  4. Run it. One dataset row comes back per document, and a short run summary is saved as OUTPUT.

Input example

This is the prefilled input. It runs the bundled 5-page sample agreement in extract mode:

{
"mode": "extract",
"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"]
}
}

To run it on your own file, add "documentUrl": "https://example.com/contract.pdf". Your document always wins over the sample, and the record notes in warnings that the sample was ignored.

The extraction schema

A JSON Schema object. Supported property types: string, number, integer, boolean, and array of string. Use "format": "date" on date fields. Nested objects are refused with a message, so flatten them into separate fields. Up to 40 fields.

Two optional extensions per property:

  • x-keywords: extra terms to look for, in either script. Quote the phrase the document itself uses. This is the biggest single lever on accuracy.
  • x-pattern: a regular expression, matched without regard to case, whose first capture group becomes the value. Arabic-Indic digits are normalised before matching.

Output example

The real record from the input above (build 0.1.8, run wz9fIfNuOMxZCTeaq). Only the fieldReports list is cut to two entries.

{
"mode": "extract",
"sourceType": "sample",
"sourceRef": "english-services-agreement",
"fileName": "services-agreement-en.pdf",
"format": "pdf",
"textSource": "pdf-text-layer",
"pageCount": 5,
"pagesCharged": 5,
"ocrPagesCharged": 0,
"extractionsCharged": 1,
"emptyPagesNotCharged": 0,
"truncatedAtMaxCost": false,
"paragraphCount": 67,
"clauseCount": 50,
"language": "latin",
"direction": "ltr",
"warnings": [],
"extractedAt": "2026-10-08T19:29:56.465Z",
"fields": {
"effective_date": "2025-03-14",
"annual_fee": 240000,
"notice_period_days": 60,
"governing_law": "10.1 This Agreement and any dispute arising out of or in connection with it shall be governed by and construed in accordance with the laws of the United Arab Emirates as applied in the Emirate of Dubai."
},
"fieldReports": [
{
"field": "effective_date",
"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\")",
"paragraphAnchor": "P2",
"clauseAnchor": "C1",
"page": 1,
"method": "date-parse",
"confidence": 0.9
},
{
"field": "annual_fee",
"found": true,
"value": 240000,
"valueRaw": "240,000",
"evidence": "4.1 The Client shall pay the Service Provider a fixed annual fee of two hundred and forty thousand United Arab Emirates Dirhams (AED 240,000), payable in four equal quarterly instalments in advance.",
"paragraphAnchor": "P25",
"clauseAnchor": "C17",
"page": 2,
"method": "number-parse-monetary",
"confidence": 0.95
}
],
"fieldsRequested": 4,
"fieldsFound": 4,
"missingRequired": [],
"complete": true
}

evidence is always a verbatim substring of the paragraph named by paragraphAnchor. In analyze mode the row instead carries text, paragraphs[] (anchor, page, text, direction) and clauses[] (anchor, label, heading, paragraph anchors, page, depth).

The dataset has three views: Documents (what was read and what it cost), Extracted fields and Document structure.

Arabic example

On the bundled Arabic DOCX, a contract dated بتاريخ ١٥ رمضان ١٤٤٦هـ الموافق ١٤ مارس ٢٠٢٥م, an Arabic schema returns contract_date: "2025-03-14", annual_fees: 1200000 (read from Arabic-Indic digits), renewal_notice_days: 60 and start_date: "2025-04-01". The Hijri reading is reported in notes: 15 Ramadan 1446 converts to 2025-03-15 on the tabular calendar, so the Gregorian date the document states for itself is the one returned.

Pricing

Pay per event, with no start fee. You pay for pages that produced text and for extractions, nothing else.

EventPriceWhen it is charged
Text page (page)$0.003Each page of a PDF, DOCX or text file that produced text. A file with no page breaks counts one page per 3,000 characters.
OCR page (ocr-page)$0.012Each image in which OCR found text.
Schema extraction (extraction)$0.02Once per extract run over a document with text.

Prices are lower on Bronze, Silver and Gold plans.

What typical runs cost on the Free plan:

Documentanalyzeextract
1-page invoice PDF$0.003$0.023
5-page contract PDF$0.015$0.035
One scanned invoice image$0.012$0.032
50-page Arabic PDF$0.15$0.17

Pages that produce no text, such as the pages of a scanned PDF, are never charged. Every charged count is written into the record (pagesCharged, ocrPagesCharged, extractionsCharged) and into OUTPUT, so a bill can be checked line by line.

If you set a maximum cost per run, the actor plans the charge before doing the work. It reads and delivers only the pages your limit pays for, marks the record truncatedAtMaxCost: true and sets the status to "Stopped at your maximum cost". If not even one page fits, nothing is delivered and nothing is charged.

Use it from Claude, Cursor or any MCP client

The actor is available as an MCP tool through Apify's MCP server:

https://mcp.apify.com?tools=omargnagy/document-to-json-extractor

Add it to your client's MCP config:

{
"mcpServers": {
"document-to-json": {
"url": "https://mcp.apify.com?tools=omargnagy/document-to-json-extractor"
}
}
}

Your client signs in to Apify on first use, or you can send your Apify API token as an Authorization: Bearer header. Then ask in plain language, for example: "Read https://example.com/lease.pdf and give me the start date, monthly rent and notice period, with the clause each one comes from."

Call it from your code

One synchronous call returns the dataset rows. Replace the URL with your document.

curl -X POST "https://api.apify.com/v2/acts/omargnagy~document-to-json-extractor/run-sync-get-dataset-items" \
-H "Authorization: Bearer $APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"mode": "analyze", "documentUrl": "https://example.com/contract.pdf"}'

JavaScript:

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('omargnagy/document-to-json-extractor').call({
mode: 'extract',
documentUrl: 'https://example.com/invoice.pdf',
schema: {
type: 'object',
properties: {
invoice_number: { type: 'string', description: 'the invoice number' },
total_due: { type: 'number', description: 'the total amount due' },
},
},
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items[0].fields);

Python:

import os
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("omargnagy/document-to-json-extractor").call(run_input={
"mode": "analyze",
"documentUrl": "https://example.com/contract.pdf",
})
items = client.dataset(run["defaultDatasetId"]).list_items().items
for p in items[0]["paragraphs"][:5]:
print(p["anchor"], p["page"], p["text"])

Field reference

FieldMeaning
formatpdf, docx, image or text, decided from the bytes first and the file name second.
textSourcepdf-text-layer, docx-body, plain-text or ocr.
pageCountPages, or null for a format with no pagination.
pagesCharged / ocrPagesCharged / extractionsChargedWhat this record was actually charged.
emptyPagesNotChargedPages that were read but produced no text, so were not charged.
truncatedAtMaxCosttrue when your maximum cost paid for only part of the document. A cut extraction is never marked complete.
language / directionarabic, latin, mixed or undetermined, and rtl or ltr.
normalisationsEncoding repairs applied to the text, such as Arabic presentation forms.
warningsAnything that should change how much you trust the output.
fieldsExtract mode. Exactly the properties your schema named.
fieldReports[]Extract mode. Per field: found, value, valueRaw, evidence, anchors, page, method, confidence, notes.
paragraphs[] / clauses[]Analyze mode. Every paragraph and clause with its anchor.
ocrMeanConfidenceOCR runs only. Mean confidence from 0 to 100.

confidence is a stated formula, not a feeling: it starts from how much of the field's vocabulary the paragraph carried and rises when a typed parser recovered the value. It is capped at 0.95.

Limits

  • One document per run, up to 25 MB. For many documents, start one run per document from the API, a task or a schedule.
  • Scanned PDFs are not OCR'd. A PDF with no text layer comes back with a warning and costs nothing. Send the page images instead, and they go through OCR. In a PDF that mixes text pages with scanned ones, each page without text is skipped, not charged, and named in warnings.
  • OCR reads English and Arabic only. Arabic OCR is less accurate than the text layer, especially on digits, and the record warns you when OCR read Arabic.
  • On some Arabic PDFs each printed line comes back as its own paragraph, a sentence's closing period can sit at the start of the line, and a line that mixes Arabic with Latin text can stay in visual order. Article headings written that way can be missed by the clause list. DOCX and plain text do not have this problem.
  • A string field written as a label and value returns just the value. Otherwise string and array fields return the best matching sentence, so expect a sentence for things like governing law.
  • A field with no label of its own, such as a company name printed only in a letterhead, often comes back as not found.
  • A date or amount belongs to the label right before it. On Invoice date: 12 March 2025 Due date: 11 April 2025 each date goes to its own field, and a date that only anchors an offset ("30 days after the invoice date of 12 March 2025") is not a due date. A date field takes a date only from a sentence that names it. A line labelled just Date counts as the document's own date, so it can answer invoice_date but never due_date. If the document uses other words, such as "Ship date" for shipping_date, add them to x-keywords.
  • A very long document that exceeds the dataset item size drops its full text first, then its clause list, then trailing paragraphs, and says so in warnings.

FAQ

Can it read scanned PDFs? Not directly. It does not rasterise PDF pages. A scanned PDF returns a warning and is not charged. Export the pages as images (PNG or JPG) and send those; each image is one OCR page.

Does it use an LLM or need an API key? No. Text comes from pdf.js, a DOCX reader and bundled Tesseract OCR. Extraction is rule based, so the same document and schema always give the same answer.

How accurate is it on invoices? On a public sample invoice PDF it returned 5 of 6 fields correctly: invoice number, invoice date, due date, total and billed-to. The vendor name, printed in the letterhead with no label, came back as not found rather than guessed. Contract terms with clear wording (dates, fees, notice periods) come back typed.

How are Hijri dates handled? A Hijri date is converted with the tabular civil calendar and the conversion is noted in notes. When the same sentence also states the Gregorian date, that stated date is returned, because official calendars can differ from the tabular one by a day.

How do I control what a run costs? Set a maximum cost per run in the run options. The actor reads only the pages that limit pays for and tells you so in the status and the record.

Can I process many documents at once? One run reads one document. Call the actor once per document from the API or MCP. Runs start in seconds and can run in parallel, up to your plan's concurrency limit.

What happens to a field it cannot find? It is reported with found: false, a null value and method: "none". If it was in required, it is listed in missingRequired and the record is not complete.

Which file types work? PDF with a text layer, DOCX, plain text, and PNG, JPG, WebP, TIFF or BMP images. A link to a web page (HTML) is refused, because this actor reads documents, not websites.