API-Referenz

Collections

Collections sind eigene, frei definierbare Datenstrukturen (Blog, Team, Produkte, FAQ …), die in Templates über data("slug") verwendet werden.

Überall, wo {ref} steht, kannst du ID oder Slug verwenden: /collections/3/items ≙ /collections/blog/items.

Collection-Schema

{
  "id": 1,
  "slug": "blog",
  "name": "Blog",
  "folder": "Website A",
  "public_api": true,
  "live": false,
  "singleton": false,
  "detail_page_id": 7,
  "detail_field": "slug",
  "lang": "de",
  "fields": [
    { "name": "titel", "label": "Titel", "type": "text", "required": true },
    { "name": "slug",  "label": "URL",   "type": "text", "required": true },
    { "name": "datum", "label": "Datum", "type": "date", "required": false },
    { "name": "bild",  "label": "Bild",  "type": "image", "required": false },
    { "name": "text",  "label": "Text",  "type": "markdown", "required": false },
    { "name": "kategorie", "label": "Kategorie", "type": "select", "required": false, "options": ["News", "Technik"] },
    { "name": "autor", "label": "Autor", "type": "reference", "required": false, "collection": 2 },
    { "name": "tags", "label": "Tags", "type": "references", "required": false, "collection": 3 }
  ],
  "created_at": 1791191665,
  "updated_at": 1791191665,
  "item_count": 12
}
Feld Beschreibung
slug a-z0-9_-, beginnt mit Buchstabe. Eindeutig über Collections und REST-Quellen
folder Ordner in der Admin-Liste, nur zur Übersicht (leer = ohne)
public_api true = Lese-Endpunkte ohne Authentifizierung erreichbar (nur veröffentlichte Einträge), auch cross-origin (CORS: Access-Control-Allow-Origin: *)
live true = Live-Daten: Die Einträge entstehen auf dem Live-Server (z. B. per Workflow aus einem Formular) und bleiben beim Einspielen eines Bundles erhalten
singleton true = genau ein Eintrag (Website-Einstellungen, Farbcodes …): GET …/items, /data/{slug} und data() im Template liefern ihn als Objekt (bzw. null) statt als Liste. Ein zweiter Eintrag (auch per Batch) gibt 409, der CSV-Import 400; anlegen und ändern am einfachsten mit PUT …/item. Umschalten geht nur mit höchstens einem Eintrag (409)
detail_page_id Detailseite: ID einer (Catch-All-)Seite, unter der die Einträge erscheinen – Pfad = Seite + / + Wert von detail_field. Das Admin zeigt beim Bearbeiten eines Eintrags die Vorschau dieser Seite. 0 = keine; unbekannte Seite: 400
detail_field Feld mit dem URL-Teil (Standard slug, muss ein Feld sein – sonst 400)
lang Sprache aller Einträge (BCP 47, z. B. de, en; leer = ohne Sprache). Voraussetzung, um Einträge als Sprachfassungen zu verknüpfen
fields[].name Feldname im Template (item.titel): [a-zA-Z_][a-zA-Z0-9_]*, eindeutig, nicht id
fields[].label Anzeigename im Admin (Standard: name)
fields[].required Pflichtfeld
fields[].options Erlaubte Werte bei select
fields[].collection Bei reference/references: ID der verknüpften Collection (muss existieren, auch die eigene)
item_count Anzahl Einträge (nur lesend)

Feldtypen

Typ Gespeichert als Hinweise
text string
textarea string Mehrzeilig
html string Im Template mit |safe ausgeben
markdown string Markdown-Quelltext; im Template mit |markdown als HTML ausgeben. Die API liefert den Quelltext
number number Strings wie "3.5" werden konvertiert
boolean bool Fehlt der Wert, wird false gespeichert
date string Empfohlen: ISO YYYY-MM-DD (sortierbar)
url string
select string Muss einer der options sein (wenn welche definiert sind)
image number Datei-ID → im Template {{ image(item.bild) }}
file number Datei-ID → im Template file(item.datei).url
reference number Eintrags-ID der Collection collection. Auch als String
references number[] Liste von Eintrags-IDs, auch als String "1, 2". Doppelte fallen weg, die Reihenfolge bleibt
blocks array Blockbaum (auch als JSON-Text). Beim Anlegen und bei Teil-Updates, die das Feld enthalten, streng geprüft (400 mit block.*-Code und params.field). Im Template {% blocks item.feld %}. CSV-Export als JSON-Text; beim Import werden ungültige Bäume mit Warnung leer gelassen

Die Items-Endpunkte liefern Verknüpfungen als IDs. Im Template (data(), item()) und über GET /data/{slug} sind sie aufgelöst: Statt der ID steht der verknüpfte Eintrag da, eine Ebene tief. Gelöschte und, ohne Entwürfe, unveröffentlichte Einträge fallen weg. Filter vergleichen die IDs; bei references bedeutet Gleichheit „enthält“ (data("blog", "tags", 3)), __ne „enthält nicht“. Der CSV-Import nimmt IDs oder den Text eines Ziel-Eintrags (bevorzugt slug), der Export schreibt IDs mit Komma.

