Schnellstart
Am Ende dieser Seite haben Sie ein PDF aus reinem HTML gerendert, dieses HTML als Vorlage gespeichert und die Vorlage mit Ihren eigenen Daten gerendert. Sie brauchen ein Formfeed-Konto und ein Terminal.
1. Test-API-Schlüssel holen
- Melden Sie sich unter app.formfeed.dev an; eine neue Adresse legt das Konto an, mit einer Organisation, deren Inhaber Sie sind, und einem ersten Workspace. Der Free-Plan braucht keine Karte.
- Öffnen Sie API-Schlüssel → Schlüssel anlegen, wählen Sie die Umgebung Test und behalten Sie die voreingestellten Scopes. Schlüssel anlegen dürfen nur Inhaber und Admins; wer das Konto angelegt hat, ist Inhaber.
- Kopieren Sie den Schlüssel. Er beginnt mit
ff_test_und wird nur einmal angezeigt.
export FORMFEED_API_KEY=ff_test_...
Test-Schlüssel kosten nichts: Renderings werden nicht abgerechnet und zählen nicht gegen Ihr Kontingent, Ausgaben laufen nach 24 Stunden ab, und im Free-Plan tragen sie eine kleine Testmarkierung. Nutzen Sie sie, bis Sie in Produktion gehen. Den Unterschied zwischen Test- und Live-Schlüsseln erklären Authentifizierung und API-Schlüssel und Umgebungen.
2. HTML rendern
Noch ist keine Vorlage nötig: Senden Sie HTML und eine Vorlagensprache, und Formfeed setzt die Daten ein.
- curl
- TypeScript
- Python
curl https://api-eu.formfeed.dev/v1/renders \
-H "Authorization: Bearer $FORMFEED_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"html": "<h1>Rechnung {{ number }}</h1><p>Summe: {{ total | money: \"EUR\" }}</p>",
"engine": "liquid",
"data": { "number": "2026-0042", "total": 960 }
}'
npm install @formfeed/sdk
import { writeFile } from 'node:fs/promises';
import { Formfeed } from '@formfeed/sdk';
const client = new Formfeed({ apiKey: process.env.FORMFEED_API_KEY! });
const render = await client.renders.create({
html: '<h1>Rechnung {{ number }}</h1><p>Summe: {{ total | money: "EUR" }}</p>',
engine: 'liquid',
data: { number: '2026-0042', total: 960 },
});
console.log(render.download_url);
await writeFile('invoice.pdf', await client.renders.download(render));
pip install formfeed
import os
from formfeed import Formfeed
client = Formfeed(os.environ["FORMFEED_API_KEY"])
render = client.renders.create(
html='<h1>Rechnung {{ number }}</h1><p>Summe: {{ total | money: "EUR" }}</p>',
engine="liquid",
data={"number": "2026-0042", "total": 960},
)
print(render.download_url)
with open("invoice.pdf", "wb") as f:
f.write(client.renders.download(render))
Die Antwort ist ein Render-Objekt (gekürzt; jedes Feld beschreibt Das Rendering-Objekt):
{
"id": "rnd_7f3c9a1e2b4d4c8f9e0a1b2c3d4e5f60",
"status": "succeeded",
"output": "pdf",
"download_url": "https://cdn.formfeed.dev/o/eu/…?exp=1789293600&sig=…",
"expires_at": "2026-09-13T10:00:00.000Z",
"bytes": 18734,
"page_count": 1,
"units": 0,
"region": "eu",
"environment": "test",
"deduplicated": false,
"timings": {
"queue_ms": 0,
"compile_ms": 4,
"render_ms": 612,
"post_ms": 0,
"upload_ms": 38,
"total_ms": 702
},
"error": null,
"created_at": "2026-09-12T10:00:00.000Z",
"completed_at": "2026-09-12T10:00:00.702Z"
}
Öffnen Sie download_url im Browser, der die Datei speichert (sie wird als Download ausgeliefert), oder laden Sie sie mit curl -o invoice.pdf "<download_url>". Der Link ist signiert und bis zu sieben Tage gültig, aber nie länger, als die Ausgabe selbst aufbewahrt wird: Mit einem Test-Schlüssel sind das 24 Stunden, wie expires_at zeigt. GET /v1/renders/{id} gibt einen frischen Link aus, solange die Datei existiert. units ist 0, weil ein Test-Schlüssel kostenlos rendert; dasselbe Rendering mit einem Live-Schlüssel kostet 1 Einheit.
Stimmt etwas mit der Anfrage nicht, ist die Antwort stattdessen ein Problem, zum Beispiel 401 unauthorized, wenn der Schlüssel fehlt oder falsch ist, oder 422 template_syntax_error mit Zeile und Spalte, wenn ein Vorlagen-Tag wie {{ number nicht geschlossen ist.
3. Als Vorlage speichern
HTML mit jeder Anfrage zu senden funktioniert. Eine gespeicherte Vorlage bringt Ihnen aber Versionierung, eine Live-Vorschau, Autovervollständigung aus Ihren Daten und ein Datenschema, das Integrationen lesen können.
Unter Vorlagen → Beispiele stehen die Vorlagen von formfeed.dev/de/examples bereit: Rechnungen, Zertifikate, Berichte, Etiketten und Social-Media-Bilder, jede mit ihren Beispieldaten. Wählen Sie eine aus: Diese Vorlage verwenden legt eine Kopie im Workspace an, bereits veröffentlicht. Ihr Panel API zeigt dann die Anfrage aus Schritt 4 für diese Kopie, mit ihrem Slug und ihren Beispieldaten.
-
Öffnen Sie in der App Vorlagen → Neue Vorlage. Nennen Sie sie
Invoice; der Slug wirdinvoice. Behalten Sie als Art PDF bei, wählen Sie als Engine Liquid (der Code unten ist Liquid) und klicken Sie auf Vorlage anlegen. -
Der Editor öffnet sich. In der Leiste links ist Code gewählt; ersetzen Sie den Inhalt seines Tabs Vorlage durch:
<h1>Rechnung {{ number }}</h1><table>{% for line in lines %}<tr><td>{{ line.description }}</td><td>{{ line.qty }} × {{ line.price | money: "EUR" }}</td></tr>{% endfor %}</table><p><strong>Summe: {{ lines | sum: "total" | money: "EUR" }}</strong></p>(Das ist Liquid. Die Seite Vorlagensprachen zeigt dasselbe in Jinja2 und Handlebars. Liquid gibt Werte aus, ohne HTML zu maskieren; wo ein Wert von Ihren Nutzern stammt, schreiben Sie
{{ line.description | escape }}.) -
Öffnen Sie in der Leiste Daten und Schema und fügen Sie diese Beispieldaten ein. Vorschau und Autovervollständigung nutzen sie; ein API-Rendering nie, es verwendet immer die Daten der Anfrage:
{"number": "2026-0042","lines": [{ "description": "Beratung", "qty": 8, "price": 120, "total": 960 }]} -
Drücken Sie
Strg+S(⌘S), ergänzen Sie auf Wunsch eine Notiz und bestätigen Sie mit Version speichern; klicken Sie dann auf Veröffentlichen. Die API rendert standardmäßig die veröffentlichte Version.
4. Die Vorlage rendern
Jetzt enthält die Anfrage nur noch den Slug und die Daten:
- curl
- TypeScript
- Python
curl https://api-eu.formfeed.dev/v1/renders \
-H "Authorization: Bearer $FORMFEED_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"template": "invoice",
"data": { "number": "2026-0043", "lines": [{ "description": "Workshop", "qty": 1, "price": 1500, "total": 1500 }] },
"filename": "invoice-2026-0043.pdf"
}'
const data = {
number: '2026-0043',
lines: [{ description: 'Workshop', qty: 1, price: 1500, total: 1500 }],
};
const render = await client.renders.create({
template: 'invoice',
data,
filename: `invoice-${data.number}.pdf`,
});
await writeFile(
`invoice-${data.number}.pdf`,
await client.renders.download(render),
);
data = {"number": "2026-0043", "lines": [{"description": "Workshop", "qty": 1, "price": 1500, "total": 1500}]}
render = client.renders.create(
template="invoice",
data=data,
filename=f"invoice-{data['number']}.pdf",
)
with open(f"invoice-{data['number']}.pdf", "wb") as f:
f.write(client.renders.download(render))
filename ist der Name, unter dem die Datei gespeichert und heruntergeladen wird. Er wird übernommen, wie er ist, und nicht mit den Daten gefüllt; bauen Sie ihn deshalb in Ihrem Code. Zeichen außer Buchstaben, Ziffern, ., -, _, (, ) und Leerzeichen werden zu _, und die Endung der Ausgabe wird ergänzt, wenn sie fehlt.
Scheitert die Vorlage, antwortet die API mit 422, dem Problem und der Zeile, in der es aufgetreten ist, statt ein kaputtes PDF zu liefern: template_syntax_error für ein Tag, das sich nicht lesen lässt, template_runtime_error für eines, das mit Ihren Daten scheitert. Ein Wert, der in Ihren Daten fehlt, lässt das Rendering nicht scheitern; er wird leer ausgegeben. Sollen die Daten vorher geprüft werden, geben Sie der Vorlage ein Datenschema und senden "validate_data": true; das antwortet mit 422 data_validation_error und allen Abweichungen, bevor etwas gerendert oder gezählt wird.
Öffnen Sie die Vorlage und klicken Sie in der Leiste des Editors auf API. Das Panel zeigt diese Anfrage mit dem Slug der Vorlage in acht Sprachen, sagt, welche Version ein Aufruf rendert, und Testanfrage senden führt sie aus und zeigt die Antwort. Siehe den Editor-Rundgang.
Nächste Schritte
- Die Vorlage produktionsreif machen: Papierformat, Ränder, Kopf- und Fußzeilen mit Seitenzahlen, Schriften und Bilder behandelt Vorlagen schreiben.
- Die Integration absichern: Geben Sie jedem Rendering einen
Idempotency-Keymit, behandeln Sie429mitRetry-Afterund verlagern Sie lange Dokumente auf asynchrone Renderings mit Webhooks. Die SDKs erledigen die ersten beiden Punkte für Sie. - Vorlagen in Git halten: Nach
npx formfeed loginholtnpx formfeed init my-templates --from-workspaceIhre veröffentlichten Vorlagen in einen Ordner mit lokaler Vorschau und Hot-Reload. Siehe Vorlagen als Code. - Live gehen: Legen Sie einen Schlüssel in der Umgebung Live an und tauschen Sie ihn aus. Sonst ändert sich nichts, außer dass Renderings nun Einheiten kosten und ihre Dateien so lange aufbewahrt werden, wie Ihr Plan vorsieht, statt 24 Stunden.
- Ein vollständiges Programm übernehmen: Die Guides je Sprache enthalten je Sprache eines, das seine Daten von der Platte liest, die Fehlerantwort behandelt und das PDF speichert, für Node.js, Python, PHP, Go, C#, Java, Ruby und curl.