PDF Form API: Fill Any PDF Form from Your Code
Most PDF tooling for developers solves generation: take a template, pour in variables, get a document. That is not the problem when the PDF already exists and comes from someone else. A visa application from an embassy, a claim form from an insurer, an onboarding packet from a government portal. You do not control the layout, the field names inside it are often things like Text42, and yet the same twelve values from your database have to land in the right boxes, every time.
This guide covers the FillWise PDF form API, an endpoint we run on top of the same engine our Chrome extension uses. You send the PDF and the source data, the engine reads the form the way a person would and returns the completed document. It shows the exact request and response, the limits, what happens when things go wrong, and what the API deliberately does not do.
Why filling existing PDFs is harder than generating new ones
- Field names are unreliable. In real-world PDFs the field next to "Family name" can be called
Text42,f1_03[0]orundefined_7. Any integration keyed on field names breaks on the next revision of the form. - Half the forms have no fields at all. Many official PDFs are flat: a text layer and printed lines, nothing interactive. Filling one means drawing text at the right coordinates, which nobody wants to hand-measure.
- The data is never in the shape the form wants. Your system has
date_of_birthas ISO, the form wantsdd/mm/yyyysplit across three boxes, and the nationality dropdown has its own spelling of every country. - Every form is a one-off. Building a mapping per document pays off only for forms you fill hundreds of times. For the long tail it is cheaper to retype.
The API is built around that long tail: no mapping step, the form is read at request time.
The request
One endpoint, JSON in, JSON out:
POST https://fillwise.ai/api/v1/pdf/fill
Authorization: Bearer <your key>
Content-Type: application/json
| Field | Type | Notes |
|---|---|---|
pdf |
string, required | The form, base64-encoded. Up to 20 MB |
data |
string | Source text in any language and any shape: a CRM record, an email, notes. Up to 60 000 characters. Required unless attachments is given |
attachments |
array, optional | Up to four source documents the AI reads the data from: { "name", "mediaType", "data" } with data base64-encoded, 5 MB each. mediaType is image/jpeg, image/png, image/webp or application/pdf |
pages |
string, optional | Which pages to fill, for example "1, 3-6". All pages by default |
The key comes from your account: open fillwise.ai/account, section API keys, create one, copy it once. Up to five keys can be active at a time and any of them can be revoked. X-Api-Key: <key> works as an alternative to the Authorization header.
curl
curl -sS https://fillwise.ai/api/v1/pdf/fill \
-H "Authorization: Bearer $FILLWISE_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"pdf\": \"$(base64 -w0 application.pdf)\",
\"data\": \"Maria Petrova, born 14.03.1992 in Riga, Latvian, passport LV4482913 issued 05.02.2021, valid until 05.02.2031\",
\"pages\": \"1-2\"
}" > response.json
Python
import base64, json, os, requests
with open("application.pdf", "rb") as f:
pdf_b64 = base64.b64encode(f.read()).decode()
r = requests.post(
"https://fillwise.ai/api/v1/pdf/fill",
headers={"Authorization": f"Bearer {os.environ['FILLWISE_API_KEY']}"},
json={
"pdf": pdf_b64,
"data": "Maria Petrova, born 14.03.1992 in Riga, Latvian, passport LV4482913",
},
timeout=120,
)
r.raise_for_status()
result = r.json()
with open("filled.pdf", "wb") as f:
f.write(base64.b64decode(result["pdf"]))
print(result["filled_count"], "fields,", result["mode"])
Node
import { readFile, writeFile } from "node:fs/promises";
const pdf = (await readFile("application.pdf")).toString("base64");
const res = await fetch("https://fillwise.ai/api/v1/pdf/fill", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.FILLWISE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
pdf,
data: "Maria Petrova, born 14.03.1992 in Riga, Latvian, passport LV4482913",
}),
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
const result = await res.json();
await writeFile("filled.pdf", Buffer.from(result.pdf, "base64"));
console.log(result.filled_count, "fields,", result.mode);
The response
{
"success": true,
"filled_count": 26,
"mode": "acroform",
"pdf": "<base64 of the filled PDF>"
}
mode tells you how the document was filled:
acroform: the PDF has real form fields and they were filled through the fields. Text goes into text fields, checkboxes are checked, radio groups get one option, dropdowns get one of their own options.flat: the PDF has no fields, only a text layer. The engine found the labels and lines and drew the values at those positions. This is the mode to review most carefully, because placement is computed, not defined by the form.
filled_count is the number of values actually written. A zero means nothing in your source data matched the form. The auto-fill is still spent in that case, because the matching did run, so check that the text really contains the values the form asks for before retrying.
Errors
Every error is JSON with error and code:
| Status | Code | Meaning |
|---|---|---|
| 400 | BAD_REQUEST, INVALID_PDF |
A field is missing or malformed, or the PDF could not be read |
| 401 | MISSING_API_KEY, INVALID_API_KEY |
No key, or a key that was revoked |
| 422 | SCANNED_PDF_UNSUPPORTED |
The PDF has no text layer |
| 429 | QUOTA_EXCEEDED |
No auto-fills left on the account, or more than 20 requests in a minute |
| 502 | AI_FAILED |
The AI provider did not return a usable answer. The auto-fill is refunded, retry the request |
What to check by hand
The engine reads the form and places values where they belong. It does not know your rules, so keep a review step:
- Flat mode output. Open the file and look at it. Values sit where the labels were found, which is right most of the time and slightly off on some forms with unusual layouts.
- Dates and formats. The engine follows the format the field or its label suggests. If a form is ambiguous about
dd/mmversusmm/dd, it can guess wrong. - Fields you did not provide data for. They stay empty. The API never invents a value, so an empty box means your source text did not contain it.
- XFA forms. They have no standard fields, fall through to the flat path and usually come back with a low
filled_count. Export them to a regular PDF first.
Privacy
Be precise here, because PDFs are usually the sensitive documents. With the API, the PDF is sent to our server, processed in memory and returned in the same response. It is not written to disk and not kept after the response is sent. What goes to the AI provider is the list of fields the form contains plus the source data you supplied. Attached documents are read by the AI to extract the data and are handled the same way. Usage statistics record the domain, the number of fields and the timestamp, not the content.
This is different from the Chrome extension, where the PDF is parsed inside the browser and only the field list leaves it. If keeping the document on your side matters more than automation, the extension flow is the one to use.
Pricing
There is no separate API plan. Every successful fill spends one auto-fill from the account that owns the key, the same balance the extension and the account page use. The free plan starts with 20 auto-fills and adds 5 more every week, no card needed. Paid plans are 100, 500 and 2000 auto-fills per month; details at fillwise.ai/#price or in your account.
FAQ
Can I fill web forms through the API? No. The API handles PDF documents. Web forms are filled by the Chrome extension on the page itself.
Does it work with scanned PDFs?
No. A scan has no text layer, so there is nothing to anchor the values to, and the request is rejected with 422. The Chrome extension can read a scanned page as an image, the API cannot yet.
How many requests can I send? 20 per minute per account. For larger batches, space the requests out. If you need more, write to us.
Is the filled PDF flattened?
No. In acroform mode the fields stay interactive, so the recipient can still edit them. In flat mode the values are drawn onto the page and cannot be edited as fields.
Can I try it without code? Yes. The same engine runs in your account at fillwise.ai/account: upload a PDF, paste the data, review the result in the editor before downloading. The API is that flow without the screen.
Get started
Create a key at fillwise.ai/account, section API keys, and send your first request with the curl example above. The free auto-fills are enough to test it on a real form. If you prefer filling forms in the browser, the Chrome extension covers web forms as well as PDFs.