API-Überblick
Die Formfeed-API ist eine REST-API mit JSON über HTTPS. Diese Seite beschreibt die Konventionen, die für jeden Endpunkt gelten; die API-Referenz enthält jedes Feld.
Basis-URL
https://api-eu.formfeed.dev/v1
Nutzen Sie diesen Host. Formfeed rendert in Frankfurt und speichert Dateien in der EU, und die SDKs verwenden diesen Host standardmäßig. https://api.formfeed.dev/v1 und https://api-us.formfeed.dev/v1 erreichen heute dasselbe Gateway: Nicht der Host entscheidet, wo ein Dokument gerendert wird, sondern der Workspace Ihres Schlüssels, und die EU ist die einzige Region, die rendert. Siehe Datenresidenz.
Authentifizierung
Senden Sie einen API-Schlüssel als Bearer-Token:
Authorization: Bearer ff_live_3f9c...
X-API-Key: ff_live_… funktioniert ebenfalls, für Clients, die Authorization nicht setzen können. Schlüssel gehören zu einem Workspace und tragen Scopes; ff_test_-Schlüssel rendern kostenlos. Siehe Authentifizierung.
Anfragen und Antworten
- Anfrage-Bodys sind JSON mit
Content-Type: application/json. Feldnamen sindsnake_case. Uploads (Dateien der Bibliothek, Word- und PowerPoint-Vorlagen, umzuwandelnde Dokumente) sindmultipart/form-data. Ein Body, der kein gültiges JSON ist, dem ein Pflichtfeld fehlt oder der einen Wert vom falschen Typ enthält, ergibt400 invalid_request, meist mit einer Listeissues, die zu jedem Problem denpathund einemessagenennt. - Antworten sind JSON. Fehler sind
application/problem+json(RFC 9457) mit einem stabilencode; siehe Fehler. - Renderings, Batch-Jobs, Vorlagen und Dateien der Bibliothek haben IDs mit einem Typpräfix und 32 Hexadezimalzeichen:
rnd_…(Rendering),job_…(Batch-Job),tpl_…(Vorlage),fil_…(Datei der Bibliothek). Webhook-Ereignisse haben IDsevt_…. Webhook-Endpunkte, Workspaces und Listen-Sitzungen haben einfache UUIDs. Überall, wo eine Rendering-, Job-, Vorlagen- oder Datei-ID erwartet wird, können Sie auch die einfache UUID senden, und eine Vorlage auch über ihren Slug (invoice-de). - Zeitstempel sind ISO 8601 in UTC. Je nach Feld stehen sie mit
Zoder+00:00und mit oder ohne Sekundenbruchteile (2026-09-11T10:00:00Z,2026-09-11T10:00:00.4821+00:00). Lesen Sie sie deshalb mit einer Datumsbibliothek, statt Zeichenketten zu vergleichen. - Jede Antwort trägt eine
X-Request-Id, jedes Problem denselben Wert alsrequest_id. Geben Sie sie an, wenn Sie den Support kontaktieren.
Header
Anfrage-Header, die die API liest:
| Header | Wann |
|---|---|
Authorization: Bearer <key> | Jede Anfrage außer GET /v1/openapi.json und der Geräteanmeldung. Siehe Authentifizierung. |
X-API-Key: <key> | Statt Authorization. Werden beide gesendet, zählt Authorization. |
Content-Type | application/json, oder multipart/form-data für Uploads. |
Idempotency-Key | Optional bei jedem POST; siehe Idempotenz. |
X-Formfeed-Workspace | Nur mit einem Sitzungstoken von Formfeed (die App oder ein über den MCP-Server verbundener KI-Agent): die ID des Workspace, für den der Aufruf gilt. Ein API-Schlüssel ignoriert ihn, weil er bereits zu einem Workspace gehört. |
X-Formfeed-Environment: test | Nur die eigene Testanfrage der App (POST /renders mit der Sitzung eines Mitglieds). Ein API-Schlüssel ignoriert ihn: Seine Umgebung ist sein Präfix. |
Antwort-Header, die die API schreibt:
| Header | Bei |
|---|---|
X-Request-Id | Jeder Antwort. |
Idempotent-Replayed: true | Einer gespeicherten Antwort auf einen wiederholten Idempotency-Key. |
RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset | Anfragen, die gegen das Rate-Limit zählen. |
Retry-After | Sekunden, die Sie warten sollen: bei 429, bei 503 und bei jedem 202. |
Location | Jedem 202: das Ergebnis des Renderings bei einem Rendering, /v1/jobs/{id} bei einem Batch. |
X-Units-Charged, X-Units-Remaining, X-Render-Region, X-Render-Engine, X-Render-Deduplicated, Server-Timing | POST /renders und den PDF-Tools; siehe das Rendering-Objekt. |
X-Render-Async-Fallback: true | Einem synchronen Rendering, das in den Hintergrund gewandert ist (der automatische Fallback). |
X-Render-Id, X-Render-Pages, X-Render-Expires-At, X-Formfeed-PDF-UA | Einer Antwort, die die Datei selbst ist (?output=binary). |
WWW-Authenticate | Einem 401, weil kein Schlüssel gesendet wurde. |
Paginierung
GET /renders, GET /templates und GET /files liefern eine Seite und einen Cursor:
{
"data": [ … ],
"next_cursor": "2026-09-11 10:00:00.4821+00|0f3c9a2e-7b1d-4e5f-8a6c-3b2d1e0f9a8b"
}
Übergeben Sie next_cursor als ?cursor=, um die nächste Seite zu erhalten; auf der letzten Seite ist er null. Behandeln Sie ihn als undurchsichtige Zeichenkette und kodieren Sie ihn für die URL, denn er enthält Leerzeichen und andere Zeichen, die eine URL nicht erlaubt. limit setzt die Seitengröße: 1–100 für Renderings und Dateien (ein größerer Wert ergibt 400 invalid_request), 1–200 für Vorlagen (ein größerer Wert wird auf 200 gekürzt), standardmäßig 50. Die all()-Helfer der SDKs (renders.all(), templates.all(), files.all()) folgen dem Cursor für Sie.
curl "https://api-eu.formfeed.dev/v1/renders?status=failed&limit=100" -H "Authorization: Bearer $FORMFEED_API_KEY"
GET /renders nimmt die Filter template (Slug, tpl_…-ID oder UUID), status (queued, rendering, succeeded, failed), environment (live, test), since und until (ISO-8601-Zeitpunkte) und liefert die neuesten zuerst. Webhook-Endpunkte, geteilte Partials, Versionen und Channels einer Vorlage kommen als eine Liste in data, ohne Cursor.
Idempotenz
Netzwerke fallen aus. Senden Sie bei jedem POST einen Idempotency-Key-Header und wiederholen Sie die Anfrage mit demselben Schlüssel, wenn Sie nicht wissen, ob der erste Versuch angekommen ist:
curl https://api-eu.formfeed.dev/v1/renders \
-H "Authorization: Bearer $FORMFEED_API_KEY" \
-H "Idempotency-Key: order-8812-invoice" \
-H "Content-Type: application/json" \
-d '{ "template": "invoice", "data": { … } }'
Die erste JSON-Antwort zu einem Schlüssel wird pro Workspace 24 Stunden gespeichert. Eine Wiederholung mit demselben Schlüssel liefert diese Antwort erneut, mit ihrem Statuscode, ihrem Body und Idempotent-Replayed: true, ohne ein zweites Mal zu rendern oder abzurechnen. Idempotenz-Schlüssel sind bis zu 128 Zeichen lang. Verwenden Sie etwas, das an Ihren eigenen Datensatz gebunden ist (order-8812-invoice), oder eine UUID. Die SDKs senden bei jedem Aufruf, der ein Rendering oder einen Batch anlegt, einen neuen.
Was Sie wissen sollten, bevor Sie sich darauf verlassen:
- Der Schlüssel ist nicht an die Anfrage gebunden. Ein Schlüssel, der innerhalb von 24 Stunden erneut verwendet wird, liefert die erste Antwort, auch für einen anderen Body oder einen anderen Endpunkt. Verwenden Sie für jede neue Anfrage einen neuen Schlüssel.
- Fehler unter 500 werden ebenfalls gespeichert. Eine Anfrage, die mit
402 quota_exceededoder422beantwortet wurde, erhält mit demselben Schlüssel 24 Stunden lang dieselbe Antwort. Haben Sie die Ursache behoben, senden Sie sie mit einem neuen Schlüssel. Serverfehler (5xx) und429 rate_limitedwerden nicht gespeichert, eine Wiederholung versucht es also wirklich erneut. - Die Antwort wird gespeichert, wenn die erste Anfrage beantwortet ist. Eine Wiederholung, die gesendet wird, während der erste Versuch noch läuft, wird nicht erkannt und rendert ein zweites Mal. Warten Sie auf eine Antwort oder auf das Timeout Ihres Clients, bevor Sie wiederholen; ein synchrones Rendering kann bis zu 60 Sekunden dauern, bevor es mit
202antwortet. - Eine Wiederholung ist die Antwort, wie sie war. Ein gespeichertes
202zeigt das Rendering alsqueued; den aktuellen Stand liefertGET /renders/{id}. Eine Wiederholung trägt auch die Header, die die erste Antwort beschreiben:LocationundRetry-Aftereines202,X-Units-*undX-Render-*.X-Units-Chargednennt, was die erste Antwort gekostet hat; die Wiederholung selbst kostet nichts. - Dateien werden nicht wiederholt. Eine Antwort, die die Datei selbst war (
?output=binary), wird nicht gespeichert, eine Wiederholung rendert also erneut. Ein Live-Rendering mit demselben Inhalt kommt dann meist kostenlos aus dem Dedup-Cache.
Wiederholungen
Wiederholen Sie 429 und 503 nach der Anzahl Sekunden in Retry-After und Netzwerkfehler mit exponentiellem Backoff, immer mit demselben Idempotenz-Schlüssel. Wiederholen Sie andere 4xx-Antworten nicht: Sie beschreiben ein Problem mit der Anfrage, das eine Wiederholung nicht behebt. Die SDKs setzen genau diese Regel um (standardmäßig bis zu 3 Wiederholungen, mit der Wartezeit aus Retry-After, sonst 0,5 s und dann jeweils doppelt so lange, höchstens 30 s).
Aus dem Browser
API-Schlüssel gehören auf einen Server. Die API lehnt einen Schlüssel ab, der von einer Webseite kommt: Eine Anfrage mit einem Origin-Header ergibt 403 forbidden („This API key may not be used from a browser origin“). Rufen Sie Formfeed aus Ihrem Backend auf und geben Sie dem Browser das Ergebnis, zum Beispiel die download_url des Renderings. Die einzige Ausnahme sind die Maker-Portale der Power Platform von Microsoft (Power Automate, Power Apps, Copilot Studio, Logic Apps im Azure-Portal), deren Connector-Proxy den Origin des Portals weitergibt; siehe Power Automate.
Versionierung
Die API-Version steht im Pfad (/v1). Innerhalb einer Version sind Änderungen additiv: neue Endpunkte, neue optionale Anfragefelder, neue Antwortfelder, neue Fehlercodes und neue Ereignistypen. Schreiben Sie Clients, die unbekannte Felder ignorieren und einen unbekannten code wie seinen HTTP-Status behandeln. Inkompatible Änderungen erscheinen nur in einer neuen Version. In /v1 ist nichts abgekündigt; eine Abkündigung würde im Änderungsprotokoll angekündigt, bevor sich etwas ändert.
Das OpenAPI-3-Dokument liegt unter https://api.formfeed.dev/v1/openapi.json (ohne Schlüssel abrufbar). Erzeugen Sie daraus einen Client, importieren Sie es in Postman oder Insomnia oder richten Sie Ihre API-Tools darauf aus.
Endpunkte
Alle Pfade unten beginnen mit /v1. „Scope“ ist, was ein API-Schlüssel braucht (Scopes); ein Schlüssel ohne ihn erhält 403 forbidden.
| Endpunkt | Was er tut | Scope |
|---|---|---|
POST /renders | Eine Vorlage, HTML oder eine URL rendern | render:create |
GET /renders, GET /renders/{id} | Renderings auflisten, eines abrufen (mit frischem Download-Link) | render:read |
GET /renders/{id}/result | Das Ergebnis, auf das ein 202 zeigt: 202, solange das Rendering läuft, dann das Rendering, seine Datei oder sein Problem | render:read |
GET /renders/{id}/input | Die aufbewahrte Anfrage eines Renderings, um es nachzustellen | render:input |
DELETE /renders/{id}/outputs | Die gespeicherten Dateien eines Renderings löschen | file:delete |
POST /renders/batch, GET /jobs/{id} | Bis zu 1.000 Renderings als Batch senden, den Job verfolgen | render:create, render:read |
POST /pdf/merge, /pdf/protect, /pdf/watermark | PDF-Renderings nachbearbeiten: zusammenführen, Passwort, Wasserzeichen | render:create |
POST /pdf/convert | Eine Word-, Excel-, PowerPoint-, OpenDocument-, RTF- oder HTML-Datei oder die Ausgabe einer Office-Vorlage in ein PDF-Rendering umwandeln | render:create |
POST /pdf/info | Seitenzahl, Seitengrößen und Metadaten eines PDF-Renderings | render:read |
GET /templates, GET /templates/{id} | Vorlagen auflisten und lesen | template:read |
GET /templates/{id}/versions[/{which}], GET /templates/{id}/schema | Versionen mit ihren Dateien, das Datenschema | template:read |
GET /templates/{id}/versions/{which}/file | Das Dokument einer Version einer Word- oder PowerPoint-Vorlage herunterladen | template:read |
POST /templates/{id}/validate | Eine Version mit Daten prüfen, ohne zu rendern, kostenlos | template:read |
POST /templates, PUT/DELETE /templates/{id} | Vorlagen anlegen, ändern und archivieren | template:write |
POST /templates/{id}/versions, POST …/versions/{n}/publish | Versionen anlegen und veröffentlichen | template:write |
GET /templates/{id}/channels | Die Release Channels der Vorlage | template:read |
PUT/DELETE /templates/{id}/channels/{name}, POST …/channels/{name}/promote, POST …/rollback | Release Channels verschieben, löschen, befördern und zurücksetzen | template:write |
GET /files, GET /files/{id} | Die Dateibibliothek des Workspace auflisten und lesen | file:read |
POST /files, DELETE /files/{id} | In die Dateibibliothek hochladen und daraus löschen | file:write, file:delete |
GET /brand, GET /partials, GET /partials/{name} | Brand Kit und geteilte Partials der Organisation lesen | template:read |
PUT/DELETE /partials/{name} | Geteilte Partials anlegen, ändern und löschen | template:write |
GET/POST /webhooks, PUT/DELETE /webhooks/{id}, POST /webhooks/{id}/test | Webhook-Endpunkte verwalten | webhook:manage |
POST /webhooks/listen, DELETE /webhooks/listen/{id} | Eine Listen-Sitzung starten und beenden | webhook:listen |
POST /webhooks/events/{id}/resend | Ein gespeichertes Ereignis erneut an einen Endpunkt oder eine Listen-Sitzung senden | webhook:manage oder webhook:listen |
GET /account, GET /usage | Workspace, Plan und Verbrauch | account:read |
DELETE /workspaces/{id} | Einen Workspace der Organisation löschen; sein Inhalt wird nach 30 Tagen entfernt | workspace:delete |
GET/PUT/DELETE /workspaces/{id}/storage, POST …/storage/test, POST …/storage/redeliver | Den eigenen Bucket des Workspace verwalten und fehlgeschlagene Kopien erneut senden | keiner: die Sitzung eines Inhabers oder Admins in der App |
POST /auth/device, POST /auth/device/token | Die Geräteanmeldung von formfeed login | keiner: noch kein Schlüssel |
POST /compat/apitemplate/* | Kompatibilität mit apitemplate.io | render:create |
SDKs und Tools
- TypeScript-SDK:
npm install @formfeed/sdk, für Node, Deno, Bun und Cloudflare Workers, auf der Serverseite (nicht aus dem Browser). - Python-SDK:
pip install formfeed, synchron und mit asyncio. - CLI:
npx formfeed, Vorlagen als Code und Renderings aus dem Terminal. - MCP-Server:
npx @formfeed/mcpoder der gehostete Endpunkthttps://mcp.formfeed.dev, für KI-Agenten.