Zum Hauptinhalt springen

Dateien

Jeder Workspace hat eine Dateibibliothek: die Bilder, die eine Vorlage zeigt, und die PDFs, die sie zusammenführt. Eine Datei wird über ihren Namen adressiert, nicht über eine ID. Eine Vorlage schreibt also

<img src="{{ asset('logo.png') }}" alt="" />

und funktioniert weiter, wenn Sie ein neues logo.png hochladen. asset() liefert die öffentliche CDN-URL der Datei. Auch relative Pfade werden gegen die Bibliothek aufgelöst: <img src="logo.png"> in einer Vorlage oder in html, das Sie als Fragment senden, findet dieselbe Datei. (Ein vollständiges HTML-Dokument, das mit <!doctype oder <html> beginnt, bekommt diese Basis nicht; verwenden Sie dort die vollständige URL.)

Die Bibliothek nimmt nur Bilder und PDFs auf. Word- und PowerPoint-Dateien gehören woandershin: Eine Word- oder PowerPoint-Vorlage bewahrt ihr Dokument mit der Version der Vorlage auf, und ein Dokument, das Sie in PDF umwandeln, wird gar nicht gespeichert.

Hochladen​

curl -X POST https://api.formfeed.dev/v1/files \
-H "Authorization: Bearer $FORMFEED_API_KEY" \
-F file=@logo.png
{
"id": "fil_5b0000000000400080000000000000a1",
"name": "logo.png",
"content_type": "image/png",
"bytes": 8421,
"sha256": "3f786850e387550fdab836ed7e6dc881de23001b…",
"url": "https://cdn.formfeed.dev/a/f0000000-0000-4000-8000-000000000001/logo.png",
"created_at": "2026-09-12T10:00:00Z",
"updated_at": "2026-09-12T10:00:00Z"
}

Die Anfrage ist multipart/form-data mit zwei Teilen:

TeilPflichtBedeutung
filejaDie Datei. Ihr Content-Type kommt aus dem Typ des Teils, wenn die Bibliothek ihn annimmt, sonst aus der Dateiendung des Namens.
nameneinDer Name in der Bibliothek. Ohne ihn gilt der Name der hochgeladenen Datei, bereinigt: Jedes Zeichen, das ein Name nicht enthalten darf, wird zu -. Ein name, den Sie senden, muss bereits gültig sein (sonst 400 invalid_request).

Ein Name hat bis zu 200 Zeichen aus Buchstaben, Ziffern, Punkt, Bindestrich und Unterstrich, mit / für Ordner (brand/header.svg); jeder Teil zwischen den Schrägstrichen beginnt mit einem Buchstaben oder einer Ziffer. Groß- und Kleinschreibung zählt: Logo.png und logo.png sind zwei Dateien.

Die Antwort beschreibt die Datei: id (fil_…, zum Lesen und Löschen), name, content_type, bytes, den sha256 des Inhalts, die öffentliche url sowie wann sie angelegt und zuletzt ersetzt wurde.

Derselbe Name ersetzt die vorhandene Datei und antwortet mit 200 statt 201. ID, URL und jeder Verweis in jeder Vorlage bleiben gleich; Vorlagen übernehmen ein neues Logo also ohne neue Version. Die neue Datei erreicht aber nicht sofort jedes Dokument; siehe Caching.

TypenPNG, JPEG, WebP, GIF, SVG, AVIF, PDF
Größe10 MB pro Datei (darüber 413 file_too_large)
Anzahl1.000 Dateien pro Workspace, in jedem Plan; eine weitere ergibt 400 invalid_request mit "feature": "files". Ersetzen ist immer möglich.
PläneAlle Pläne
Scopesfile:read zum Auflisten und Lesen, file:write zum Hochladen (schließt file:read ein), file:delete zum Löschen. Keiner davon ist ein Standard-Scope: Wählen Sie sie beim Anlegen des Schlüssels (Scopes).
In der AppDie Seite Dateien. Editoren laden hoch und ersetzen, Inhaber und Admins löschen, Betrachter sehen die Liste.
EinheitenKostenlos. Uploads werden nicht gezählt.

Eine Datei, deren Bytes nicht zum Content-Type passen, wird mit 400 invalid_request abgelehnt: Die Bibliothek wird über das CDN ausgeliefert, eine „HTML-Seite namens logo.png“ darf dort nicht hinein. Eine leere Datei und ein Typ außerhalb der Liste werden genauso abgelehnt.

Schriften der Organisation gehören nicht in diese Bibliothek. Sie haben ihre eigene Seite in der App, weil eine Schrift an der Organisation hängt und in jedes Rendering eingebaut wird (siehe Vorlagen schreiben).

Mit den SDKs​

import { readFile } from 'node:fs/promises';
import { Formfeed } from '@formfeed/sdk';