Leere Werte ("", null, fehlend) werden als null gespeichert. Felder, die nicht im Schema stehen, werden verworfen.

Endpunkt Zugriff Beschreibung
GET /collections 🔑 Alle Collections
POST /collections ✏️ Anlegen → 201
GET /collections/{ref} 🔑 / 🌐 bei public_api Einzelne Collection
PUT /collections/{ref} ✏️ Merge-Update. fields wird – wenn angegeben – vollständig ersetzt
DELETE /collections/{ref} ✏️ Löscht Collection mit allen Einträgen → 204

Hinweis zum Ändern des Schemas: Vorhandene Einträge werden nicht migriert. Werte umbenannter oder entfernter Felder bleiben gespeichert, bis der Eintrag das nächste Mal gespeichert wird – dann werden sie verworfen.

Ohne Authentifizierung liefern nicht-öffentliche Collections 404 (nicht 401), damit ihre Existenz nicht erkennbar ist.

Einträge

Eintrag-Objekt

{
  "id": 5,
  "collection_id": 1,
  "data": {
    "titel": "Hallo Welt",
    "slug": "hallo-welt",
    "datum": "2026-10-05",
    "bild": 7,
    "text": "Erster **Beitrag**",
    "kategorie": "News"
  },
  "sort": 0,
  "published": true,
  "translation_group": "",
  "created_at": 1791191665,
  "updated_at": 1791191665
}

GET /collections/{ref}/items 🔑 / 🌐

Alle Einträge in manueller Reihenfolge (sort). Mit Authentifizierung inklusive Entwürfen, öffentlich nur published: true. Bei einem Singleton der eine Eintrag als Objekt (oder null).

Für Filtern, Sortieren und Limitieren nutze GET /data/{slug}.

GET /collections/{ref}/items/{id} 🔑 / 🌐

Einzelner Eintrag. Öffentlich: Entwürfe liefern 404.

POST /collections/{ref}/items ✏️

{
  "data": { "titel": "Hallo Welt", "slug": "hallo-welt", "bild": 7 },
  "published": true
}

published ist optional (Standard true). Der Eintrag wird ans Ende gehängt. 201, 400 bei Validierungsfehlern, z. B. field "Titel" is required.

Mehrere Einträge auf einmal: Statt eines Objekts ein Array schicken (max. 1000 Einträge je Anfrage):

[
  { "data": { "titel": "Erster", "slug": "erster" } },
  { "data": { "titel": "Zweiter", "slug": "zweiter" }, "published": false }
]

Alles oder nichts: Ist ein Eintrag ungültig, wird keiner angelegt, die Meldung nennt den Index (ab 0), z. B. item 1: field "Titel" is required. Die Einträge landen in der Reihenfolge des Arrays am Ende. Antwort 201 mit dem Array der angelegten Einträge (inkl. id). Der Seiten-Cache wird einmal geleert, das Ereignis created kommt wie beim Einzel-POST je Eintrag. Ein leeres oder zu großes Array liefert 400.

PUT /collections/{ref}/items/{id} ✏️

Teil-Update: Nur die in data angegebenen Felder werden geändert, der Rest bleibt erhalten.

{ "data": { "titel": "Neuer Titel" }, "published": false }

GET /collections/{ref}/export 🔑

Alle Einträge (inkl. Entwürfe) als CSV, Content-Disposition: attachment; filename="<slug>-<datum>.csv". Spalten: id, published, dann die Felder in Schema-Reihenfolge (Kopfzeile = Feldnamen). UTF-8 mit BOM, Komma als Trennzeichen; Ja/Nein als true/false, Zahlen mit Punkt, Bilder/Dateien als Datei-ID.

POST /collections/{ref}/import ✏️

CSV-Import in eine bestehende Collection. multipart/form-data:

Feld
file CSV-Datei (UTF-8, optional mit BOM, max. 32 MB / 50 000 Zeilen). Trennzeichen , ; oder Tab wird aus der Kopfzeile erkannt.
mapping optional, JSON-Array mit einem Eintrag je Spalte: Feldname, "" (ignorieren), "_published" (ja/nein → veröffentlicht), "_draft" (ja/nein → Entwurf) oder "_id" (Eintrags-ID, nur für key=_id). Ohne: Zuordnung nach Feldname/Bezeichnung, Spalte id → _id, Webflow-Spalten Draft/Archived → _draft.
key optional, Abgleich: ein zugeordnetes Feld (z. B. slug) oder _id. Zeilen, deren Wert schon ein Eintrag hat, aktualisieren ihn (nur die zugeordneten Felder; Status nur mit Status-Spalte), sonst neuer Eintrag. Leerer Wert → neuer Eintrag. Fehler: Wert doppelt in der Datei, Wert bei mehreren Einträgen, unbekannte _id. Ohne key werden alle Zeilen angehängt.

