API-Referenz

Grundlagen

Authentifizierung

Es gibt zwei Wege, sich zu authentifizieren:

Methode Für wen Wie
API-Token Externe Clients, Skripte, Sync-Jobs, Headless-Frontends Header Authorization: Bearer ic_… oder bei GET ?token=ic_…
Session-Cookie Die Admin-App im Browser Cookie ic_session nach POST /auth/login

API-Tokens werden im Admin unter Einstellungen → API-Tokens oder per POST /tokens angelegt. Der Klartext-Token wird nur einmal bei der Erstellung angezeigt; gespeichert wird nur ein SHA-256-Hash.

curl -H "Authorization: Bearer ic_Xy…" https://example.com/api/v1/pages

Für einfache GET-Abrufe (Browser-Adresszeile, Tools ohne Header-Unterstützung) geht der Token auch als URL-Parameter:

curl "https://example.com/api/v1/collections/faq/items?token=ic_Xy…"

?token= gilt nur für GET/HEAD – schreibende Anfragen brauchen den Header. Achtung: URLs landen in Browser-Verlauf und Proxy-/Webserver-Logs; dafür am besten einen Nur-Lese-Token verwenden. Ein Token in der URL macht Anfragen auch cross-origin authentifiziert: Bei öffentlichen Collections liefert ein fremdes Frontend mit ?token= dann auch Entwürfe.

Wird ein Authorization: Bearer-Header gesendet, zählt nur er: Ist der Token ungültig, gilt die Anfrage als nicht authentifiziert (geschützte Endpunkte → 401), auch wenn zusätzlich ein gültiges Cookie mitkommt.

Session-Cookies sind HttpOnly und SameSite=Lax, ihre Gültigkeit beträgt 14 Tage. Mit dem Startparameter -secure-cookies (bzw. IC_SECURE_COOKIES=1) wird zusätzlich das Secure-Flag gesetzt – hinter HTTPS unbedingt aktivieren.

CSRF-Schutz

Alle schreibenden Anfragen (POST, PUT, DELETE), die über ein Session-Cookie authentifiziert sind, müssen den Header

X-IC-CSRF: 1

mitsenden (der Wert ist beliebig, er darf nur nicht leer sein). Fehlt er, antwortet der Server mit 403 missing X-IC-CSRF header. Browser senden eigene Header cross-origin nur nach einem CORS-Preflight, den der Server nicht beantwortet – dadurch sind Cookie-Sessions gegen CSRF geschützt.

Anfragen mit API-Token brauchen diesen Header nicht.

Berechtigungen

Jeder Endpunkt hat eine der folgenden Zugriffsstufen:

Stufe Wer darf In dieser Doku
öffentlich jeder, ohne Login 🌐
lesen Admin-Session oder beliebiger API-Token 🔑
schreiben Admin-Session oder API-Token mit can_write ✏️
nur Session eingeloggter Admin im Browser (kein Token) 👤

Lese-Tokens, die einen schreibenden Endpunkt aufrufen, erhalten 403 token is read-only. Das gilt auch für nicht öffentliche HTTP-Eingänge der Workflows (/flows/… mit POST/PUT/PATCH/DELETE; GET bleibt erlaubt), für die Vorschau-Endpunkte (POST /pages/{id}/preview, POST /pens/{uuid}/preview) sowie für Läufe und Versionen von Workflows und eigenen Nodes. GET /sources liefert Lese-Tokens die Header-Namen ohne Werte. Öffentliche HTTP-Eingänge übernehmen X-IC-Webhook-Hop nur zusammen mit einer X-IC-Delivery-ID einer eigenen Zustellung; ihre Fehlerantwort an Anonyme ist 500 mit workflow.failed ohne Details. Nicht öffentliche GET-Eingänge lehnt der Server bei Sitzungs-Cookie und Sec-Fetch-Site: cross-site|same-site ab.

Collections können zusätzlich als öffentlich lesbar markiert werden (public_api: true). Dann sind ihre Lese-Endpunkte ohne Authentifizierung erreichbar, liefern aber nur veröffentlichte Einträge (siehe Collections).

