Sign in

PDFops

@pdfops.dev
64 followers 310 following 692 posts

Hosted PDF API for AI agents, n8n and Python. Fill AcroForm fields, merge PDFs, inspect forms, generate invoices. Deterministic output, no headless browser. MCP server + SDKs, free key at pdfops.dev

PostsRepliesMedia
PDFops @pdfops.dev · 01/10/2026
New on the blog: Merging a Letter invoice with an A4 attachment in pypdf, and the page size nobody normalizes. pypdf's append() copies each input page's own /MediaBox into the merged output as-is, so a Letter invoice merged with an A4 attachment comes out with two page sizes. Why →
pdfops.dev
Merging a Letter invoice with an A4 attachment in pypdf, and the page size nobody normalizes — PDFops
pypdf's append() copies each input page's own /MediaBox into the merged output as-is, so a Letter invoice merged with an A4 attachment comes
010
PDFops @pdfops.dev · 30/09/2026
The fill endpoint takes multipart/form-data, not a JSON body. In Python that's the PDF in files= and the field map in data= as json.dumps(fields), a JSON string, not a dict. Get that shape right and the rest is one requests.post. Working snippet →
pdfops.dev
Fill a PDF in Python | PDFops
Deterministic AcroForm fill in one HTTP call, no native dependencies.
010
PDFops @pdfops.dev · 30/09/2026
Agents re-run tool calls: timeouts, re-plans, the same step twice. If the fill tool is deterministic, three identical pdf_fill calls give you three byte-identical files, so you dedupe by hash instead of guessing which copy is the real one. PDF tools over MCP →
pdfops.dev
PDF tools for AI agents over MCP | PDFops
Give an agent pdf_inspect, pdf_fill and pdf_merge, and why it should inspect first.
020
PDFops @pdfops.dev · 29/09/2026
Byte-identical output means you can name a PDF after its own hash. Use sha256(bytes) as the R2 or S3 key: identical fills collapse into one object, and "did we already generate this one?" becomes a HEAD request. It only works if your generator is byte-stable →
pdfops.dev
The case for deterministic PDF filling | PDFops
Why same input to byte-identical output changes how you store, cache and test PDFs.
010
PDFops @pdfops.dev · 29/09/2026
Contract templates change when legal edits them. If the field names stay the same, your calling code doesn't have to. Version the template file and put that version in a footer field at issue time, so every PDF you ever sent says which template made it. The pattern →
pdfops.dev
Contract PDFs from onboarding flows | PDFops
Fill a contract template from form data, then hand the PDF to your e-sign tool.
010
PDFops @pdfops.dev · 28/09/2026
Filling a hybrid AcroForm/XFA PDF still returns 200 and a PDF, so the status code can't tell you the XFA layer was dropped. We set an x-pdfops-warning: xfa-layer-dropped response header instead. Read it in your handler: older XFA-first Adobe viewers show the blank template →
pdfops.dev
Fill a PDF in Node.js | PDFops
Deterministic AcroForm fill from your backend in one HTTP call.
010
PDFops @pdfops.dev · 28/09/2026
If your PDF needs conditionals, loops and partials, a real templating engine like PDFMonkey's Liquid is the right tool and PDFops isn't. We fill fields on a fixed-layout PDF that already exists, nothing more. Pick by whether the layout changes per document →
pdfops.dev
PDFops vs PDFMonkey | PDFops
When an HTML templating engine fits, and when filling an existing PDF does.
000
PDFops @pdfops.dev · 27/09/2026
Filling an invoice template means the layout is fixed: a template with 10 line-item fields holds 10 lines, and line 11 has nowhere to go. Size it for your real worst case, or build a base PDF per invoice first. That's the price of never re-rendering the layout. Both patterns →
pdfops.dev
Invoice PDFs at scale | PDFops
Fill an invoice template from a webhook and get identical bytes every time.
020
PDFops @pdfops.dev · 27/09/2026
PDFShift bills overage per credit past your plan. PDFops does the opposite today: hit your quota and you get a 429 until the 1st, so the bill is always the sticker price. The tradeoff is real, your code has to handle that 429. Which failure mode you'd rather have decides it →
pdfops.dev
PDFops vs PDFShift | PDFops
Hosted PDF API comparison: form fill and merge versus HTML rendering.
000
PDFops @pdfops.dev · 26/09/2026
Quota math: a fill is one request and a merge is another, so a filled-then-merged bundle costs two. On the $16 plan (4,000 requests) that's 2,000 bundles a month. Only 2xx responses count, so failed calls while you debug are free. Count requests per document →
pdfops.dev
PDFops | PDF fill and merge API
Deterministic PDF fill and merge over HTTP, from $16/mo or free to try.
010
PDFops @pdfops.dev · 26/09/2026
A field name that isn't in the PDF gets a 400 unknown_field with the name in the message, not a 200 and a blank box. A value longer than the field's maxLength gets exceeds_max_length instead of being truncated. Typos and overflows fail loudly, so you can catch them in CI →
pdfops.dev
PDFops playground
Fill and merge PDFs in your browser, no signup.
000
PDFops @pdfops.dev · 25/09/2026
Field map gotcha: every value has to be a string, numbers and checkbox states included. {"qty": 3} gets a 400 invalid_field_value that names the field. {"qty": "3"} fills. Convert once where you build the map, not field by field at call time →
pdfops.dev
Fill a PDF in JavaScript | PDFops
Fill AcroForm fields from the browser or a backend with one HTTP call.
010
PDFops @pdfops.dev · 25/09/2026
The Inspector reads your PDF's AcroForm fields entirely in the browser tab. Nothing is uploaded, so a real customer's form never leaves your machine while you work out field names. It also writes the fill call for you, curl, fetch or Worker, with those names already in it →
pdfops.dev
PDF Form-Field Inspector | PDFops
List a PDF's AcroForm fields in your browser and copy the exact fill call.
010
PDFops @pdfops.dev · 24/09/2026
New on the blog: Giving an AI agent PDF tools over MCP, and why it has to inspect before it fills. An MCP server hands an agent pdf_inspect, pdf_fill, and pdf_merge tools directly, no client code to write. The field-name problem that makes inspecting first non-optional, and what →
pdfops.dev
Giving an AI agent PDF tools over MCP, and why it has to inspect before it fills — PDFops
An MCP server hands an agent pdf_inspect, pdf_fill, and pdf_merge tools directly, no client code to write. The field-name problem that makes
011
PDFops @pdfops.dev · 24/09/2026
Fill a PDF's fields after it's already been digitally signed and most viewers will flag the signature as invalid — any edit to a signed file, even one field, falls outside the byte range the signature covers. Fill first, sign last, never the other way around →
pdfops.dev
Signed contracts from a Tally webhook
Fill a contract template from Tally answers and email back a signed PDF, on one Worker.
010
PDFops @pdfops.dev · 23/09/2026
Not every 'pick one' group in a PDF is a radio group. Some forms fake it with independent checkboxes laid out to look exclusive — same visual style, different field names — so setting one true doesn't clear the others. Check the type before you assume selecting is exclusive →
pdfops.dev
Fill a PDF in JavaScript
Build a correct field map from real AcroForm data, in the browser or on a server.
000
PDFops @pdfops.dev · 23/09/2026
Marking a field Required in Acrobat only makes Acrobat itself nag before a human hits save. Neither the PDF format nor /fill-form enforces it — you can submit a field map that skips a required field entirely and the call still succeeds. Validate that yourself first →
pdfops.dev
Fill a PDF in Python
Native libs vs one POST with a JSON field map, compared with working code.
010
PDFops @pdfops.dev · 22/09/2026
A text field with the Password flag set only tells Acrobat to mask what you type on screen — the stored /V value in the file is a plain string, no hashing, no encryption. Treat it like any other visible field when you decide what belongs in a field map →
pdfops.dev
PDF field Inspector
Paste a PDF, see every AcroForm field, its type, flags, and current value.
010
PDFops @pdfops.dev · 22/09/2026
Text fields, checkboxes, dropdowns, radio groups, option lists — /fill-form fills all five. Push buttons and signature fields aren't silently skipped, they 400 with unsupported_field_type, so a typo'd field name and an unfillable field type don't look like the same failure →
pdfops.dev
AcroForm field Inspector — PDFops
See every AcroForm field's real name, type, and widget count before you fill.
010
PDFops @pdfops.dev · 21/09/2026
Merging five PDFs into one drops each source's outline tree — the merged file is a fresh document with only page objects copied in, so whatever table-of-contents bookmarks they had don't survive. Build a new one against the merged page count if you need it →
pdfops.dev
PDFops playground
Fill and merge in the browser and see exactly what gets sent.
010
PDFops @pdfops.dev · 21/09/2026
Merge concatenates page objects, not visual orientation — each page keeps whatever /Rotate value its source PDF gave it. Feed it a landscape scan and a portrait template and the merged file comes back with both, exactly as mixed as the inputs were →
pdfops.dev
Reports — PDFops
Turn a recurring data payload into a client-ready PDF report with one API call.
000
PDFops @pdfops.dev · 20/09/2026
Merge doesn't try to preserve N source files' timestamps — the output gets a fixed epoch CreationDate/ModDate and a single Producer string, every time. However many PDFs go in, whenever they were made, the merged file's own metadata is always identical →
pdfops.dev
Merging PDFs in a Bun server
Page order, quota checks, and the 429 you'll eventually hit — in Bun.serve().
000
PDFops @pdfops.dev · 20/09/2026
Loading a PDF for /fill-form uses updateMetadata: false, so the file's own CreationDate and ModDate pass through untouched instead of pdf-lib's default of stamping wall-clock time on save. One line of config, one less thing breaking byte-identical output →
pdfops.dev
The case for deterministic PDF filling
Same input, byte-identical output — hash it, cache it, sign it.
020
PDFops @pdfops.dev · 19/09/2026
flatten=true on /fill-form bakes field values into page content and drops the AcroForm — that's the whole guarantee. It is not tamper evidence, not a signature, not compliance. Flatten to stop edits; sign separately if you need proof nothing changed after →
pdfops.dev
Contracts — PDFops
Fill a contract template's fields deterministically, then route it to e-sign.
000
PDFops @pdfops.dev · 19/09/2026
Every /fill-form call regenerates every field's appearance stream with an embedded Helvetica, unconditionally — so a programmatically-built AcroForm whose DA points at a font it never embedded still renders filled, not blank. No NeedAppearances flag to remember →
pdfops.dev
Fill a PDF in Node.js — PDFops
One POST, a JSON field map, deterministic bytes back — appearances included.
010
PDFops @pdfops.dev · 18/09/2026
PDFops' /fill-form takes a checkbox value as the plain string "true" or "false" — never the field's actual on-state export code like /Yes or /1. pdf-lib resolves that mapping internally, so you never inspect a checkbox just to check it. Try it live →
pdfops.dev
PDFops playground
Try a fill or merge call, then copy the exact snippet for your stack.
010
PDFops @pdfops.dev · 18/09/2026
New on the blog: Filling a checkbox with pypdf, and the on-state string it never validates. pypdf's update_page_form_field_values() sets a checkbox with zero validation, so a wrong on-state string leaves it silently unchecked. The state it actually needs, and the flag that hides →
pdfops.dev
Filling a checkbox with pypdf, and the on-state string it never validates — PDFops
pypdf's update_page_form_field_values() sets a checkbox with zero validation, so a wrong on-state string leaves it silently unchecked. The s
010
PDFops @pdfops.dev · 17/09/2026
One field name can have multiple widget annotations — an initials box repeated on every page of a 12-page lease is one field, not twelve. Set its value once and every visible instance updates together. The Inspector lists it as a single entry, not a duplicate per page →
pdfops.dev
AcroForm field Inspector — PDFops
See every AcroForm field's real name, type, and widget count before you fill.
000
PDFops @pdfops.dev · 17/09/2026
DocRaptor's async mode hands back a job ID and expects you to poll or wait on a callback, because rendering HTML/CSS at scale can be slow. Filling fields on a PDF that already exists has no render pass to wait on, so the call is just request in, bytes out, no job to track →
pdfops.dev
PDFops vs DocRaptor
Synchronous fill and merge, compared against DocRaptor's async render jobs.
000
PDFops @pdfops.dev · 16/09/2026
Same fill call, four snippets: the playground shows curl, Node, Python, and a Cloudflare Worker fetch side by side for whatever you just ran. Copy whichever matches your stack and the request is already correct — no translating a curl example into your language by hand →
pdfops.dev
PDFops playground
Try a fill or merge call, then copy the exact snippet for your stack.
000
PDFops @pdfops.dev · 16/09/2026
An invoice PDF that's about to be emailed doesn't need to touch disk in between: fill-form returns bytes, and most transactional email APIs take an attachment as base64 or a buffer directly. Generate and send in the same request handler, nothing written, nothing to clean up →
pdfops.dev
Invoice PDFs — PDFops
Fill an invoice template and attach the bytes to an email, no intermediate file.
010
PDFops @pdfops.dev · 15/09/2026
New on the blog: Getting started with PDFops. Sign up, grab a key, and make your first fill-form and merge call — the walkthrough covers both primitives end to end, with the exact request bodies, before you build anything on top of them →
pdfops.dev
Getting started — PDFops
First fill-form and merge calls, request bodies included, start to finish.
010
PDFops @pdfops.dev · 15/09/2026
The response is just bytes — an ArrayBuffer you can pipe straight into an S3 PutObject or an R2 bucket without ever writing a temp file to disk. No cleanup step, no /tmp quota to hit on a serverless box with a few hundred MB to work with →
pdfops.dev
Fill a PDF in Node — PDFops
Bytes back, no temp file — pipe the response straight to object storage.
000
PDFops @pdfops.dev · 14/09/2026
The field map in Python is just a dict — no schema class, no dataclass, no generated types to keep in sync with the PDF. Paste the form into the Inspector, copy the field names it lists, build the dict, POST it. The PDF's own AcroForm tree is the only schema that matters →
pdfops.dev
Fill a PDF in Python — PDFops
A plain dict as the field map — no native deps, no generated types.
000
PDFops @pdfops.dev · 14/09/2026
Anvil's plans are seat- and volume-tiered, priced for a team building forms inside their editor. If you already have the PDF and just need fields filled from a script, that tier structure is overhead for a job that's one HTTP call. What the two pricing models actually assume →
pdfops.dev
PDFops vs Anvil
Pay-per-call fill and merge, compared against Anvil's editor-first, seat-priced model.
010
PDFops @pdfops.dev · 13/09/2026
A PDF signature field (/FT /Sig) holds a cryptographic signature dictionary, not a text string — there's no value to POST into it. Fill-form only touches text, checkbox, and choice fields; a signature field just passes through empty, waiting for whatever signs it →
pdfops.dev
Contract PDFs — PDFops
Fill a contract template's fields deterministically, then route it to e-sign.
000
PDFops @pdfops.dev · 13/09/2026
The Inspector lists fields in the PDF's internal object order, not left-to-right, top-to-bottom reading order. Field #1 in the list can be a footer widget on page 4 while the form's first visible box is field #38. Don't assume position from the index →
pdfops.dev
AcroForm field Inspector — PDFops
See every AcroForm field's real name, type, and value — in the order the file stores them.
010
PDFops @pdfops.dev · 12/09/2026
PDF/A, the archival format compliance teams ask for, bans embedded JavaScript outright. If a report template has a calculated field — a script summing line items — that script gets stripped on save, so the total has to be computed before you fill, not left to run in the viewer →
pdfops.dev
Report PDFs — PDFops
Fill recurring report templates deterministically, compliance-safe by default.
000
PDFops @pdfops.dev · 12/09/2026
Netlify Edge Functions run on Deno under the hood, so a native PDF binary or a headless browser is a non-starter there. A fetch call with a JSON field map doesn't care what's under the hood — same code path as Node or a Worker →
pdfops.dev
Fill a PDF in JavaScript — PDFops
One fetch call fills PDF form fields from any JS runtime, edge or not.
000
PDFops @pdfops.dev · 11/09/2026
New on the blog: Packing-slip PDFs from a fulfillment webhook, and the redelivery that duplicates them. A fulfillment webhook's order-shipped event can redeliver under retry, and a naive handler fills the same packing slip twice. The atomic-claim pattern, on Vercel with Upstash R →
pdfops.dev
Packing-slip PDFs from a fulfillment webhook, and the redelivery that duplicates them — PDFops
A fulfillment webhook's order-shipped event can redeliver under retry, and a naive handler fills the same packing slip twice. The atomic-cla
010
PDFops @pdfops.dev · 11/09/2026
Government forms love auto-generated field names like topmostSubform[0].Page1[0].f1_01[0] — unreadable on their own. Most also carry a /TU tooltip string, the human label the form author actually typed. The Inspector surfaces it next to the real name. #webdev
pdfops.dev
AcroForm field Inspector — PDFops
Paste a PDF, see every field's real type and export values before you guess at a field map.
000
PDFops @pdfops.dev · 10/09/2026
No sales call, no demo booking, no "contact us for pricing": sign up, get a key, make your first fill-form call in about a minute. That's the entire onboarding funnel for a $0.004-per-call API. →
pdfops.dev
PDFops — deterministic PDF fill & merge API
Free tier, 100 requests/month, rate-limit headers on every response.
010
PDFops @pdfops.dev · 10/09/2026
New on the blog: Fill and merge PDFs in your browser. Useful past the demo: a KYC packet with an applicant's ID scan never has to leave their machine for a one-off job, and the same tab shows you the exact API call to make once it stops being one-off →
pdfops.dev
Fill and merge PDFs in your browser
Free, no-signup browser tool for filling and merging PDFs — no backend required.
010
PDFops @pdfops.dev · 09/09/2026
New on the blog: Monthly PDF bundles on a Cron Trigger. If the trigger fires twice for the same period — a retry, a redeploy mid-run — fill and merge are plain stateless calls, so rerunning today's batch doesn't double-bill or double-mail anyone. Idempotent by default →
pdfops.dev
Monthly PDF bundles on a Cron Trigger
Per-customer statement bundles built and delivered on a schedule, no standing server.
000
PDFops @pdfops.dev · 09/09/2026
New on the blog: Auto-fill a W-9 from a webhook. Typeform sends snake_case answer keys; the W-9's AcroForm fields are named nothing like that. The actual work in this pipeline isn't the fill call, it's the field map translating one naming scheme into the other →
pdfops.dev
Auto-fill a W-9 from a webhook
Turn onboarding-form answers straight into a filled W-9, on Vercel Edge.
020
PDFops @pdfops.dev · 08/09/2026
Wrote up why the name is PDFops and not PDF-generator: the product treats documents like infrastructure — stateless calls, idempotent retries, byte-identical output — not like a creative rendering pipeline. That framing decided the entire API surface →
pdfops.dev
Why I built PDFops
An operator's account of why hosted PDF generation needed a rebuild for edge runtimes.
010
PDFops @pdfops.dev · 08/09/2026
DocRaptor is built to lay out HTML/CSS from scratch — great when you're designing the page. A W-9 or a court form isn't a layout problem, it's fields that already have positions; filling them doesn't touch the page at all. Where the two tools actually differ →
pdfops.dev
PDFops vs DocRaptor
Rendering HTML layouts vs filling the fields of a fixed-layout PDF.
010
PDFops @pdfops.dev · 07/09/2026
PDFShift bills for render time because it's booting a browser to lay out HTML on every request. If your input is a PDF that already exists and only the field values change, there's no layout pass to bill for — the cost model doesn't have that variable. Compared →
pdfops.dev
PDFops vs PDFShift
HTML-to-PDF rendering vs deterministic form-fill — different jobs, compared.
010
PDFops @pdfops.dev · 07/09/2026
Filling a PDF from Python doesn't need asyncio, threads, or worrying about the GIL — there's no CPU-bound rendering happening in your process at all. It's one POST and one response, so a sync requests call and an async client cost the same. Both shown → #webdev
pdfops.dev
Fill a PDF in Python
Native libs vs one POST with a JSON field map, compared with working code.
000
PDFops @pdfops.dev · 06/09/2026
An auditor re-running last quarter's compliance report should get the exact same PDF back, byte for byte, given the same source data — not a document that merely looks the same. That's a determinism requirement, not a formatting one. #webdev
pdfops.dev
Fill reports with PDFops
A JSON payload and a template that doesn't change — no re-render per run.
000