Feature
The rendering API
One endpoint takes a template and JSON and answers with a signed URL. Everything around it exists so that endpoint keeps working under load, on retries and a year from now.
Free plan with 100 units a month, no credit card

Sync, async and batch
A single invoice comes back in the response. A hundred-page report is queued and reported through a webhook. A thousand certificates go into one batch that ends in a zip.
curl -X POST https://api-eu.formfeed.dev/v1/renders/batch \
-H "Authorization: Bearer ff_live_..." \
-H "Content-Type: application/json" \
-d '{"template": "certificate", "zip": true, "items": [{"data": {"name": "Jane Doe"}}]}'Built for retries
- Idempotency keys. A request repeated with the same Idempotency-Key within 24 hours gets the original answer instead of a second render.
- Deduplication. A live request identical to one rendered in the last 24 hours is answered with that file at zero units. Test keys,
urlrenders, post-processing and batch items always render. - Rate limit headers. Requests that start work, such as renders, batches and PDF tools, answer with the limit, the remainder and the reset; a 429 carries Retry-After. The limit applies per workspace and follows the plan.
- Problem details. Errors follow RFC 9457 with a stable code and, for template errors, the line and column.
- Validation first. With validate_data the request is checked against the template’s stored schema and refused with a list of errors before anything is rendered or counted.
PDF tools in the same request
A render’s post block merges further PDFs after the document, stamps a text or image watermark and encrypts the result with AES-256; the same tools run on stored outputs through their own endpoints, and a convert endpoint turns Word, Excel, PowerPoint and OpenDocument files into PDF (office conversion). Images come out as PNG, JPG or WebP. Post-processing and conversion are on Starter and above.
Webhooks you can verify
Endpoints receive signed events for completed and failed renders and batches and, when they ask for them, quota warnings. The signature is an HMAC over the raw body with a timestamp, and both SDKs verify it in one call. A delivery is tried up to six times over about 14.5 hours, the app shows each one with its status and attempts, and the command line tool forwards events to localhost while you build the receiver. A receiver that checks the signature in constant time is written out for Go, PHP, C#, Java and Ruby.
Batches from a spreadsheet
Not every batch starts in code. Upload a CSV or Excel file in the app, map its columns to the template’s fields and download the zip.

Reproducible output
A render records the template version with its checksum, the Chromium build and the SHA-256 of the file. The same inputs on the same Chromium build give the same document; the file carries the time it was made, so each render has a hash of its own. Kept beside an archived invoice, the two checksums prove which template version made it and that the file was not changed since. Details are in the rendering documentation.
Copies in your own bucket
On Pro and above, a workspace can connect an Amazon S3, Cloudflare R2 or S3-compatible bucket (Hetzner, IONOS, Scaleway, Wasabi, Backblaze B2, MinIO, Google Cloud Storage), and every document lands there too, under a key template you choose or the key a request names. Formfeed only ever writes to the bucket and never reads or deletes in it, and it tests the key before storing it. Passing errors are retried in the background; after five failures in a row that a retry cannot fix, such as a revoked key, the connection pauses and the owners and admins get an email. Once the key works again, Retry failed deliveries sends the missed documents that Formfeed still keeps. Its own copy stays for the workspace retention, so download links keep working. Setup and permissions are in the storage documentation.
Clients
TypeScript and Python SDKs, a command line tool, an OpenAPI document and a Model Context Protocol server for AI agents. All of them talk to the same endpoints, so nothing is exclusive to one client. Without an SDK the API is one POST with a JSON body: the language guides have complete programs for PHP, Go, C#, Java, Ruby and curl beside Node.js and Python.