# PDF Form Filler API (Fill, Flatten & List Fields) (`jumblemog/pdf-form-filler`) Actor

Fill any fillable PDF form from field names or the labels printed on the page, flatten it, or list its fields with a numbered preview. Text, checkboxes, radio buttons, dropdowns.

- **URL**: https://apify.com/jumblemog/pdf-form-filler.md
- **Developed by:** [Jumble Mog](https://apify.com/jumblemog) (community)
- **Categories:** AI, Automation, Developer tools
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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 Form Filler API (Fill, Flatten & List Fields)

**Fill any fillable PDF form from a few values.** Give a link to the PDF and `{"Name": "Jane Example", "SSN": "123-45-6789"}` and you get the filled PDF back, flattened if you like, with a preview of each page. You don't need the form's internal field names (`topmostSubform[0].Page1[0].f1_01[0]`): use the label printed next to the box, the field's number from the preview, or its name.

- **List the fields** of any fillable PDF: number, internal name, type, the label printed next to it (or its tooltip), page, current value, options, length limit, required and read-only flags. A **preview image** draws every box with its number, and a ready-to-fill **template** lists every field with its current value.
- **Fill** text boxes, checkboxes, radio buttons, dropdowns and list boxes (several choices for a multi-select list). A value for a row of boxes that share one label, such as an SSN in 3 + 2 + 4 boxes or a phone number in three parts, is split across them.
- **Match values loosely**: the start of a label (`Address`), its words in any order, small typos (`busines name`), common abbreviations (SSN, EIN, DOB, ZIP, tel). A value that matches no field, or isn't one of the field's options, is reported with the reason and the closest fields, and the rest are still filled.
- **Flatten** the result so the values become part of the page and can't be edited.

It's made for:

- AI agents and assistants that fill tax, HR, insurance, school or government forms for people: an agent reads the field list once, then sends the values by label.
- Back offices and SaaS apps that turn database records or form submissions into completed PDFs (W-9, W-4, I-9, applications, claim forms, contracts with fields).
- Anyone who fills the same PDF form over and over and wants to do it from a spreadsheet, Zapier, Make or n8n.

![The IRS Form W-9 read by this Actor, left: its 23 fields drawn with their numbers (blue text boxes, orange checkboxes); right: the same form filled with a name, the individual/sole proprietor box, an address and an SSN split across the three SSN boxes, then flattened](https://api.apify.com/v2/key-value-stores/Km5b4PG3kYHdZSb1E/records/pdf-form-filler-example.png?signature=DYPt33I8V3uAC4rFxI7h)

### How to use it

1. Add **Requests**, one per PDF. A link on its own lists the form's fields; an object with `fields` fills it. The form has an example of each.
2. Optionally choose to **flatten** filled PDFs and how many **preview pages** to draw.
3. Click **Start**. Each PDF is a row in the results, with its summary, preview, filled PDF link and report.

The quickest way to fill a new form: run it once with just the link, look at the numbered preview, then send the values by label or by number (`"#12": "5"`).

#### What you can send

```json
{
  "pdf": "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
  "fields": {
    "Name of entity/individual": "Jane Example",
    "Individual/sole proprietor": true,
    "Address": "123 Main St",
    "City, state, and ZIP code": "Springfield, IL 62701",
    "SSN": "123-45-6789"
  },
  "flatten": true
}
```

| Field | What it takes |
|---|---|
| `pdf` | A https link (Google Drive and Dropbox share links work), a `data:application/pdf;base64,...` URI or the PDF as base64 text. Also accepted as `url` or `file`. |
| `fields` | `{"field name, printed label or #number": value}`, or a list of `{"name": ..., "value": ...}`. Leave it out to list the fields. Also accepted as `values` or `data`. |
| `flatten` | `true` to draw the values into the page so they can't be edited. |
| `fileName` | The name for the output files. |
| `previewPages` | How many pages with fields to draw (0 to 20; 2 by default). |
| `title` | A title for the result. |

**Values by field type:** text and numbers for text boxes (cut to the box's length limit, with a warning); `true`/`false` (or yes/no, x, checked) for checkboxes; the choice's name or the label printed beside it for radio buttons (`"Pro"`); one of the options for dropdowns (any text if the dropdown is editable); one option or a list of options for list boxes.

### Examples

- `https://www.irs.gov/pub/irs-pdf/fw9.pdf` → 23 fields (15 text boxes, 8 checkboxes), all with labels such as "Name of entity/individual", "Exempt payee code (if any)" and "Social security number", and a numbered preview of page 1.
- The W-9 filled with the values above → 7 fields filled (the SSN split as 123 / 45 / 6789), flattened, with a preview.
- `{"pdf": ..., "fields": {"Membership plan": "Gold"}}` on a form whose plans are Basic, Pro and Team → not filled: "'Gold' isn't a choice of Membership plan: basic (Basic), pro (Pro), team (Team)".

### How-to examples

Each example below is a complete input. The result line is what the run says in `summary`.

#### How to list the fields of a fillable PDF

```json
{"requests": [{"pdf": "https://www.irs.gov/pub/irs-pdf/fw9.pdf"}]}
```

Result: "fw9.pdf has 23 form fields (15 text boxes, 8 checkboxes) on 1 of its 6 pages; 23 have a label. Fill them by name, label or number (#)." Each field comes with its label, type and options, and a preview draws its number on the page.

#### How to fill a W-9 automatically

Use the labels printed on the form. You don't need the internal field names.

```json
{"requests": [{"pdf": "https://www.irs.gov/pub/irs-pdf/fw9.pdf", "flatten": true,
               "fields": {"Name of entity/individual": "Jane Example", "Individual/sole proprietor": true,
                          "Address": "123 Main St", "City, state, and ZIP code": "Springfield, IL 62701",
                          "SSN": "123-45-6789"}}]}
```

Result: "Filled 7 fields of fw9.pdf from 5 of 5 values (1 split across boxes); flattened so it can't be edited." The SSN is split into its three boxes.

#### How to fill the same PDF form for many people

Send one request per person. Short keys such as `Name`, `LLC` and `EIN` find their fields too.

```json
{"requests": [
  {"pdf": "https://www.irs.gov/pub/irs-pdf/fw9.pdf", "fileName": "W-9 Ana Lee", "flatten": true,
   "fields": {"Name": "Ana Lee", "Individual/sole proprietor": "yes", "Address": "8 Elm Rd",
              "City, state, and ZIP code": "Dayton, OH 45402", "SSN": "111-22-3333"}},
  {"pdf": "https://www.irs.gov/pub/irs-pdf/fw9.pdf", "fileName": "W-9 Blue Fern Cafe", "flatten": true,
   "fields": {"Name": "Blue Fern Cafe LLC", "LLC": "yes", "Address": "88 Market Street",
              "City, state, and ZIP code": "Springfield, IL 62701", "EIN": "12-3456789"}}
]}
```

Result: two filled PDFs, "Filled 7 fields of W-9 Ana Lee.pdf from 5 of 5 values (1 split across boxes)" and "Filled 6 fields of W-9 Blue Fern Cafe.pdf from 5 of 5 values (1 split across boxes)", both flattened. Up to 100 PDFs fit in one run.

#### How to let an AI agent fill PDF forms

Add this Actor to the [Apify MCP server](https://mcp.apify.com) with `https://mcp.apify.com/?tools=jumblemog/pdf-form-filler`. The agent calls it once with the PDF link to read the fields and their labels, asks the user for the values, then calls it again with `fields` and gives the user `pdfUrl`. Values that couldn't be placed are listed in `notFilled` with the closest fields, so the agent can fix them.

### FAQ

**Do I need the PDF's internal field names?** No. The label printed next to a box, the start of it, or the field's number from the preview all work. Internal names work too.

**Which PDFs can it fill?** Fillable PDFs, the ones with form fields (an AcroForm), such as IRS, HR, insurance and school forms. A scanned or flat PDF has no fields to fill.

**Can the filled PDF be locked?** Yes. Set `"flatten": true` and the values become part of the page.

**Does it work with Zapier, Make or n8n?** Yes. Call the Actor with its Apify app or an HTTP request, and pass the values from a form, a spreadsheet row or a database record in `fields`.

**Does it sign the form?** No. Signature fields are listed but not signed. Sign the filled PDF in your e-signature tool.

### Output

One dataset item per PDF, also saved together in the run's `OUTPUT` record:

```json
{
  "request": 2, "status": "ok", "type": "fill", "title": "fw9.pdf: filled",
  "summary": "Filled 7 fields of fw9.pdf from 5 of 5 values (1 split across boxes); flattened so it can't be edited.",
  "form": {"fileName": "fw9.pdf", "pages": 6, "fields": 23, "byType": {"text": 15, "checkbox": 8}, "xfa": true, "labeled": 23, "filled": 7, "notFilled": 0, "flattened": true},
  "filled": [{"key": "SSN", "index": 19, "field": "topmostSubform[0].Page1[0].f1_11[0]", "label": "Social security number", "type": "text", "value": "123", "matchedBy": "label, split across boxes"}, "..."],
  "notFilled": [],
  "fields": [{"index": 1, "name": "topmostSubform[0].Page1[0].f1_01[0]", "type": "text", "label": "Name of entity/individual. An entry is required. ...", "labelFrom": "above", "page": 1, "value": "Jane Example", "options": null, "maxLength": null, "required": false, "readOnly": false, "multiline": false}, "..."],
  "pdfUrl": "https://api.apify.com/v2/key-value-stores/.../records/002-fw9-flat.pdf?signature=...",
  "previewUrls": ["https://api.apify.com/v2/key-value-stores/.../records/002-fw9-page1.png?signature=..."]
}
```

Listing a form (`"type": "describe"`) gives `form`, `fields`, a `template` of every fillable field and the numbered previews. Every item has `warnings` (values cut short, values not filled, required fields left empty), `notes`, a Markdown `report` and its link. A PDF that can't be read or filled has `"status": "failed"` and the reason in `error`, and isn't billed.

### How it works

1. **Reading**: the form's AcroForm fields are read with [pypdf](https://github.com/py-pdf/pypdf), with inherited types, flags and values, and each box is matched to its page. Fields are numbered top to bottom, left to right.
2. **Labels**: a field's tooltip if it has one, otherwise the text printed to the left of the box, on its top edge, or (for checkboxes and radio buttons) to its right, read with [PDFium](https://pdfium.googlesource.com/pdfium/). Dot leaders are followed back to their label and wrapped label lines are joined.
3. **Filling**: text and choices are written with new appearances so every viewer shows them; checkboxes and radio buttons get their on state. XFA data, which would hide the new values in Adobe Reader, is removed.
4. **Flattening and previews**: PDFium draws each field into its page and the form is removed; previews are rendered by PDFium.

### Pricing

You pay one low price per PDF read or filled, at the price shown on this page. Previews, the filled PDF and the report are included. PDFs that can't be read or filled are never charged. If you set a maximum cost per run, the Actor stops before a PDF that wouldn't fit.

### Speed

A form takes well under a second to read or fill, plus the download. A run through the API answers in about 10 seconds, including the cloud container starting.

### Tips and limits

- **Fillable PDFs only**: the PDF needs form fields (an AcroForm). A scanned or flat PDF has none; the error says so. XFA-only forms (some older government forms that open only in Adobe Reader) have no AcroForm fields to fill.
- **Check the labels** against the numbered preview on a new form: they're read from the page layout, and on crowded forms a box can pick up its neighbour's caption. Fill by number when two boxes share a label.
- **Signatures**: signature fields are listed but not signed; add a signature in your e-signature tool after filling.
- **Encrypted PDFs** open if they need no password to view.
- **Limits**: 100 PDFs per run; 50 MB and 1,000 pages per PDF.

### For AI agents and developers

This Actor works with the [Apify MCP server](https://mcp.apify.com), so AI agents can call it as a tool. Call it with just the PDF link to get `fields` (with `label`, `type` and `options`) and the `template`, then call it again with `fields` set to the values. A direct API call that waits for the answer:

```bash
curl -X POST "https://api.apify.com/v2/acts/jumblemog~pdf-form-filler/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"requests": [{"pdf": "https://www.irs.gov/pub/irs-pdf/fw9.pdf", "fields": {"Name": "Jane Example", "SSN": "123-45-6789"}}]}'
```

`run-sync` instead returns the run's `OUTPUT`: the status, the counts and every result as JSON.

In Python, with the `apify-client` package, filling a local file:

```python
import base64
from apify_client import ApifyClient

client = ApifyClient("YOUR_TOKEN")
pdf = base64.b64encode(open("application.pdf", "rb").read()).decode()
run = client.actor("jumblemog/pdf-form-filler").call(run_input={
    "requests": [{"pdf": pdf, "fields": {"Full name": "Ana Lee", "Membership plan": "Pro"}, "flatten": True}],
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["summary"], item["pdfUrl"])
```

### Credits

- [pypdf](https://github.com/py-pdf/pypdf) (BSD-3-Clause) reads and fills the form; [pypdfium2](https://github.com/pypdfium2-team/pypdfium2) and [PDFium](https://pdfium.googlesource.com/pdfium/) (Apache-2.0 / BSD-3-Clause) read the page text, flatten and render; [Pillow](https://python-pillow.org) draws the previews, with the [Inter](https://rsms.me/inter/) font (SIL Open Font License).
- The example form is the IRS Form W-9, a public US government form.

# Actor input Schema

## `requests` (type: `array`):

One PDF per entry. A link to a fillable PDF lists its form fields: number, name, type, the label printed next to it, page, options, length limit, plus a preview with every box numbered and a ready-to-fill template. An object fills it: {"pdf": a https link, a data: URI or base64, "fields": {"field name, printed label or #number": value}, "flatten": true}. Values: text; true/false for checkboxes; a choice's name or printed label for radio buttons; an option for dropdowns (a list for multi-select lists). A value for a row of boxes with one label (an SSN in 3 + 2 + 4 boxes) is split across them. Labels match loosely (start of the label, its words, small typos, SSN/EIN/DOB); a value that matches no field is reported with suggestions. Google Drive and Dropbox share links work. Other fields: fileName, previewPages, title.

## `flatten` (type: `boolean`):

Draw the values into the page so they can't be edited (when a request doesn't say). Off by default.

## `previewPages` (type: `integer`):

PNG previews of up to this many pages with fields (2 by default; 0 for none).

## Actor input object example

```json
{
  "requests": [
    "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
    {
      "pdf": "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
      "fields": {
        "Name of entity/individual": "Jane Example",
        "Individual/sole proprietor": true,
        "Address": "123 Main St",
        "City, state, and ZIP code": "Springfield, IL 62701",
        "SSN": "123-45-6789"
      },
      "flatten": true
    }
  ]
}
```

# Actor output Schema

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

One row per PDF: the preview and the summary.

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

The OUTPUT record: every result in one JSON object.

## `files` (type: `string`):

Filled PDFs, page previews (PNG) and Markdown reports in the run's key-value store.

# 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 = {
    "requests": [
        "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
        {
            "pdf": "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
            "fields": {
                "Name of entity/individual": "Jane Example",
                "Individual/sole proprietor": true,
                "Address": "123 Main St",
                "City, state, and ZIP code": "Springfield, IL 62701",
                "SSN": "123-45-6789"
            },
            "flatten": true
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("jumblemog/pdf-form-filler").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 = { "requests": [
        "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
        {
            "pdf": "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
            "fields": {
                "Name of entity/individual": "Jane Example",
                "Individual/sole proprietor": True,
                "Address": "123 Main St",
                "City, state, and ZIP code": "Springfield, IL 62701",
                "SSN": "123-45-6789",
            },
            "flatten": True,
        },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("jumblemog/pdf-form-filler").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 '{
  "requests": [
    "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
    {
      "pdf": "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
      "fields": {
        "Name of entity/individual": "Jane Example",
        "Individual/sole proprietor": true,
        "Address": "123 Main St",
        "City, state, and ZIP code": "Springfield, IL 62701",
        "SSN": "123-45-6789"
      },
      "flatten": true
    }
  ]
}' |
apify call jumblemog/pdf-form-filler --silent --output-dataset

```

## MCP server setup

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

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/VMO6JJCQp8hs8FcIA/builds/qZGsFODC7Y7DV2WsN/openapi.json
