# Text to JSON: Extract Structured Data from Any Document (`birchwood-data/text-to-data`) Actor

Turn messy text, emails, HTML, PDFs and Word docs into clean JSON that matches your own schema. Invoices, receipts, forms, listings and emails. Free rule-based extraction, or bring your own LLM key. Built for AI agents and MCP.

- **URL**: https://apify.com/birchwood-data/text-to-data.md
- **Developed by:** [Birchwood Data](https://apify.com/birchwood-data) (community)
- **Categories:** AI, Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$2.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

### What is Text to JSON?

**Turn messy text into clean JSON that matches your own schema.** Paste an email, an invoice, a receipt, a web page or a form, or point it at PDF / Word / HTML links. Describe the fields you want, and you get back validated JSON in exactly that shape.

Use it as a **text to JSON converter, PDF data extractor, invoice and receipt parser, email parser or document parsing API**. Run it in Apify Console with no code, call it from the API or Python/JavaScript clients, schedule it, or plug it into Make, Zapier, n8n and AI agents over MCP.

You pay **$2.00 per 1,000 documents** ($0.002 each). Documents that fail, or where none of your fields are found, are free. There's no monthly rental, no platform-usage charge and no AI markup.

***

### Why use Text to JSON?

- **Your schema, your shape.** Use a standard JSON Schema or a one-line shorthand like `{"name": "string", "total": "number", "items": [{"sku": "string", "qty": "integer"}]}`. Nested objects, enums and arrays of objects (tables and line items) all work.
- **Free extraction engine by default.** The built-in rule-based extractor reads labels (`Invoice #: 42`), tables (pipe, tab, space-aligned, CSV and HTML), bullet lists and common patterns (emails, phones, URLs, dates, money, addresses). It's deterministic, fast, and costs no AI tokens.
- **Bring your own LLM key for free-form text.** Add your own OpenAI, Anthropic, Gemini, OpenRouter, Groq, Mistral, DeepSeek or any OpenAI-compatible key and the Actor uses that model. Your provider bills you directly and we add nothing. The key is a secret input: Apify stores it encrypted, and it's never logged or written to the output. Gemini's free tier works too.
- **Validated output.** Every result is checked against your schema, so you get `valid`, `validationErrors`, `missingRequired`, `missingFields` and a `completeness` score. Your pipeline can tell a complete record from a partial one.
- **Clean types.** `"$1,299.00"` becomes `1299`, `"Sept 28, 2026"` becomes `"2026-09-28"`, `"yes"` becomes `true`, and `"€1.299,00"` becomes `1299`.
- **Many formats.** Accepts pasted text, HTML, PDF (with a text layer), DOCX, JSON, CSV and TXT, up to 25 MB per file.
- **Built for AI agents.** It works as a tool over the [Apify MCP server](https://mcp.apify.com) in Claude, Cursor, VS Code, n8n and Make. Prices are per event only, so the Actor also qualifies for agentic payments.

### What can you use Text to JSON for?

- 🧾 **Invoices and receipts**: vendor, invoice number, dates, totals, tax and line items.
- 📧 **Emails and contact forms to CRM**: name, company, email, phone, budget and intent.
- 🏠 **Listings and product pages**: price, specs, features and availability.
- 📄 **Forms and applications**: applicant details, addresses, yes/no answers and skills.
- 💼 **Job posts and resumes**: title, salary, location and requirements.
- 🤖 **AI agent pipelines**: a dependable "text to JSON" step for n8n, Make, Zapier, LangChain or your own agent.

### How to extract structured data from text, PDFs and documents

1. Paste your document into **Text**, or add **Document URLs**, or send a batch in **Multiple texts**.
2. Describe the output in **Output schema**. The shorthand is the quickest way to start.
3. Optional: choose an **LLM provider** and paste **your own API key** to use AI extraction.
4. Click **Start**. Each document comes back as one dataset item, and the `OUTPUT` record holds just the extracted JSON.

#### Schema shorthand

| Shorthand | Meaning |
| --- | --- |
| `"string"`, `"number"`, `"integer"`, `"boolean"` | basic types |
| `"date"`, `"datetime"` | ISO 8601 date or date-time |
| `"email"`, `"url"`, `"phone"` | pulled out and cleaned |
| `"open\|closed\|pending"` | enum (one of these values) |
| `["string"]` | list of strings |
| `[{"sku": "string", "qty": "integer"}]` | table / line items |
| `{"street": "string", "city": "string"}` | nested object |

You can also pass any standard JSON Schema (draft-07 / 2020-12, with local `$ref`s). Add a `title` to a property to tell the extractor which label to look for, e.g. `"customer": {"type": "string", "title": "Bill to"}`.

### Input example

```json
{
  "text": "ACME Supplies Inc.\nInvoice #: INV-2026-0042\nInvoice Date: Sept 28, 2026\n\nDescription   Qty   Unit Price   Amount\nSteel brackets   4   $12.50   $50.00\nHex bolts M8   200   $0.15   $30.00\n\nTotal Due: $80.00",
  "schema": {
    "vendor_name": "string",
    "invoice_number": "string",
    "invoice_date": "date",
    "total": "number",
    "line_items": [{ "description": "string", "quantity": "number", "unit_price": "number", "amount": "number" }]
  }
}
```

### Output example

```json
{
  "documentId": "text",
  "status": "ok",
  "method": "rules",
  "data": {
    "vendor_name": "ACME Supplies Inc.",
    "invoice_number": "INV-2026-0042",
    "invoice_date": "2026-09-28",
    "total": 80.0,
    "line_items": [
      { "description": "Steel brackets", "quantity": 4, "unit_price": 12.5, "amount": 50.0 },
      { "description": "Hex bolts M8", "quantity": 200, "unit_price": 0.15, "amount": 30.0 }
    ]
  },
  "valid": true,
  "validationErrors": [],
  "missingRequired": [],
  "missingFields": [],
  "fieldsFilled": 5,
  "fieldsTotal": 5,
  "completeness": 1.0,
  "charsProcessed": 214,
  "truncated": false,
  "llmError": null,
  "processedAt": "2026-10-04T20:09:02Z"
}
```

### Rules engine or LLM: which should I use?

| | Rule-based (default, free) | Your LLM key |
| --- | --- | --- |
| Best for | Semi-structured docs: invoices, receipts, forms, "Label: value" text, tables, spec sheets, listings | Free-form prose, unusual layouts, inferring meaning |
| Cost | $0.002 per document | $0.002 per document + your provider's token cost |
| Speed | Milliseconds | Seconds |
| Deterministic | Yes | Mostly (temperature 0) |

If an LLM call fails (bad key, quota, timeout), the Actor falls back to the rules engine and reports the reason in `llmError`. You can turn this off with **Fall back to rule-based extraction**.

### How much does it cost to convert text to JSON?

**Pay per event: $0.002 per document processed ($2.00 per 1,000).**

- One charge per document that comes back with `status: "ok"` (at least one field extracted).
- Failed documents (bad URL, unreadable file) and empty results (`status: "empty"`, none of your fields found) are **free**.
- No usage pass-through: platform compute is included.
- Set **Maximum cost per run** in the run options to cap spending. The Actor stops cleanly when it reaches the cap.

### Use it from AI agents (MCP)

Add the Actor as a tool to any MCP client through Apify's hosted MCP server:

```
https://mcp.apify.com/?tools=birchwood-data/text-to-data
```

Your agent passes `text` (or `urls`) and a `schema`, and gets the JSON back. Parameter descriptions are written so agents can fill them in without help.

### API

```bash
curl -X POST "https://api.apify.com/v2/acts/birchwood-data~text-to-data/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"text": "Name: Ana Diaz\nEmail: ana@example.com", "schema": {"name": "string", "email": "email"}}'
```

### FAQ

#### Do I need an OpenAI or other LLM API key?

No. The default rule-based engine is free of AI costs and works well on invoices, receipts, forms, tables and "Label: value" text. Add your own key only for free-form prose.

#### Can it extract tables and invoice line items?

Yes. Define an array of objects in your schema (for example `[{"sku": "string", "qty": "integer"}]`) and pipe, tab, space-aligned, CSV and HTML tables are mapped to it row by row.

#### Which file formats can I parse?

Pasted text, HTML, PDF with a text layer, DOCX, JSON, CSV and TXT, up to 25 MB per file. Scanned image-only PDFs need OCR first.

#### Can I process many documents at once?

Yes. Use **Multiple texts** or **Document URLs** to send a batch. Each document becomes one dataset item that you can export as JSON, CSV or Excel.

#### How do I call it from my code or an AI agent?

Use the API example above or the Apify Python/JavaScript client. For AI agents, add it as a tool via the Apify MCP server.

### Limitations

- Scanned, image-only PDFs need OCR first. We read the PDF's text layer.
- The rules engine looks for labels, tables and patterns. It does not interpret free prose the way an LLM does, so use your own key for that.
- Fields that aren't found are returned as `null` (or `[]` for lists). They're never guessed.
- Pages that need a login or JavaScript to render aren't fetched. Paste their text instead.

### Privacy

Documents are processed only during your run and stored only in your own run's dataset. Your LLM key is a secret input and is never logged or saved to the output. When you use an LLM, the text goes to the provider you chose, under your own account with them.

### Other Actors by Birchwood Data

| Actor | What it does |
| --- | --- |
| [ATS Job Scraper: Greenhouse, Lever, Ashby & More](https://apify.com/birchwood-data/ats-job-scraper) | Scrape live jobs from Greenhouse, Lever, Ashby, SmartRecruiters and Recruitee career pages, with salary, location and remote flag. Feed the job descriptions into Text to JSON to extract skills or requirements. |

### Support

Found a document it handles badly? Open an issue on the **Issues** tab with a (redacted) sample and the schema you used. We usually improve the extractor within a few days.

# Actor input Schema

## `text` (type: `string`):

Paste one document: an email, invoice, receipt, form, listing, chat message, HTML page source, etc. For several documents use 'Multiple texts' or 'Document URLs'.

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

Either a standard JSON Schema ({"type":"object","properties":{...}}) or a simple shorthand: {"name": "string", "total": "number", "date": "date", "email": "email", "paid": "boolean", "status": "open|closed", "tags": \["string"], "items": \[{"description": "string", "qty": "integer"}]}. Shorthand types: string, number, integer, boolean, date, datetime, email, url, phone, 'a|b|c' (enum). Nested objects and arrays of objects (tables / line items) are supported.

## `texts` (type: `array`):

Optional. A JSON array of documents: plain strings, or objects like {"id": "msg-1", "text": "..."} (the id is copied to the output). Each document becomes one output item.

## `urls` (type: `array`):

Optional. Public links to documents. PDFs (with a text layer), Word .docx, web pages, text, JSON and CSV files are supported. Max 25 MB each. Scanned image-only PDFs need OCR first.

## `instructions` (type: `string`):

Optional hints for the LLM, e.g. 'Amounts are in EUR', 'customer = the company being billed', 'dates are day-first'. Ignored by the rule-based extractor.

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

auto = use your LLM key if you provide one, otherwise the free rule-based extractor. rules = always rule-based (fast, deterministic, no AI cost). llm = always use your LLM key.

## `llmProvider` (type: `string`):

Which provider your API key is for. Your key is billed by that provider directly; this Actor never adds a markup.

## `llmApiKey` (type: `string`):

Optional. Your own API key for the provider above. Stored encrypted by Apify, used only for this run, never logged or saved to the output.

## `llmModel` (type: `string`):

Optional model ID. Defaults: OpenAI gpt-6-luna, Anthropic claude-haiku-4-5, Gemini gemini-3.1-flash-lite, OpenRouter openai/gpt-6-luna, Groq llama-3.3-70b-versatile, Mistral mistral-small-latest, DeepSeek deepseek-chat. Required for 'openai-compatible'.

## `llmBaseUrl` (type: `string`):

Optional. For 'openai-compatible' (e.g. https://your-host/v1) or to route a provider through a proxy/gateway.

## `fallbackToRules` (type: `boolean`):

If your LLM call errors (bad key, quota, timeout), still return a rule-based result instead of failing that document. The LLM error is reported in 'llmError'.

## `includeSourceText` (type: `boolean`):

Adds 'sourceText' (the plain text we extracted from the document) to each output item. Handy for debugging or RAG.

## `maxCharsPerDocument` (type: `integer`):

Longer documents are truncated to this many characters (the output says 'truncated': true).

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

How many documents to fetch/extract at the same time.

## Actor input object example

```json
{
  "text": "ACME Supplies Inc.\nPhone: (217) 555-0142   Email: billing@acme-supplies.com\n\nInvoice #: INV-2026-0042\nInvoice Date: Sept 28, 2026\nDue Date: 10/28/2026\n\nBill To:\nBirch & Co. LLC\n77 Oak Street, Portland, OR 97205\n\nDescription              Qty    Unit Price     Amount\nSteel brackets (box)       4       $12.50      $50.00\nHex bolts M8 x 100       200        $0.15      $30.00\n\nSubtotal: $80.00\nTax (8%): $6.40\nTotal Due: $86.40",
  "schema": {
    "vendor_name": "string",
    "invoice_number": "string",
    "invoice_date": "date",
    "due_date": "date",
    "customer": "string",
    "vendor_email": "email",
    "subtotal": "number",
    "tax": "number",
    "total": "number",
    "line_items": [
      {
        "description": "string",
        "quantity": "number",
        "unit_price": "number",
        "amount": "number"
      }
    ]
  },
  "mode": "auto",
  "llmProvider": "openai",
  "fallbackToRules": true,
  "includeSourceText": false,
  "maxCharsPerDocument": 120000,
  "maxConcurrency": 3
}
```

# Actor output Schema

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

One item per document: extracted data, validation result, completeness, errors.

## `output` (type: `string`):

Just the extracted JSON: an object for one document, an array for several.

# 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 = {
    "text": `ACME Supplies Inc.
Phone: (217) 555-0142   Email: billing@acme-supplies.com

Invoice #: INV-2026-0042
Invoice Date: Sept 28, 2026
Due Date: 10/28/2026

Bill To:
Birch & Co. LLC
77 Oak Street, Portland, OR 97205

Description              Qty    Unit Price     Amount
Steel brackets (box)       4       $12.50      $50.00
Hex bolts M8 x 100       200        $0.15      $30.00

Subtotal: $80.00
Tax (8%): $6.40
Total Due: $86.40`,
    "schema": {
        "vendor_name": "string",
        "invoice_number": "string",
        "invoice_date": "date",
        "due_date": "date",
        "customer": "string",
        "vendor_email": "email",
        "subtotal": "number",
        "tax": "number",
        "total": "number",
        "line_items": [
            {
                "description": "string",
                "quantity": "number",
                "unit_price": "number",
                "amount": "number"
            }
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("birchwood-data/text-to-data").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 = {
    "text": """ACME Supplies Inc.
Phone: (217) 555-0142   Email: billing@acme-supplies.com

Invoice #: INV-2026-0042
Invoice Date: Sept 28, 2026
Due Date: 10/28/2026

Bill To:
Birch & Co. LLC
77 Oak Street, Portland, OR 97205

Description              Qty    Unit Price     Amount
Steel brackets (box)       4       $12.50      $50.00
Hex bolts M8 x 100       200        $0.15      $30.00

Subtotal: $80.00
Tax (8%): $6.40
Total Due: $86.40""",
    "schema": {
        "vendor_name": "string",
        "invoice_number": "string",
        "invoice_date": "date",
        "due_date": "date",
        "customer": "string",
        "vendor_email": "email",
        "subtotal": "number",
        "tax": "number",
        "total": "number",
        "line_items": [{
                "description": "string",
                "quantity": "number",
                "unit_price": "number",
                "amount": "number",
            }],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("birchwood-data/text-to-data").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 '{
  "text": "ACME Supplies Inc.\\nPhone: (217) 555-0142   Email: billing@acme-supplies.com\\n\\nInvoice #: INV-2026-0042\\nInvoice Date: Sept 28, 2026\\nDue Date: 10/28/2026\\n\\nBill To:\\nBirch & Co. LLC\\n77 Oak Street, Portland, OR 97205\\n\\nDescription              Qty    Unit Price     Amount\\nSteel brackets (box)       4       $12.50      $50.00\\nHex bolts M8 x 100       200        $0.15      $30.00\\n\\nSubtotal: $80.00\\nTax (8%): $6.40\\nTotal Due: $86.40",
  "schema": {
    "vendor_name": "string",
    "invoice_number": "string",
    "invoice_date": "date",
    "due_date": "date",
    "customer": "string",
    "vendor_email": "email",
    "subtotal": "number",
    "tax": "number",
    "total": "number",
    "line_items": [
      {
        "description": "string",
        "quantity": "number",
        "unit_price": "number",
        "amount": "number"
      }
    ]
  }
}' |
apify call birchwood-data/text-to-data --silent --output-dataset

```

## MCP server setup

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

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/yD38z5alT9T4ZaVlS/builds/JKZoACoPBndXA9KDd/openapi.json
