To fill PDF form fields with a fill pdf form api, list AcroForm names on POST /v1/pdf/form/fields, then write values on POST /v1/pdf/form/fill. Flatten is on by default. The product page states this is not OCR. The public surface is AcroForm only: it is not XFA and it does not read a scanned page. Product: https://relaypdf.com/pdf-tools/form. Hub: https://relaypdf.com/pdf-tools. REST list: https://relaypdf.com/docs.
This is a PDF tool, not a renderer. You send an existing PDF (url or file), not HTML. Typical order is generate or convert first if the template is Word or HTML, then fill. Each successful tools call is $0.005 on the live rate card. Failed jobs are not billed. This article uses only documented fields.
List fields, then fill
The docs hub lists two rows: POST /v1/pdf/form/fields (List AcroForm fields) and POST /v1/pdf/form/fill (Fill and flatten form fields). The forms product page is the same sequence: inspect, then write. OpenAPI names the fill body FormFillPdfRequest. fields is required and typed as an object. The published sample is a map of field name to string: Name to Jane Doe on a W-9 URL.
fields listing uses PdfSourceRequest: url or file, optional password, plus the shared output fields filename, response, and callbackUrl. The 200 content is JSON (or a URL-mode wrapper). OpenAPI does not publish a field-item schema. The CLI and MCP labels are “AcroForm names” and “Field names.” Do not invent types, page numbers, or widget flags in client copy. Use the returned names as keys on fill.
Fill accepts the same source pair (url or file) plus optional password. flatten is a boolean on FormFillPdfRequest. The product page: “Flatten is on by default.” The Node README shows flatten: true on formFill. OpenAPI does not mark a schema default; the product page is the source for the default. Set flatten to false only if you have a documented reason to leave widgets editable. Mixing html, markdown, or templateId into either form body is not documented.
| You have | Send |
|---|---|
| An AcroForm PDF to inspect | POST /v1/pdf/form/fields |
| Known field names and values | POST /v1/pdf/form/fill with fields |
| A public HTTPS PDF | url |
| PDF bytes you already hold | file (base64 in JSON; SDKs accept bytes) |
| A passworded source PDF | password (optional string) |
| Widgets that must stay editable | flatten: false (default is on) |
| A Word or HTML layout, not a form PDF | Render or convert first — this is not that job |
| An XFA or scanned form | Not this API (not XFA, not OCR) |
What flatten means here
The fill endpoint is documented as fill and flatten. After a successful fill with the default, the values are written into the page appearance and the form is flattened. That is the usual path for a completed W-9, application, or packet you will archive or email. If you omit flatten, you get the product default (on). If you send flatten: true, you match the Node sample. If you send flatten: false, you are opting out of that default; the schema allows the boolean and does not describe leftover widget behavior beyond that.
Fill does not create fields. If the name is missing from the AcroForm, do not assume a new widget appears. List first when the template is not yours. Keys are the AcroForm names, not the visible captions. The product sample uses Name. The CLI sample uses Name. Your PDF may use dotted or vendor names; those come from /fields, not from guesswork.
Limits and price (live, 23 Aug 2026 ET)
Form list and form fill are billed as PDF tools. Pricing lists “PDF tools /v1/pdf/* · /v1/barcodes · /v1/zip” at $0.005 per successful operation. Two calls (list, then fill) are two tools jobs if both succeed. Failed jobs, validation errors, 429s, and 402s do not debit.
A megabyte cap is not published on pricing, the docs hub, the form product page, or OpenAPI. Oversized HTML or files return HTTP 413 payload_too_large. New accounts get the $5 trial. Trial rate limit is 20/min; funded or auto-reload is 60/min; burst is 5 / 10s (SDK READMEs). callbackUrl must be https. Private, loopback, and metadata hosts on url are url_not_allowed.
| Constraint | Published value | Source |
|---|---|---|
| List endpoint | POST /v1/pdf/form/fields | docs hub; OpenAPI |
| Fill endpoint | POST /v1/pdf/form/fill | docs hub; OpenAPI |
| Form model | AcroForm (not XFA) | docs hub; product; brief |
| OCR | Not supported (product: “This is not OCR.”) | product page |
| fields | required object | FormFillPdfRequest |
| flatten | boolean; on by default | schema + product page |
| Source PDF | url or file; optional password | PdfSourceRequest / FormFillPdfRequest |
| Price | $0.005 per successful job | /pricing |
| Output modes | binary | url | async | docs hub; ResponseMode |
| url-mode download | GET /v1/files/:id, 24 hours, no key | docs hub |
| Byte cap | Not published; 413 payload_too_large | /docs/errors |
List fields over REST
Send one source. Default response is binary-style headers on generating endpoints; this path returns JSON. Set response to url for a 24-hour GET /v1/files/:id with no key. Set response to async for HTTP 202 and poll GET /v1/jobs/:id, or supply callbackUrl (https).
curl -X POST https://api.relaypdf.com/v1/pdf/form/fields
-H "Authorization: Bearer $RELAYPDF_API_KEY"
-H "Content-Type: application/json"
-d '{
"url": "https://example.com/w9.pdf"
}'
Fill and flatten over REST
Send the same source plus fields. filename names the output PDF. Default response is binary: PDF bytes plus x-relaypdf-id, x-relaypdf-size, and content-disposition. The JSON below matches the product page (url, fields.Name) plus filename.
curl -X POST https://api.relaypdf.com/v1/pdf/form/fill
-H "Authorization: Bearer $RELAYPDF_API_KEY"
-H "Content-Type: application/json"
-d '{
"url": "https://example.com/w9.pdf",
"fields": { "Name": "Jane Doe" },
"filename": "w9-jane.pdf"
}'
--output w9-jane.pdf
To leave widgets in place, add flatten false. That is the documented opt-out. Do not send appearance dictionaries, font sizes, or checkbox enums that are not on FormFillPdfRequest. fields is an object; OpenAPI does not publish per-value types. The live samples use strings.
curl -X POST https://api.relaypdf.com/v1/pdf/form/fill
-H "Authorization: Bearer $RELAYPDF_API_KEY"
-H "Content-Type: application/json"
-d '{
"url": "https://example.com/w9.pdf",
"fields": { "Name": "Jane Doe" },
"flatten": false,
"filename": "w9-editable.pdf"
}'
--output w9-editable.pdf
Node, Python, and CLI
Python (relaypdf 0.1.x) exposes pdf.form_fields(**input) and pdf.form_fill(fields, **input). The method table describes fill as “Fill and flatten.” REST field names stay camelCase. file may be bytes. Node (@relaypdf/sdk 0.1.x) uses pdf.formFields and pdf.formFill. The published Node tools sample is formFill with file, fields: { Name: "Jane" }, flatten: true.
CLI (@relaypdf/cli 0.1.4): relaypdf form-fields file.pdf and relaypdf form-fill file.pdf --fields '{"Name":"Jane"}' --out filled.pdf. MCP lists form_fields (field names) and form_fill (fields object). Do not ask anyone to paste a key; run npx @relaypdf/cli setup.
import os
from relaypdf import RelayPDF
client = RelayPDF(api_key=os.environ["RELAYPDF_API_KEY"])
names = client.pdf.form_fields(url="https://example.com/w9.pdf")
filled = client.pdf.form_fill(
{"Name": "Jane Doe"},
url="https://example.com/w9.pdf",
filename="w9-jane.pdf",
)
filled.save("w9-jane.pdf")
import { RelayPDF } from "@relaypdf/sdk";
const client = new RelayPDF({
apiKey: process.env.RELAYPDF_API_KEY!,
});
const listed = await client.pdf.formFields({
url: "https://example.com/w9.pdf",
});
const filled = await client.pdf.formFill({
url: "https://example.com/w9.pdf",
fields: { Name: "Jane Doe" },
flatten: true,
filename: "w9-jane.pdf",
});
await filled.save("w9-jane.pdf");
Errors and what is not billed
Failures are { error: { code, message } }. SDKs throw RelayPDFError. Branch on code. invalid_request covers a missing fields object or otherwise invalid sources. url_not_allowed covers a private, loopback, or metadata host, or a non-https callbackUrl. payload_too_large is 413. processing_failed is a 502 on a tool job. payment_required is an empty wallet. rate_limited includes Retry-After. None of those debit.
Only a successful list or fill (HTTP 200, or a completed async job) takes $0.005. Rendering a new PDF first is $0.015. A LibreOffice convert first is $0.04. Merge, protect, extract, stamp, and zip are separate tools jobs if you call them.
What this is not
This fill pdf form api is not XFA. It is not OCR. It does not extract a text layer from a scan (that sibling is POST /v1/pdf/text, also no OCR). It does not render HTML, convert Office, merge, stamp, or protect. Chain those documented tools on the bytes or on the 24-hour url. Sibling how-to: extract pages (https://relaypdf.com/pdf-tools/extract). Invoice layout that is not an AcroForm belongs on POST /v1/pdf.
OpenAPI does not publish checkbox encodings, radio export values, signature widgets, or appearance streams. Do not invent them. url-mode downloads are public for 24 hours with no key. If the completed form must stay closed, protect it after you flatten (userPassword on POST /v1/pdf/protect).
Ship it
Use the fill pdf form api when the source is already an AcroForm PDF. List names, POST values, flatten by default. Not XFA. Not OCR. $0.005 on success. Start at https://relaypdf.com/pdf-tools/form. Copy FormFillPdfRequest from https://relaypdf.com/openapi.json. Auth is a bearer key on https://api.relaypdf.com.