Endpoint reference
POST /api/inspect
List the AcroForm fields in a PDF you upload — names, types, options, current values — plus a paste-ready fillTemplate for /api/fill-form. Returns JSON. Prefer a browser? The free inspector at /tools/inspect runs the same engine.
This is the first call in the natural workflow: inspect to learn the field names, fill-form to set them, merge to combine the results. You can't fill fields whose names you don't know.
Request
Method: POST. URL: https://pdfops.dev/api/inspect. Body: multipart/form-data.
| Field | Type | Required | Description |
|---|---|---|---|
pdf | File (PDF binary) | Yes | The PDF to inspect. Maximum size: ~10 MB. |
| Header | Required | Description |
|---|---|---|
X-API-Key | No | Optional. Same auth semantics as /api/fill-form and /api/merge: no header → anonymous, 100 requests/IP/month; valid key → your tier's quota; invalid or revoked key → 401 invalid_api_key. |
Successful (2xx) calls count 1 request against your quota — the same meter as the other PDF endpoints. Check consumption with GET /api/usage.
Response
On success: 200 OK, Content-Type: application/json:
{
"count": 4,
"fields": [
{ "name": "customer_name", "type": "text", "value": "", "readOnly": false },
{ "name": "agreed_to_terms", "type": "checkbox", "checked": false, "readOnly": false },
{ "name": "plan", "type": "radio", "value": "Starter", "options": ["Starter", "Pro"], "readOnly": false },
{ "name": "approval", "type": "unsupported", "raw": "PDFSignature", "readOnly": false }
],
"fillTemplate": {
"customer_name": "",
"agreed_to_terms": "true",
"plan": "Starter"
}
}
| Field | Type | Meaning |
|---|---|---|
count | number | Number of AcroForm fields found. |
fields | array | One object per field — schema below. |
fillTemplate | object | A paste-ready JSON object for /api/fill-form's fields parameter. Supported fields only; checkbox placeholders are the string "true", choice placeholders are the first option. Overwrite the placeholder values and send it. |
A PDF with no AcroForm returns 200 with {"count": 0, "fields": [], "fillTemplate": {}} — deliberately not an error, because "does this PDF have fields?" is exactly the question you're asking.
On any failure: 4xx, Content-Type: application/json, body is { "error": "<code>", "details": "<explanation>" }.
Field objects
| Field | Type | Meaning |
|---|---|---|
name | string | The AcroForm field name — exactly what /api/fill-form expects as a fields key. |
type | string | One of text, checkbox, dropdown, radio, optionlist, unsupported. |
value | string | Present on text and choice fields: the current saved value. |
checked | boolean | Present on checkbox fields: whether the box is currently checked. |
options | string[] | Present on choice fields (dropdown, radio, optionlist): the allowed values. |
readOnly | boolean | Whether the field is flagged read-only in the PDF. |
raw | string | Present on unsupported fields: the underlying AcroForm class, e.g. PDFButton or PDFSignature. |
How type maps to AcroForm classes and which extra property each carries:
type | AcroForm class | Carries |
|---|---|---|
text | PDFTextField | value |
checkbox | PDFCheckBox | checked |
dropdown | PDFDropdown | options + value |
radio | PDFRadioGroup | options + value |
optionlist | PDFOptionList | options + value |
unsupported | PDFButton, PDFSignature | raw |
Error codes
| Error code | Status | Trigger |
|---|---|---|
invalid_multipart | 400 | Request body could not be parsed as multipart/form-data. |
missing_pdf | 400 | No pdf field present or it wasn't a File. |
invalid_pdf | 400 | The uploaded PDF couldn't be parsed (corrupt, encrypted, not a PDF). |
invalid_api_key | 401 | An X-API-Key header was sent but the key is unknown or revoked. No fallback to the anonymous IP cap — get a fresh key at /pricing. |
rate_limited (anonymous) | 429 | No X-API-Key header and the per-IP limit (100/month) is exhausted. Retry-After header gives seconds until next calendar month. |
rate_limited (keyed) | 429 | Your key's tier quota (Free 250 / Indie 4,000 / Pro 25,000 per month) is exhausted. Retry-After header gives seconds until next calendar month. |
Note there's no no_form error here: a form-less PDF is a valid answer (count: 0), not a failure.
Example — curl
Don't have a fillable PDF? Grab the sample invoice-template.pdf with customer_name + total fields.
curl -X POST https://pdfops.dev/api/inspect \
-F "pdf=@invoice-template.pdf" \
-H "X-API-Key: pdfops_live_..." # optional — raises your quota to your tier
Example — JavaScript: inspect, then fill
fillTemplate is shaped so it pipes straight into /api/fill-form — overwrite the placeholders and send:
import { readFile, writeFile } from 'node:fs/promises';
const pdf = await readFile('invoice-template.pdf');
const blob = new Blob([pdf], { type: 'application/pdf' });
// 1. Inspect — learn the field names.
const fd = new FormData();
fd.append('pdf', blob, 'invoice-template.pdf');
const { fillTemplate } = await (
await fetch('https://pdfops.dev/api/inspect', { method: 'POST', body: fd })
).json();
// 2. Fill — overwrite the placeholders, pipe the template into fill-form.
const fields = { ...fillTemplate, customer_name: 'Acme Co', total: '$1,250.00' };
const fillFd = new FormData();
fillFd.append('pdf', blob, 'invoice-template.pdf');
fillFd.append('fields', JSON.stringify(fields));
const res = await fetch('https://pdfops.dev/api/fill-form', { method: 'POST', body: fillFd });
await writeFile('filled.pdf', Buffer.from(await res.arrayBuffer()));
FAQ
Why did I get count: 0 on a PDF that looks like a form?
The PDF has no AcroForm layer. Three common cases: a flat PDF (the "form" is just text drawn on the page — no interactive fields), an XFA form (a different, XML-based form technology that AcroForm tooling doesn't read), or a scanned document (an image of a form — reading it would need OCR, which PDFops doesn't do). count: 0 is a 200, not an error, because "does this PDF have fields?" is exactly the question this endpoint answers. To make the PDF fillable, add AcroForm fields in Acrobat (Tools → Prepare Form), LibreOffice Draw, or pdftk.
What does type: "unsupported" mean?
The field exists in the AcroForm but isn't a type /api/fill-form can set — buttons and signatures. The raw property carries the underlying class (e.g. PDFButton, PDFSignature). Inspect reports them so you see the complete form; they're excluded from fillTemplate, and sending them to /api/fill-form would return 400 unsupported_field_type.
Does /api/inspect consume my quota?
Yes — a successful (2xx) inspect counts as 1 request against the same monthly quota as /api/fill-form and /api/merge. Failed requests (4xx/5xx) don't consume budget. A typical inspect-then-fill round trip costs 2 requests.
What's the difference between /api/inspect and the free tool at /tools/inspect?
Same engine, different surface. /tools/inspect is the browser version — drop a PDF on the page and read the field list, no code, no key. /api/inspect is the same inspection over HTTP for your scripts, CI, and edge functions, and it returns the machine-readable fillTemplate you can pipe into /api/fill-form.
Are value and checked the current saved values in the PDF?
Yes. value (text and choice fields) and checked (checkboxes) reflect what's currently saved in the uploaded PDF. That makes /api/inspect double as a reader: inspect a filled PDF to extract what's in it — for example, a form a customer filled in and sent back.