# PDF Table Extractor - PDF to Excel, CSV & JSON (validated rows) (`first_watch/pdf-table-extraction`) Actor

Extract tables from PDF price lists, invoices and order confirmations into clean rows for Excel, CSV or JSON. Maps your columns, reads EU and US numbers, checks totals and duplicates, returns rejected rows with reasons. Pay only for accepted rows.

- **URL**: https://apify.com/first\_watch/pdf-table-extraction.md
- **Developed by:** [Jordan Nabbe](https://apify.com/first_watch) (community)
- **Categories:** Automation, Business, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 accepted rows

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

## PDF Table to Validated Rows (CSV, Excel, JSON)

Turn the table in a supplier PDF (price lists, order confirmations, stock reports, statements) into clean spreadsheet rows that you can import without re-checking them by hand. You describe the columns you expect once; every run finds the table, maps header variants such as `Qty`, `QTY` or `Aantal` to your names, converts European and US numbers and dates, and checks required fields, duplicates and line totals. Rows that pass go to the dataset. Rows that fail are listed with the reason, the page and the row number. You only pay for rows that passed.

Built for operations, purchasing and finance teams and for the people who automate their imports (Make, n8n, Zapier, scripts), when the same kind of PDF arrives every week or month.

### Try it in one click

Press **Start**. The prefilled input contains a real one-page supplier price list PDF. The result shows five accepted rows. It also shows one rejected row: the supplier's line total is wrong (3 × 42.00 was printed as 162.00). A wrapped two-line description is joined back into one cell. Then replace the sample PDF with your own and adjust the columns.

### What you get

Each accepted row is one flat dataset item with your column names first, ready for CSV or Excel export:

| sku | description | quantity | unit\_price | line\_total | sourceDocument | sourcePage | sourceRow |
|---|---|---|---|---|---|---|---|
| HC-2032 | Hose clamp 20-32 mm, stainless | 50 | 0.84 | 42 | northwind-price-list-2026-10 | 1 | 1 |
| PG-0010 | Pressure gauge 0-10 bar, glycerine filled | 4 | 18.9 | 75.6 | northwind-price-list-2026-10 | 1 | 2 |

Every row also carries `extraction`: how it was read (`pdf-text-layer`, `csv`, …), how many text cells were found, how many fragments were joined, how many wrapped lines were merged and how many cells straddled a column boundary. These are measured facts about the extraction, not a guess at accuracy. Rows with merged or boundary-straddling cells are the ones to spot-check.

The `OUTPUT` record holds:

- `outcome`: `SUCCEEDED`, `PARTIAL` (some rows or documents failed), `FAILED` (nothing usable; the run also ends as failed) or `LIMIT_REACHED`, plus counts;
- `documents`: per document the header mapping, warnings, the detected number and date notation and an extraction report (pages read, repeated headers skipped, lines that could not be assigned to any column, with samples);
- `rejectedRows`: every rejected row with its values and error codes (`MISSING_REQUIRED_FIELD`, `INVALID_TYPE`, `AMBIGUOUS_NUMBER`, `AMBIGUOUS_DATE`, `DUPLICATE_KEY`, `ARITHMETIC_CHECK_FAILED`, `UNEXPECTED_COLUMN`).

### How to use it with your own files

1. Put each PDF in `documents`: `{ "id": "march-prices", "base64Document": "<base64>" }` or `{ "id": "march-prices", "url": "https://…/march.pdf" }`. Tables you already have can be passed as `csv`, `text`, `json`, `html` or `records`.
2. List the columns you want in `targetSchema.columns`: `name`, `type` (`string`, `number`, `integer`, `boolean`, `date`), `required` and the header spellings your suppliers use in `aliases`.
3. Optional checks: `uniqueBy` (for example the SKU) and `arithmeticChecks` (for example quantity × unit price = line total, with a tolerance).
4. Run, then export the dataset as CSV/Excel or read it through the API.

Numbers such as `1,234.56`, `1.234,56`, `€ 12,50`, `12.50 EUR`, `(45.00)` and `45,00-` are read automatically. The notation is decided per document from values that can only be read one way. A value like `1.250` that could mean 1.25 or 1250 is never guessed. It is rejected as `AMBIGUOUS_NUMBER` unless the document shows the notation elsewhere or you set `options.numberFormat` to `en` or `eu`. Dates work the same way: `25-10-2026`, `10/25/2026`, `03.10.2026`, `3 Oct 2026` and `2026-10-03` all become `2026-10-03`. A date like `03/10/2026` is only accepted when the document or `options.dateFormat` settles the order.

Strict mode (`options.failOnAnyRejectedRow: true`) delivers nothing unless every row passes, for imports that must be all-or-nothing.

### Automate it

Call the Actor from Make, n8n, Zapier or a script with the Apify endpoint `run-sync-get-dataset-items`: it waits for the run and returns the accepted rows. Minimal Python (standard library only):

```python
import base64, json, os, urllib.request
body = {"documents": [{"id": "march", "base64Document": base64.b64encode(open("march.pdf", "rb").read()).decode()}],
        "targetSchema": {"columns": [{"name": "sku", "type": "string", "required": True, "aliases": ["SKU"]},
                                     {"name": "line_total", "type": "number", "required": True, "aliases": ["Total"]}]}}
req = urllib.request.Request("https://api.apify.com/v2/acts/first_watch~pdf-table-extraction/run-sync-get-dataset-items?maxTotalChargeUsd=1",
                             data=json.dumps(body).encode(), method="POST",
                             headers={"Authorization": "Bearer " + os.environ["APIFY_TOKEN"], "Content-Type": "application/json"})
rows = json.load(urllib.request.urlopen(req, timeout=330))
```

`maxTotalChargeUsd` caps what one run can cost. Keep the same `targetSchema` per supplier so the column mapping stays stable when their headers change slightly.

### Pricing

Pay per event. The main charge is per accepted table row; everything else listed below is free.

| Event | Price (USD) | Charged when |
|---|---|---|
| Run start (`apify-actor-start`) | $0.005 | once per run; the default 512 MB memory is one event |
| Accepted row (`apify-default-dataset-item`) | $0.002 | per accepted row delivered to the dataset |

Not charged:

- rejected rows with their error codes (in the OUTPUT record)
- documents that could not be read (corrupt, scanned, encrypted, no matching table)
- the run summary, header mappings and extraction-quality report
- rows withheld because strict mode found a rejected row

Examples (default 512 MB memory, one start event):

- One supplier price list with 40 accepted rows: $0.005 start + 40 × $0.002 = **$0.085**
- Monthly batch of 12 PDFs, 600 accepted rows: $0.005 start + 600 × $0.002 = **$1.205**
- A scanned PDF that cannot be read: $0.005 start + 0 × $0.002 = **$0.005**

Spending limit: when you set a maximum cost per run, the Actor stops before it would exceed it, keeps every accepted table row it already delivered and reports `limitReached` in the `OUTPUT` record. It never delivers results beyond the limit.

The Store's Pricing tab shows the prices in force. If this section and the Pricing tab ever differ, the Pricing tab applies.

### Limits (read these before relying on it)

- Only PDFs with a text layer. Scanned or photographed PDFs are refused with `SCANNED_PDF_REQUIRES_OCR`. They are not charged. There is no OCR.
- One table per document: the header line that best matches your columns. Rows below it are read on every page. Repeated headers on later pages are skipped.
- Headers must be on one line. Multi-line headers such as "Unit" above "price" are not recognised.
- Columns are assigned by position under the header. Unusual layouts (merged cells, sub-totals inside the table, two tables side by side) can put a value in the wrong column. The schema checks usually catch this as a rejected row. Review the first runs of a new supplier template.
- A line with text only in free-text columns directly below a row is treated as a wrapped cell of that row and counted in `continuationLines`.
- Password-protected and truncated PDFs are refused.
- Up to 50 documents per run, 250 pages and 5,000 rows per document, 10 MiB per inline document and 25 MiB per downloaded PDF. Downloads are HTTPS only, public addresses only, at most 3 redirects and 30 seconds each.

### FAQ

**Is my data stored or logged?** Only in your own Apify run storage. Logs contain counts and status codes, never cell values.

**What happens if one PDF fails?** It is reported in `documents` with an error code and is not charged. The other documents are still processed.

**Why was a row rejected that looks fine?** Check `rejectedRows[].errors`. Common causes are an ambiguous number or date (set `numberFormat` or `dateFormat`), or a column the schema does not list (add an alias or set `allowUnexpectedColumns`).

**Can I keep extra columns?** Yes. Set `targetSchema.allowUnexpectedColumns` to `true` and they are passed through under their header text.

# Actor input Schema

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

1-50 documents. Each needs a unique 'id' and exactly one source: 'base64Document' (PDF bytes as base64; a data: URL prefix or line breaks are fine), 'url' (public https:// link to a PDF), 'keyValueStoreKey' (PDF record in the run's default key-value store), or inline 'records', 'csv', 'json', 'html' (first table) or 'text' (tab, pipe, semicolon or comma separated). Optional: 'delimiter' for csv/text and 'pageRange' {from: 1, to: 3} for PDFs. Inline and base64 sources are processed offline.

## `targetSchema` (type: `object`):

Canonical columns every row must satisfy. 'columns': list of {name, type (string, number, integer, boolean, or date as YYYY-MM-DD), required, aliases}. Optional: 'uniqueBy' (column names that must be unique together), 'arithmeticChecks' (list of {operands, operator: sum, subtract or multiply, result, tolerance (default 0.01)}) and 'allowUnexpectedColumns' (default false: rows with extra columns are rejected).

## `options` (type: `object`):

caseInsensitiveHeaders (default true), trimValues (default true), emptyStringIsNull (default true), failOnAnyRejectedRow (default false; true = all-or-nothing: nothing is delivered or charged if any row is rejected), numberFormat ('auto', 'en' for 1,234.56 or 'eu' for 1.234,56; default auto), dateFormat ('auto', 'DMY', 'MDY' or 'YMD'; default auto) and ocrAllowed (must stay false; there is no OCR).

## Actor input object example

```json
{
  "documents": [
    {
      "id": "northwind-price-list-2026-10",
      "base64Document": "JVBERi0xLjQKJeLjz9MKMSAwIG9iago8PCAvVHlwZSAvRm9udCAvU3VidHlwZSAvVHlwZTEgL0Jhc2VGb250IC9IZWx2ZXRpY2EgL0VuY29kaW5nIC9XaW5BbnNpRW5jb2RpbmcgPj4KZW5kb2JqCjIgMCBvYmoKPDwgL0xlbmd0aCAxODYyID4+CnN0cmVhbQpCVCAvRjEgMTIgVGYgNDAuMDAgODEwLjAwIFRkIChOb3J0aHdpbmQgSW5kdXN0cmlhbCAtIHByaWNlIGxpc3QgT2N0b2JlciAyMDI2KSBUaiBFVApCVCAvRjEgOSBUZiA0MC4wMCA3ODAuMDAgVGQgKFNLVSkgVGogRVQKQlQgL0YxIDkgVGYgMTEwLjAwIDc4MC4wMCBUZCAoRGVzY3JpcHRpb24pIFRqIEVUCkJUIC9GMSA5IFRmIDM0MS4wMCA3ODAuMDAgVGQgKFF0eSkgVGogRVQKQlQgL0YxIDkgVGYgNDMxLjk5IDc4MC4wMCBUZCAoVW5pdCBwcmljZSkgVGogRVQKQlQgL0YxIDkgVGYgNTIzLjQ4IDc4MC4wMCBUZCAoTGluZSB0b3RhbCkgVGogRVQKQlQgL0YxIDkgVGYgNDAuMDAgNzYwLjAwIFRkIChIQy0yMDMyKSBUaiBFVApCVCAvRjEgOSBUZiAxMTAuMDAgNzYwLjAwIFRkIChIb3NlIGNsYW1wIDIwLTMyIG1tLCBzdGFpbmxlc3MpIFRqIEVUCkJUIC9GMSA5IFRmIDM0NC45OSA3NjAuMDAgVGQgKDUwKSBUaiBFVApCVCAvRjEgOSBUZiA0NTIuNDkgNzYwLjAwIFRkICgwLjg0KSBUaiBFVApCVCAvRjEgOSBUZiA1MzcuNDggNzYwLjAwIFRkICg0Mi4wMCkgVGogRVQKQlQgL0YxIDkgVGYgNDAuMDAgNzQwLjAwIFRkIChQRy0wMDEwKSBUaiBFVApCVCAvRjEgOSBUZiAxMTAuMDAgNzQwLjAwIFRkIChQcmVzc3VyZSBnYXVnZSAwLTEwIGJhciwpIFRqIEVUCkJUIC9GMSA5IFRmIDExMC4wMCA3MjkuNTAgVGQgKGdseWNlcmluZSBmaWxsZWQpIFRqIEVUCkJUIC9GMSA5IFRmIDM1MC4wMCA3NDAuMDAgVGQgKDQpIFRqIEVUCkJUIC9GMSA5IFRmIDQ0Ny40OCA3NDAuMDAgVGQgKDE4LjkwKSBUaiBFVApCVCAvRjEgOSBUZiA1MzcuNDggNzQwLjAwIFRkICg3NS42MCkgVGogRVQKQlQgL0YxIDkgVGYgNDAuMDAgNzA5LjUwIFRkIChCVi0wMDE1KSBUaiBFVApCVCAvRjEgOSBUZiAxMTAuMDAgNzA5LjUwIFRkIChCYWxsIHZhbHZlIDEvMiBpbmNoLCBicmFzcykgVGogRVQKQlQgL0YxIDkgVGYgMzQ0Ljk5IDcwOS41MCBUZCAoMTIpIFRqIEVUCkJUIC9GMSA5IFRmIDQ1Mi40OSA3MDkuNTAgVGQgKDcuNDUpIFRqIEVUCkJUIC9GMSA5IFRmIDUzNy40OCA3MDkuNTAgVGQgKDg5LjQwKSBUaiBFVApCVCAvRjEgOSBUZiA0MC4wMCA2ODkuNTAgVGQgKFNLLTAyMDApIFRqIEVUCkJUIC9GMSA5IFRmIDExMC4wMCA2ODkuNTAgVGQgKFNlYWwga2l0IGZvciBwdW1wIHNlcmllcyAyMDApIFRqIEVUCkJUIC9GMSA5IFRmIDM1MC4wMCA2ODkuNTAgVGQgKDMpIFRqIEVUCkJUIC9GMSA5IFRmIDQ0Ny40OCA2ODkuNTAgVGQgKDQyLjAwKSBUaiBFVApCVCAvRjEgOSBUZiA1MzIuNDggNjg5LjUwIFRkICgxNjIuMDApIFRqIEVUCkJUIC9GMSA5IFRmIDQwLjAwIDY2OS41MCBUZCAoRkwtMDU1MCkgVGogRVQKQlQgL0YxIDkgVGYgMTEwLjAwIDY2OS41MCBUZCAoRmlsdGVyIGNhcnRyaWRnZSA1IG1pY3JvbikgVGogRVQKQlQgL0YxIDkgVGYgMzQ0Ljk5IDY2OS41MCBUZCAoMjApIFRqIEVUCkJUIC9GMSA5IFRmIDQ1Mi40OSA2NjkuNTAgVGQgKDYuMTUpIFRqIEVUCkJUIC9GMSA5IFRmIDUzMi40OCA2NjkuNTAgVGQgKDEyMy4wMCkgVGogRVQKQlQgL0YxIDkgVGYgNDAuMDAgNjQ5LjUwIFRkIChQVC0xMjUwKSBUaiBFVApCVCAvRjEgOSBUZiAxMTAuMDAgNjQ5LjUwIFRkIChQdW1wIGltcGVsbGVyIDEyNSBtbSkgVGogRVQKQlQgL0YxIDkgVGYgMzUwLjAwIDY0OS41MCBUZCAoMSkgVGogRVQKQlQgL0YxIDkgVGYgNDM0Ljk3IDY0OS41MCBUZCAoMSwyNDkuMDApIFRqIEVUCkJUIC9GMSA5IFRmIDUyNC45NyA2NDkuNTAgVGQgKDEsMjQ5LjAwKSBUaiBFVApCVCAvRjEgOCBUZiA0MC4wMCA2MDkuNTAgVGQgKFByaWNlcyBpbiBFVVIgZXhjbHVkaW5nIFZBVC4gVmFsaWQgdW50aWwgMzEgRGVjZW1iZXIgMjAyNi4pIFRqIEVUCmVuZHN0cmVhbQplbmRvYmoKMyAwIG9iago8PCAvVHlwZSAvUGFnZSAvUGFyZW50IDQgMCBSIC9NZWRpYUJveCBbMCAwIDU5NSA4NDJdIC9Db250ZW50cyAyIDAgUiAvUmVzb3VyY2VzIDw8IC9Gb250IDw8IC9GMSAxIDAgUiA+PiA+PiA+PgplbmRvYmoKNCAwIG9iago8PCAvVHlwZSAvUGFnZXMgL0tpZHMgWzMgMCBSXSAvQ291bnQgMSA+PgplbmRvYmoKNSAwIG9iago8PCAvVHlwZSAvQ2F0YWxvZyAvUGFnZXMgNCAwIFIgPj4KZW5kb2JqCnhyZWYKMCA2CjAwMDAwMDAwMDAgNjU1MzUgZiAKMDAwMDAwMDAxNSAwMDAwMCBuIAowMDAwMDAwMTEyIDAwMDAwIG4gCjAwMDAwMDIwMjUgMDAwMDAgbiAKMDAwMDAwMjE1MSAwMDAwMCBuIAowMDAwMDAyMjA4IDAwMDAwIG4gCnRyYWlsZXIKPDwgL1NpemUgNiAvUm9vdCA1IDAgUiA+PgpzdGFydHhyZWYKMjI1NwolJUVPRgo="
    }
  ],
  "targetSchema": {
    "columns": [
      {
        "name": "sku",
        "type": "string",
        "required": true,
        "aliases": [
          "SKU",
          "Item Code",
          "Artikel",
          "Art.nr."
        ]
      },
      {
        "name": "description",
        "type": "string",
        "required": true,
        "aliases": [
          "Description",
          "Omschrijving"
        ]
      },
      {
        "name": "quantity",
        "type": "integer",
        "required": true,
        "aliases": [
          "Qty",
          "QTY",
          "Aantal"
        ]
      },
      {
        "name": "unit_price",
        "type": "number",
        "required": true,
        "aliases": [
          "Unit price",
          "Unit Price",
          "Prijs"
        ]
      },
      {
        "name": "line_total",
        "type": "number",
        "required": true,
        "aliases": [
          "Line total",
          "Total",
          "Totaal"
        ]
      }
    ],
    "uniqueBy": [
      "sku"
    ],
    "allowUnexpectedColumns": false,
    "arithmeticChecks": [
      {
        "operands": [
          "quantity",
          "unit_price"
        ],
        "operator": "multiply",
        "result": "line_total",
        "tolerance": 0.01
      }
    ]
  },
  "options": {
    "caseInsensitiveHeaders": true,
    "trimValues": true,
    "emptyStringIsNull": true,
    "failOnAnyRejectedRow": false,
    "numberFormat": "auto",
    "dateFormat": "auto"
  }
}
```

# Actor output Schema

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

Every result row stored in the default dataset.

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

Summary written to the OUTPUT record.

# 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": [
        {
            "id": "northwind-price-list-2026-10",
            "base64Document": "JVBERi0xLjQKJeLjz9MKMSAwIG9iago8PCAvVHlwZSAvRm9udCAvU3VidHlwZSAvVHlwZTEgL0Jhc2VGb250IC9IZWx2ZXRpY2EgL0VuY29kaW5nIC9XaW5BbnNpRW5jb2RpbmcgPj4KZW5kb2JqCjIgMCBvYmoKPDwgL0xlbmd0aCAxODYyID4+CnN0cmVhbQpCVCAvRjEgMTIgVGYgNDAuMDAgODEwLjAwIFRkIChOb3J0aHdpbmQgSW5kdXN0cmlhbCAtIHByaWNlIGxpc3QgT2N0b2JlciAyMDI2KSBUaiBFVApCVCAvRjEgOSBUZiA0MC4wMCA3ODAuMDAgVGQgKFNLVSkgVGogRVQKQlQgL0YxIDkgVGYgMTEwLjAwIDc4MC4wMCBUZCAoRGVzY3JpcHRpb24pIFRqIEVUCkJUIC9GMSA5IFRmIDM0MS4wMCA3ODAuMDAgVGQgKFF0eSkgVGogRVQKQlQgL0YxIDkgVGYgNDMxLjk5IDc4MC4wMCBUZCAoVW5pdCBwcmljZSkgVGogRVQKQlQgL0YxIDkgVGYgNTIzLjQ4IDc4MC4wMCBUZCAoTGluZSB0b3RhbCkgVGogRVQKQlQgL0YxIDkgVGYgNDAuMDAgNzYwLjAwIFRkIChIQy0yMDMyKSBUaiBFVApCVCAvRjEgOSBUZiAxMTAuMDAgNzYwLjAwIFRkIChIb3NlIGNsYW1wIDIwLTMyIG1tLCBzdGFpbmxlc3MpIFRqIEVUCkJUIC9GMSA5IFRmIDM0NC45OSA3NjAuMDAgVGQgKDUwKSBUaiBFVApCVCAvRjEgOSBUZiA0NTIuNDkgNzYwLjAwIFRkICgwLjg0KSBUaiBFVApCVCAvRjEgOSBUZiA1MzcuNDggNzYwLjAwIFRkICg0Mi4wMCkgVGogRVQKQlQgL0YxIDkgVGYgNDAuMDAgNzQwLjAwIFRkIChQRy0wMDEwKSBUaiBFVApCVCAvRjEgOSBUZiAxMTAuMDAgNzQwLjAwIFRkIChQcmVzc3VyZSBnYXVnZSAwLTEwIGJhciwpIFRqIEVUCkJUIC9GMSA5IFRmIDExMC4wMCA3MjkuNTAgVGQgKGdseWNlcmluZSBmaWxsZWQpIFRqIEVUCkJUIC9GMSA5IFRmIDM1MC4wMCA3NDAuMDAgVGQgKDQpIFRqIEVUCkJUIC9GMSA5IFRmIDQ0Ny40OCA3NDAuMDAgVGQgKDE4LjkwKSBUaiBFVApCVCAvRjEgOSBUZiA1MzcuNDggNzQwLjAwIFRkICg3NS42MCkgVGogRVQKQlQgL0YxIDkgVGYgNDAuMDAgNzA5LjUwIFRkIChCVi0wMDE1KSBUaiBFVApCVCAvRjEgOSBUZiAxMTAuMDAgNzA5LjUwIFRkIChCYWxsIHZhbHZlIDEvMiBpbmNoLCBicmFzcykgVGogRVQKQlQgL0YxIDkgVGYgMzQ0Ljk5IDcwOS41MCBUZCAoMTIpIFRqIEVUCkJUIC9GMSA5IFRmIDQ1Mi40OSA3MDkuNTAgVGQgKDcuNDUpIFRqIEVUCkJUIC9GMSA5IFRmIDUzNy40OCA3MDkuNTAgVGQgKDg5LjQwKSBUaiBFVApCVCAvRjEgOSBUZiA0MC4wMCA2ODkuNTAgVGQgKFNLLTAyMDApIFRqIEVUCkJUIC9GMSA5IFRmIDExMC4wMCA2ODkuNTAgVGQgKFNlYWwga2l0IGZvciBwdW1wIHNlcmllcyAyMDApIFRqIEVUCkJUIC9GMSA5IFRmIDM1MC4wMCA2ODkuNTAgVGQgKDMpIFRqIEVUCkJUIC9GMSA5IFRmIDQ0Ny40OCA2ODkuNTAgVGQgKDQyLjAwKSBUaiBFVApCVCAvRjEgOSBUZiA1MzIuNDggNjg5LjUwIFRkICgxNjIuMDApIFRqIEVUCkJUIC9GMSA5IFRmIDQwLjAwIDY2OS41MCBUZCAoRkwtMDU1MCkgVGogRVQKQlQgL0YxIDkgVGYgMTEwLjAwIDY2OS41MCBUZCAoRmlsdGVyIGNhcnRyaWRnZSA1IG1pY3JvbikgVGogRVQKQlQgL0YxIDkgVGYgMzQ0Ljk5IDY2OS41MCBUZCAoMjApIFRqIEVUCkJUIC9GMSA5IFRmIDQ1Mi40OSA2NjkuNTAgVGQgKDYuMTUpIFRqIEVUCkJUIC9GMSA5IFRmIDUzMi40OCA2NjkuNTAgVGQgKDEyMy4wMCkgVGogRVQKQlQgL0YxIDkgVGYgNDAuMDAgNjQ5LjUwIFRkIChQVC0xMjUwKSBUaiBFVApCVCAvRjEgOSBUZiAxMTAuMDAgNjQ5LjUwIFRkIChQdW1wIGltcGVsbGVyIDEyNSBtbSkgVGogRVQKQlQgL0YxIDkgVGYgMzUwLjAwIDY0OS41MCBUZCAoMSkgVGogRVQKQlQgL0YxIDkgVGYgNDM0Ljk3IDY0OS41MCBUZCAoMSwyNDkuMDApIFRqIEVUCkJUIC9GMSA5IFRmIDUyNC45NyA2NDkuNTAgVGQgKDEsMjQ5LjAwKSBUaiBFVApCVCAvRjEgOCBUZiA0MC4wMCA2MDkuNTAgVGQgKFByaWNlcyBpbiBFVVIgZXhjbHVkaW5nIFZBVC4gVmFsaWQgdW50aWwgMzEgRGVjZW1iZXIgMjAyNi4pIFRqIEVUCmVuZHN0cmVhbQplbmRvYmoKMyAwIG9iago8PCAvVHlwZSAvUGFnZSAvUGFyZW50IDQgMCBSIC9NZWRpYUJveCBbMCAwIDU5NSA4NDJdIC9Db250ZW50cyAyIDAgUiAvUmVzb3VyY2VzIDw8IC9Gb250IDw8IC9GMSAxIDAgUiA+PiA+PiA+PgplbmRvYmoKNCAwIG9iago8PCAvVHlwZSAvUGFnZXMgL0tpZHMgWzMgMCBSXSAvQ291bnQgMSA+PgplbmRvYmoKNSAwIG9iago8PCAvVHlwZSAvQ2F0YWxvZyAvUGFnZXMgNCAwIFIgPj4KZW5kb2JqCnhyZWYKMCA2CjAwMDAwMDAwMDAgNjU1MzUgZiAKMDAwMDAwMDAxNSAwMDAwMCBuIAowMDAwMDAwMTEyIDAwMDAwIG4gCjAwMDAwMDIwMjUgMDAwMDAgbiAKMDAwMDAwMjE1MSAwMDAwMCBuIAowMDAwMDAyMjA4IDAwMDAwIG4gCnRyYWlsZXIKPDwgL1NpemUgNiAvUm9vdCA1IDAgUiA+PgpzdGFydHhyZWYKMjI1NwolJUVPRgo="
        }
    ],
    "targetSchema": {
        "columns": [
            {
                "name": "sku",
                "type": "string",
                "required": true,
                "aliases": [
                    "SKU",
                    "Item Code",
                    "Artikel",
                    "Art.nr."
                ]
            },
            {
                "name": "description",
                "type": "string",
                "required": true,
                "aliases": [
                    "Description",
                    "Omschrijving"
                ]
            },
            {
                "name": "quantity",
                "type": "integer",
                "required": true,
                "aliases": [
                    "Qty",
                    "QTY",
                    "Aantal"
                ]
            },
            {
                "name": "unit_price",
                "type": "number",
                "required": true,
                "aliases": [
                    "Unit price",
                    "Unit Price",
                    "Prijs"
                ]
            },
            {
                "name": "line_total",
                "type": "number",
                "required": true,
                "aliases": [
                    "Line total",
                    "Total",
                    "Totaal"
                ]
            }
        ],
        "uniqueBy": [
            "sku"
        ],
        "allowUnexpectedColumns": false,
        "arithmeticChecks": [
            {
                "operands": [
                    "quantity",
                    "unit_price"
                ],
                "operator": "multiply",
                "result": "line_total",
                "tolerance": 0.01
            }
        ]
    },
    "options": {
        "caseInsensitiveHeaders": true,
        "trimValues": true,
        "emptyStringIsNull": true,
        "failOnAnyRejectedRow": false,
        "numberFormat": "auto",
        "dateFormat": "auto"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("first_watch/pdf-table-extraction").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": [{
            "id": "northwind-price-list-2026-10",
            "base64Document": "JVBERi0xLjQKJeLjz9MKMSAwIG9iago8PCAvVHlwZSAvRm9udCAvU3VidHlwZSAvVHlwZTEgL0Jhc2VGb250IC9IZWx2ZXRpY2EgL0VuY29kaW5nIC9XaW5BbnNpRW5jb2RpbmcgPj4KZW5kb2JqCjIgMCBvYmoKPDwgL0xlbmd0aCAxODYyID4+CnN0cmVhbQpCVCAvRjEgMTIgVGYgNDAuMDAgODEwLjAwIFRkIChOb3J0aHdpbmQgSW5kdXN0cmlhbCAtIHByaWNlIGxpc3QgT2N0b2JlciAyMDI2KSBUaiBFVApCVCAvRjEgOSBUZiA0MC4wMCA3ODAuMDAgVGQgKFNLVSkgVGogRVQKQlQgL0YxIDkgVGYgMTEwLjAwIDc4MC4wMCBUZCAoRGVzY3JpcHRpb24pIFRqIEVUCkJUIC9GMSA5IFRmIDM0MS4wMCA3ODAuMDAgVGQgKFF0eSkgVGogRVQKQlQgL0YxIDkgVGYgNDMxLjk5IDc4MC4wMCBUZCAoVW5pdCBwcmljZSkgVGogRVQKQlQgL0YxIDkgVGYgNTIzLjQ4IDc4MC4wMCBUZCAoTGluZSB0b3RhbCkgVGogRVQKQlQgL0YxIDkgVGYgNDAuMDAgNzYwLjAwIFRkIChIQy0yMDMyKSBUaiBFVApCVCAvRjEgOSBUZiAxMTAuMDAgNzYwLjAwIFRkIChIb3NlIGNsYW1wIDIwLTMyIG1tLCBzdGFpbmxlc3MpIFRqIEVUCkJUIC9GMSA5IFRmIDM0NC45OSA3NjAuMDAgVGQgKDUwKSBUaiBFVApCVCAvRjEgOSBUZiA0NTIuNDkgNzYwLjAwIFRkICgwLjg0KSBUaiBFVApCVCAvRjEgOSBUZiA1MzcuNDggNzYwLjAwIFRkICg0Mi4wMCkgVGogRVQKQlQgL0YxIDkgVGYgNDAuMDAgNzQwLjAwIFRkIChQRy0wMDEwKSBUaiBFVApCVCAvRjEgOSBUZiAxMTAuMDAgNzQwLjAwIFRkIChQcmVzc3VyZSBnYXVnZSAwLTEwIGJhciwpIFRqIEVUCkJUIC9GMSA5IFRmIDExMC4wMCA3MjkuNTAgVGQgKGdseWNlcmluZSBmaWxsZWQpIFRqIEVUCkJUIC9GMSA5IFRmIDM1MC4wMCA3NDAuMDAgVGQgKDQpIFRqIEVUCkJUIC9GMSA5IFRmIDQ0Ny40OCA3NDAuMDAgVGQgKDE4LjkwKSBUaiBFVApCVCAvRjEgOSBUZiA1MzcuNDggNzQwLjAwIFRkICg3NS42MCkgVGogRVQKQlQgL0YxIDkgVGYgNDAuMDAgNzA5LjUwIFRkIChCVi0wMDE1KSBUaiBFVApCVCAvRjEgOSBUZiAxMTAuMDAgNzA5LjUwIFRkIChCYWxsIHZhbHZlIDEvMiBpbmNoLCBicmFzcykgVGogRVQKQlQgL0YxIDkgVGYgMzQ0Ljk5IDcwOS41MCBUZCAoMTIpIFRqIEVUCkJUIC9GMSA5IFRmIDQ1Mi40OSA3MDkuNTAgVGQgKDcuNDUpIFRqIEVUCkJUIC9GMSA5IFRmIDUzNy40OCA3MDkuNTAgVGQgKDg5LjQwKSBUaiBFVApCVCAvRjEgOSBUZiA0MC4wMCA2ODkuNTAgVGQgKFNLLTAyMDApIFRqIEVUCkJUIC9GMSA5IFRmIDExMC4wMCA2ODkuNTAgVGQgKFNlYWwga2l0IGZvciBwdW1wIHNlcmllcyAyMDApIFRqIEVUCkJUIC9GMSA5IFRmIDM1MC4wMCA2ODkuNTAgVGQgKDMpIFRqIEVUCkJUIC9GMSA5IFRmIDQ0Ny40OCA2ODkuNTAgVGQgKDQyLjAwKSBUaiBFVApCVCAvRjEgOSBUZiA1MzIuNDggNjg5LjUwIFRkICgxNjIuMDApIFRqIEVUCkJUIC9GMSA5IFRmIDQwLjAwIDY2OS41MCBUZCAoRkwtMDU1MCkgVGogRVQKQlQgL0YxIDkgVGYgMTEwLjAwIDY2OS41MCBUZCAoRmlsdGVyIGNhcnRyaWRnZSA1IG1pY3JvbikgVGogRVQKQlQgL0YxIDkgVGYgMzQ0Ljk5IDY2OS41MCBUZCAoMjApIFRqIEVUCkJUIC9GMSA5IFRmIDQ1Mi40OSA2NjkuNTAgVGQgKDYuMTUpIFRqIEVUCkJUIC9GMSA5IFRmIDUzMi40OCA2NjkuNTAgVGQgKDEyMy4wMCkgVGogRVQKQlQgL0YxIDkgVGYgNDAuMDAgNjQ5LjUwIFRkIChQVC0xMjUwKSBUaiBFVApCVCAvRjEgOSBUZiAxMTAuMDAgNjQ5LjUwIFRkIChQdW1wIGltcGVsbGVyIDEyNSBtbSkgVGogRVQKQlQgL0YxIDkgVGYgMzUwLjAwIDY0OS41MCBUZCAoMSkgVGogRVQKQlQgL0YxIDkgVGYgNDM0Ljk3IDY0OS41MCBUZCAoMSwyNDkuMDApIFRqIEVUCkJUIC9GMSA5IFRmIDUyNC45NyA2NDkuNTAgVGQgKDEsMjQ5LjAwKSBUaiBFVApCVCAvRjEgOCBUZiA0MC4wMCA2MDkuNTAgVGQgKFByaWNlcyBpbiBFVVIgZXhjbHVkaW5nIFZBVC4gVmFsaWQgdW50aWwgMzEgRGVjZW1iZXIgMjAyNi4pIFRqIEVUCmVuZHN0cmVhbQplbmRvYmoKMyAwIG9iago8PCAvVHlwZSAvUGFnZSAvUGFyZW50IDQgMCBSIC9NZWRpYUJveCBbMCAwIDU5NSA4NDJdIC9Db250ZW50cyAyIDAgUiAvUmVzb3VyY2VzIDw8IC9Gb250IDw8IC9GMSAxIDAgUiA+PiA+PiA+PgplbmRvYmoKNCAwIG9iago8PCAvVHlwZSAvUGFnZXMgL0tpZHMgWzMgMCBSXSAvQ291bnQgMSA+PgplbmRvYmoKNSAwIG9iago8PCAvVHlwZSAvQ2F0YWxvZyAvUGFnZXMgNCAwIFIgPj4KZW5kb2JqCnhyZWYKMCA2CjAwMDAwMDAwMDAgNjU1MzUgZiAKMDAwMDAwMDAxNSAwMDAwMCBuIAowMDAwMDAwMTEyIDAwMDAwIG4gCjAwMDAwMDIwMjUgMDAwMDAgbiAKMDAwMDAwMjE1MSAwMDAwMCBuIAowMDAwMDAyMjA4IDAwMDAwIG4gCnRyYWlsZXIKPDwgL1NpemUgNiAvUm9vdCA1IDAgUiA+PgpzdGFydHhyZWYKMjI1NwolJUVPRgo=",
        }],
    "targetSchema": {
        "columns": [
            {
                "name": "sku",
                "type": "string",
                "required": True,
                "aliases": [
                    "SKU",
                    "Item Code",
                    "Artikel",
                    "Art.nr.",
                ],
            },
            {
                "name": "description",
                "type": "string",
                "required": True,
                "aliases": [
                    "Description",
                    "Omschrijving",
                ],
            },
            {
                "name": "quantity",
                "type": "integer",
                "required": True,
                "aliases": [
                    "Qty",
                    "QTY",
                    "Aantal",
                ],
            },
            {
                "name": "unit_price",
                "type": "number",
                "required": True,
                "aliases": [
                    "Unit price",
                    "Unit Price",
                    "Prijs",
                ],
            },
            {
                "name": "line_total",
                "type": "number",
                "required": True,
                "aliases": [
                    "Line total",
                    "Total",
                    "Totaal",
                ],
            },
        ],
        "uniqueBy": ["sku"],
        "allowUnexpectedColumns": False,
        "arithmeticChecks": [{
                "operands": [
                    "quantity",
                    "unit_price",
                ],
                "operator": "multiply",
                "result": "line_total",
                "tolerance": 0.01,
            }],
    },
    "options": {
        "caseInsensitiveHeaders": True,
        "trimValues": True,
        "emptyStringIsNull": True,
        "failOnAnyRejectedRow": False,
        "numberFormat": "auto",
        "dateFormat": "auto",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("first_watch/pdf-table-extraction").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": [
    {
      "id": "northwind-price-list-2026-10",
      "base64Document": "JVBERi0xLjQKJeLjz9MKMSAwIG9iago8PCAvVHlwZSAvRm9udCAvU3VidHlwZSAvVHlwZTEgL0Jhc2VGb250IC9IZWx2ZXRpY2EgL0VuY29kaW5nIC9XaW5BbnNpRW5jb2RpbmcgPj4KZW5kb2JqCjIgMCBvYmoKPDwgL0xlbmd0aCAxODYyID4+CnN0cmVhbQpCVCAvRjEgMTIgVGYgNDAuMDAgODEwLjAwIFRkIChOb3J0aHdpbmQgSW5kdXN0cmlhbCAtIHByaWNlIGxpc3QgT2N0b2JlciAyMDI2KSBUaiBFVApCVCAvRjEgOSBUZiA0MC4wMCA3ODAuMDAgVGQgKFNLVSkgVGogRVQKQlQgL0YxIDkgVGYgMTEwLjAwIDc4MC4wMCBUZCAoRGVzY3JpcHRpb24pIFRqIEVUCkJUIC9GMSA5IFRmIDM0MS4wMCA3ODAuMDAgVGQgKFF0eSkgVGogRVQKQlQgL0YxIDkgVGYgNDMxLjk5IDc4MC4wMCBUZCAoVW5pdCBwcmljZSkgVGogRVQKQlQgL0YxIDkgVGYgNTIzLjQ4IDc4MC4wMCBUZCAoTGluZSB0b3RhbCkgVGogRVQKQlQgL0YxIDkgVGYgNDAuMDAgNzYwLjAwIFRkIChIQy0yMDMyKSBUaiBFVApCVCAvRjEgOSBUZiAxMTAuMDAgNzYwLjAwIFRkIChIb3NlIGNsYW1wIDIwLTMyIG1tLCBzdGFpbmxlc3MpIFRqIEVUCkJUIC9GMSA5IFRmIDM0NC45OSA3NjAuMDAgVGQgKDUwKSBUaiBFVApCVCAvRjEgOSBUZiA0NTIuNDkgNzYwLjAwIFRkICgwLjg0KSBUaiBFVApCVCAvRjEgOSBUZiA1MzcuNDggNzYwLjAwIFRkICg0Mi4wMCkgVGogRVQKQlQgL0YxIDkgVGYgNDAuMDAgNzQwLjAwIFRkIChQRy0wMDEwKSBUaiBFVApCVCAvRjEgOSBUZiAxMTAuMDAgNzQwLjAwIFRkIChQcmVzc3VyZSBnYXVnZSAwLTEwIGJhciwpIFRqIEVUCkJUIC9GMSA5IFRmIDExMC4wMCA3MjkuNTAgVGQgKGdseWNlcmluZSBmaWxsZWQpIFRqIEVUCkJUIC9GMSA5IFRmIDM1MC4wMCA3NDAuMDAgVGQgKDQpIFRqIEVUCkJUIC9GMSA5IFRmIDQ0Ny40OCA3NDAuMDAgVGQgKDE4LjkwKSBUaiBFVApCVCAvRjEgOSBUZiA1MzcuNDggNzQwLjAwIFRkICg3NS42MCkgVGogRVQKQlQgL0YxIDkgVGYgNDAuMDAgNzA5LjUwIFRkIChCVi0wMDE1KSBUaiBFVApCVCAvRjEgOSBUZiAxMTAuMDAgNzA5LjUwIFRkIChCYWxsIHZhbHZlIDEvMiBpbmNoLCBicmFzcykgVGogRVQKQlQgL0YxIDkgVGYgMzQ0Ljk5IDcwOS41MCBUZCAoMTIpIFRqIEVUCkJUIC9GMSA5IFRmIDQ1Mi40OSA3MDkuNTAgVGQgKDcuNDUpIFRqIEVUCkJUIC9GMSA5IFRmIDUzNy40OCA3MDkuNTAgVGQgKDg5LjQwKSBUaiBFVApCVCAvRjEgOSBUZiA0MC4wMCA2ODkuNTAgVGQgKFNLLTAyMDApIFRqIEVUCkJUIC9GMSA5IFRmIDExMC4wMCA2ODkuNTAgVGQgKFNlYWwga2l0IGZvciBwdW1wIHNlcmllcyAyMDApIFRqIEVUCkJUIC9GMSA5IFRmIDM1MC4wMCA2ODkuNTAgVGQgKDMpIFRqIEVUCkJUIC9GMSA5IFRmIDQ0Ny40OCA2ODkuNTAgVGQgKDQyLjAwKSBUaiBFVApCVCAvRjEgOSBUZiA1MzIuNDggNjg5LjUwIFRkICgxNjIuMDApIFRqIEVUCkJUIC9GMSA5IFRmIDQwLjAwIDY2OS41MCBUZCAoRkwtMDU1MCkgVGogRVQKQlQgL0YxIDkgVGYgMTEwLjAwIDY2OS41MCBUZCAoRmlsdGVyIGNhcnRyaWRnZSA1IG1pY3JvbikgVGogRVQKQlQgL0YxIDkgVGYgMzQ0Ljk5IDY2OS41MCBUZCAoMjApIFRqIEVUCkJUIC9GMSA5IFRmIDQ1Mi40OSA2NjkuNTAgVGQgKDYuMTUpIFRqIEVUCkJUIC9GMSA5IFRmIDUzMi40OCA2NjkuNTAgVGQgKDEyMy4wMCkgVGogRVQKQlQgL0YxIDkgVGYgNDAuMDAgNjQ5LjUwIFRkIChQVC0xMjUwKSBUaiBFVApCVCAvRjEgOSBUZiAxMTAuMDAgNjQ5LjUwIFRkIChQdW1wIGltcGVsbGVyIDEyNSBtbSkgVGogRVQKQlQgL0YxIDkgVGYgMzUwLjAwIDY0OS41MCBUZCAoMSkgVGogRVQKQlQgL0YxIDkgVGYgNDM0Ljk3IDY0OS41MCBUZCAoMSwyNDkuMDApIFRqIEVUCkJUIC9GMSA5IFRmIDUyNC45NyA2NDkuNTAgVGQgKDEsMjQ5LjAwKSBUaiBFVApCVCAvRjEgOCBUZiA0MC4wMCA2MDkuNTAgVGQgKFByaWNlcyBpbiBFVVIgZXhjbHVkaW5nIFZBVC4gVmFsaWQgdW50aWwgMzEgRGVjZW1iZXIgMjAyNi4pIFRqIEVUCmVuZHN0cmVhbQplbmRvYmoKMyAwIG9iago8PCAvVHlwZSAvUGFnZSAvUGFyZW50IDQgMCBSIC9NZWRpYUJveCBbMCAwIDU5NSA4NDJdIC9Db250ZW50cyAyIDAgUiAvUmVzb3VyY2VzIDw8IC9Gb250IDw8IC9GMSAxIDAgUiA+PiA+PiA+PgplbmRvYmoKNCAwIG9iago8PCAvVHlwZSAvUGFnZXMgL0tpZHMgWzMgMCBSXSAvQ291bnQgMSA+PgplbmRvYmoKNSAwIG9iago8PCAvVHlwZSAvQ2F0YWxvZyAvUGFnZXMgNCAwIFIgPj4KZW5kb2JqCnhyZWYKMCA2CjAwMDAwMDAwMDAgNjU1MzUgZiAKMDAwMDAwMDAxNSAwMDAwMCBuIAowMDAwMDAwMTEyIDAwMDAwIG4gCjAwMDAwMDIwMjUgMDAwMDAgbiAKMDAwMDAwMjE1MSAwMDAwMCBuIAowMDAwMDAyMjA4IDAwMDAwIG4gCnRyYWlsZXIKPDwgL1NpemUgNiAvUm9vdCA1IDAgUiA+PgpzdGFydHhyZWYKMjI1NwolJUVPRgo="
    }
  ],
  "targetSchema": {
    "columns": [
      {
        "name": "sku",
        "type": "string",
        "required": true,
        "aliases": [
          "SKU",
          "Item Code",
          "Artikel",
          "Art.nr."
        ]
      },
      {
        "name": "description",
        "type": "string",
        "required": true,
        "aliases": [
          "Description",
          "Omschrijving"
        ]
      },
      {
        "name": "quantity",
        "type": "integer",
        "required": true,
        "aliases": [
          "Qty",
          "QTY",
          "Aantal"
        ]
      },
      {
        "name": "unit_price",
        "type": "number",
        "required": true,
        "aliases": [
          "Unit price",
          "Unit Price",
          "Prijs"
        ]
      },
      {
        "name": "line_total",
        "type": "number",
        "required": true,
        "aliases": [
          "Line total",
          "Total",
          "Totaal"
        ]
      }
    ],
    "uniqueBy": [
      "sku"
    ],
    "allowUnexpectedColumns": false,
    "arithmeticChecks": [
      {
        "operands": [
          "quantity",
          "unit_price"
        ],
        "operator": "multiply",
        "result": "line_total",
        "tolerance": 0.01
      }
    ]
  },
  "options": {
    "caseInsensitiveHeaders": true,
    "trimValues": true,
    "emptyStringIsNull": true,
    "failOnAnyRejectedRow": false,
    "numberFormat": "auto",
    "dateFormat": "auto"
  }
}' |
apify call first_watch/pdf-table-extraction --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,first_watch/pdf-table-extraction"
        }
    }
}
```

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/ieojV6gy84pXmx5OA/builds/AX4vJMpQbl6RG5B5c/openapi.json