Query: dry_run=1 (nichts schreiben – liefert Spalten, Zuordnung, Vorschau, Fehler und die Zähler), replace=1 (ohne key: alle bisherigen Einträge löschen; mit key: nur die, die in der Datei nicht vorkommen – die übrigen behalten ihre ID). Alles in einer Transaktion; neue Einträge werden vor dem Löschen angelegt, damit keine gelöschte ID neu vergeben wird.

{
  "delimiter": ";", "rows": 2, "columns": [{ "name": "Titel", "samples": ["Yoga", "Pilates"] }],
  "mapping": ["titel", "preis", "_published", ""], "key": "titel",
  "preview": [{ "data": { "titel": "Yoga", "preis": 1234.5 }, "published": true, "action": "update", "id": 12 }],
  "errors": [], "error_count": 0,
  "warnings": [{ "row": 3, "column": "Bild", "message": "no file for \"https://…\"", "code": "csv.noFileFor", "params": { "value": "https://…" } }], "warning_count": 1,
  "created": 1, "updated": 1, "deleted": 0, "imported": 2
}

Umwandlung je Feldtyp: Zahl (1234.5, 1.234,5, 1,234.5; nur Komma = Dezimalkomma), Ja/Nein (true/false, ja/nein, yes/no, 1/0, x), Datum → JJJJ-MM-TT (ISO, TT.MM.JJJJ, RFC 1123, Webflow-Format), Bild/Datei (Datei-ID oder Quell-URL einer importierten Datei – sonst leer mit Warnung), sonst Text. Danach gelten die üblichen Prüfungen (Pflichtfelder, Auswahl-Optionen). row ist die Zeile in der Datei (Kopfzeile = 1), leere Zeilen werden übersprungen.

Ohne dry_run: alles oder nichts in einer Transaktion – bei Fehlern 400 mit error (Code csv.rowError/csv.cellError bei einem Fehler, sonst csv.errors), errors und error_count. Einträge in errors/warnings tragen code und params, soweit vorhanden. Erfolg 200 mit created, updated, deleted und imported (= neu + aktualisiert). Löst keine created-Ereignisse aus (anders als POST …/items). Probeläufe leeren den Seiten-Cache nicht.

POST /collections/import ✏️

Neue Collection aus einer CSV-Datei, sonst wie oben. Formularfelder: file, name (nur für den Vorschlag), optional collection (JSON wie bei POST /collections: name, slug, fields) und mapping. Ohne collection schlägt der Server Slug, Feldnamen und Typen vor (Antwortfeld collection) – Typen aus allen Werten der Spalte: Ja/Nein, Zahl (ohne führende Null), Datum, URL, HTML, Mehrzeiliger Text, Text; Webflow-Verwaltungsspalten und eine Spalte id werden ignoriert. Mit dry_run=1 nur Vorschlag und Vorschau; ohne legt der Server Collection und Einträge an (201, collection enthält die neue id). key und replace gibt es hier nicht.

curl -X POST -H "Authorization: Bearer $TOKEN" -F file=@blog.csv -F name=Blog "https://example.com/api/v1/collections/import"

POST /collections/{ref}/items/reorder ✏️

Setzt die Reihenfolge. Body: Array der Eintrags-IDs in gewünschter Reihenfolge. Nicht aufgeführte Einträge behalten ihren sort-Wert. 204.

[5, 2, 9, 1]

DELETE /collections/{ref}/items/{id} ✏️

204.

PUT /collections/{ref}/item ✏️

Nur für Singletons: legt den einen Eintrag an oder ändert ihn (Teil-Update wie bei PUT …/items/{id}, mit base_updated_at Konfliktprüfung). Body { "data": { "primaerfarbe": "#7c5cff" }, "published": true }, Antwort der Eintrag. Bei anderen Collections 400.

Sprachfassungen von Einträgen

Wie bei Seiten: GET/POST/DELETE /collections/{ref}/items/{id}/translations, verknüpfen mit { "collection": "blog-en", "item_id": 12 } (collection = ID oder Slug, fehlt = dieselbe Collection; optional "move": true). Die Sprache eines Eintrags ist die seiner Collection (lang) – eine Collection hat eine Sprache, für mehrere Sprachen legst du getrennte Collections an (blog + blog-en). Einträge aus Collections ohne Sprache lassen sich nicht verknüpfen (400 translation.noLang). url/path zeigen auf die Detailseite der Collection (detail_page_id + Wert von detail_field), ohne Detailseite leer. Ändern der Collection-Sprache prüft die Gruppen ihrer Einträge (409 translation.langTaken, Entfernen bei verknüpften Einträgen 409 translation.collectionLinked).