const formfeed = new Formfeed({ apiKey: process.env.FORMFEED_API_KEY! });

const logo = await formfeed.files.upload({
data: await readFile('logo.png'),
name: 'brand/logo.png',
});
console.log(logo.url);
import os

from formfeed import Formfeed

client = Formfeed(os.environ["FORMFEED_API_KEY"])

with open("logo.png", "rb") as f:
logo = client.files.upload(f.read(), "brand/logo.png")
print(logo.url)

Beide SDKs senden den übergebenen Namen als name; er muss also ein gültiger Name der Bibliothek sein. Außerdem gibt es files.list(), files.all() (alle Seiten), files.get(id) und files.delete(file).

Im Post-Processing​

Ein PDF aus der Bibliothek ist eine Merge-Quelle, ein PNG oder JPEG ein Wasserzeichen:

{
"template": "invoice",
"data": { "...": "..." },
"post": {
"merge_after": ["terms.pdf"],
"watermark": { "image": "draft-stamp.png", "opacity": 0.15 }
}
}

Benennen Sie Dateien der Bibliothek hier über ihren Namen oder ihre Datei-ID (fil_…); dieselben Felder nehmen auch Rendering-IDs (rnd_…) früherer Renderings. Merges nehmen nur PDFs, Wasserzeichen nur PNG und JPEG; ein anderer Typ ergibt 422 source_invalid, eine Datei, die nicht in der Bibliothek liegt, 422 source_not_found. Siehe PDF-Tools.

Auflisten, lesen und löschen​

curl "https://api.formfeed.dev/v1/files?prefix=brand/&limit=50" \
-H "Authorization: Bearer $FORMFEED_API_KEY"

curl https://api.formfeed.dev/v1/files/fil_5b0000000000400080000000000000a1 \
-H "Authorization: Bearer $FORMFEED_API_KEY"

curl -X DELETE https://api.formfeed.dev/v1/files/fil_5b0000000000400080000000000000a1 \
-H "Authorization: Bearer $FORMFEED_API_KEY"
Query-ParameterStandardBedeutung
prefixkeinerNur Namen, die damit beginnen, etwa ein Ordner: brand/.
limit50Seitengröße, 1 bis 100 (außerhalb davon 400 invalid_request).
cursorkeinerDer next_cursor der vorherigen Seite.

Die Liste antwortet mit { "data": [ … ], "next_cursor": … } und beginnt mit der neuesten Datei; auf der letzten Seite ist next_cursor null. GET /files/{id} liest eine Datei, DELETE /files/{id} löscht sie, beide über ihre fil_…-ID, nicht über den Namen; eine unbekannte ID ergibt 404 not_found. Löschen antwortet mit 204 und entfernt die Bytes sofort aus dem Speicher. Vorlagen, die den Namen verwenden, zeigen danach ein fehlendes Bild, sobald die Caches unten die Datei losgelassen haben.

Caching​

Eine Datei wird an zwei Stellen zwischengespeichert, beide Male unter ihrer URL, die sich beim Ersetzen nicht ändert:

  • Das CDN antwortet mit Cache-Control: public, max-age=3600. Browser und Edge halten eine Datei bis zu eine Stunde: Eine Ersetzung erreicht alle Betrachter, die Vorschau im Editor eingeschlossen, innerhalb dieser Stunde, und eine gelöschte Datei kann bis zum Ablauf dort noch ausgeliefert werden.
  • Der Render-Worker hält die Bilder, die ein Dokument lädt (über asset(), einen relativen Pfad oder ein <img> mit der URL der Bibliothek), bis zu eine Woche in seinem eigenen Cache. Ein ersetztes oder gelöschtes Bild kann also bis zu eine Woche lang noch in gerenderten Dokumenten erscheinen.

Wasserzeichenbilder und Merge-Quellen werden direkt aus dem Speicher gelesen und sind immer aktuell. Muss eine Änderung schon im nächsten Dokument sichtbar sein, laden Sie die Datei unter einem neuen Namen hoch (logo-2026.png) und stellen die Vorlage darauf um.

Was die Bibliothek nicht ist​

vorsicht

Dateien der Bibliothek werden ohne Signatur ausgeliefert: Wer die URL hat, kann sie abrufen, und die URL besteht aus der ID Ihres Workspaces und dem Namen der Datei. Legen Sie Kundendokumente nicht dort ab — dafür gibt es die gerenderten Ausgaben mit signierten, ablaufenden Links.

Das CDN liefert Dateien der Bibliothek mit Content-Security-Policy: sandbox aus, sodass eine direkt geöffnete SVG- oder andere Datei kein Skript ausführt, und mit Access-Control-Allow-Origin: *, sodass jede Seite sie laden darf.