Formate & Konventionen

  • Request- und Response-Bodies sind JSON (Content-Type: application/json), außer beim Datei-Upload (multipart/form-data) und der Seitenvorschau (Antwort text/html).
  • Maximale JSON-Body-Größe: 16 MB.
  • Zeitstempel sind Unix-Sekunden (int), z. B. "updated_at": 1791191665.
  • IDs sind Ganzzahlen.
  • DELETE antwortet bei Erfolg mit 204 No Content ohne Body.
  • POST zum Anlegen antwortet mit 201 Created und dem angelegten Objekt.
  • PUT ist ein Merge: Es werden nur die Felder geändert, die im Body vorkommen; fehlende Felder behalten ihren Wert. Felder wie id, created_at werden ignoriert.
  • Gleichzeitiges Bearbeiten: PUT auf Seiten, Snippets, Assets, globale Blöcke, Workflows und Collection-Einträge akzeptiert optional "base_updated_at" – das updated_at des Stands, den der Client geladen hat. Wurde inzwischen gespeichert, antwortet der Server mit 409 und { "error": "…", "code": "common.conflict", "conflict": true, "updated_at": <aktueller Stand> }, ohne etwas zu ändern. Ohne das Feld wird wie bisher überschrieben. Mehrere Änderungen auf einmal prüft POST /batch.
  • Listen sind nie null, sondern mindestens [].
  • Jede Antwort unter /api/v1 trägt den Header X-IC-Build – die Build-Kennung der eingebetteten Admin-App (auch bei Fehlern und ohne Anmeldung). Der Admin vergleicht sie mit der Kennung, mit der er geladen wurde, und bietet nach einem Server-Update das Neuladen an.

Fehler

Fehler werden immer als JSON mit einem Feld error geliefert, bei den meisten Fehlern zusätzlich mit einem maschinenlesbaren code und – falls die Meldung variable Teile hat – params:

{ "error": "slug 'api' is reserved", "code": "page.slugReserved", "params": { "slug": "api" } }
  • error ist immer englisch und für Menschen gedacht (Logs, Skripte, Rückfall).
  • code hat die Form bereich.name (z. B. auth.invalidCredentials, page.slugTaken, workflow.unknownNodeType, item.fieldRequired) und ist stabil – Codes werden nicht umbenannt; auf sie (nicht auf den Text) sollten Clients reagieren. Fehler ohne code (z. B. reine Serverfehler) haben nur error.
  • params enthält die variablen Teile (Namen, Slugs, Felder, Anzahlen) für die Platzhalter der Übersetzung.
  • Das Admin übersetzt über den Code: Wörterbuch-Schlüssel errors.<code> mit den params; unbekannte Codes zeigen error.
  • Steht vor der Meldung ein Präfix (z. B. item 3: field "Titel" is required beim Batch-Anlegen), beschreibt code die eigentliche Ursache.
  • Listen von Meldungen (Fehler/Warnungen beim CSV-Import, Protokoll des Website-Imports) tragen je Eintrag ebenfalls code/params; Statusobjekte (Website-Import-Job, Bundle-Übertragung) haben dafür error_code/error_params neben error.
  • Laufzeitfehler aus Workflows (Function-Code, Template-Syntax mit Zeile/Spalte) sind reiner Text ohne Code.
Status Bedeutung
400 Bad Request Ungültiges JSON, Validierungsfehler (Slug, Pflichtfeld, Typ …)
401 Unauthorized Nicht authentifiziert / ungültiger Token / falsche Zugangsdaten
403 Forbidden Lese-Token bei Schreibzugriff, fehlender CSRF-Header, nur-Session-Endpunkt
404 Not Found Objekt existiert nicht (oder ist für dich nicht sichtbar)
405 Method Not Allowed Methode für diesen Pfad nicht vorgesehen
409 Conflict Name/Slug bereits vergeben, Setup bereits erledigt
422 Unprocessable Entity Template-Fehler beim Rendern
429 Too Many Requests Rate-Limit überschritten (mit Retry-After) oder zu viele fehlgeschlagene Logins
500 Internal Server Error Unerwarteter Fehler (Details im Server-Log)
502 Bad Gateway Eine REST-Datenquelle war nicht erreichbar oder lieferte einen Fehler

Rate-Limit

Alle Anfragen unter /api/ – auch die HTTP-Eingänge der Workflows – sind je Client-IP begrenzt: ohne Anmeldung 120 pro Minute, mit Token oder Admin-Session das Zehnfache; ein kurzer Vorrat (die Hälfte davon) fängt Spitzen ab. Darüber antwortet der Server mit 429 und Retry-After (Sekunden). Aufrufe, die Workflows intern an die API stellen, zählen nicht. Webseiten, Medien und CSS/JS sind nicht begrenzt (sie kommen meist aus dem Cache).

Die Grenze stellt -rate-limit bzw. IC_RATE_LIMIT ein (Anfragen pro Minute ohne Anmeldung, 0 = aus).

Hinter einem Reverse-Proxy (Apache, Caddy, Traefik, Docker) gilt als Client-IP der letzte Eintrag in X-Forwarded-For – aber nur, wenn die Verbindung von einer lokalen oder privaten Adresse kommt. Den Eintrag hängt der Proxy selbst an, Besucher können ihn nicht fälschen. Das gilt auch für die Sperre nach zehn fehlgeschlagenen Logins.