Seiten
Seiten bilden Bäume. Jede Root-Seite (parent_id: null, Pfad /) ist eine eigene Website; welche ausgeliefert wird, entscheidet die aufgerufene Domain (domains), sonst die Standard-Website (default_site). Root-Seiten lassen sich nicht verschieben; gelöscht werden kann jede, auch die letzte (samt ihrer Seiten). Eine Instanz ohne Website – so startet ein neues Binary – liefert auf jedem Pfad 404. Der Pfad einer Seite ergibt sich aus den Slugs ihrer Vorfahren: /produkte/schuhe/sneaker. Pfade sind nur innerhalb einer Website eindeutig – root_id sagt, zu welcher eine Seite gehört.
Seiten-Objekt
{
"id": 2,
"parent_id": 1,
"slug": "blog",
"title": "Blog",
"html": "<main>{% for p in data(\"blog\") %}…{% endfor %}</main>",
"head": "<meta property=\"og:title\" content=\"{{ page.title }}\">",
"description": "Neuigkeiten",
"meta_title": "{% if route.slug %}{{ item(\"blog\", \"slug\", route.slug).titel }} – {% endif %}Blog",
"html_attrs": "",
"lang": "de",
"effective_lang": "de",
"translation_group": "k3j9q2xw7m",
"body_attrs": "class=\"blog\"",
"published": true,
"catch_all": true,
"no_cache": false,
"sitemap_exclude": false,
"noindex": false,
"nofollow": false,
"hide_in_nav": false,
"source_url": "https://www.firma.de/blog",
"import_root": 1,
"local_modified": false,
"root_id": 1,
"domains": "",
"default_site": false,
"notfound_page_id": 0,
"favicon_file_id": 0,
"og_image_file_id": 0,
"robots_txt": "",
"sort": 0,
"created_at": 1791191665,
"updated_at": 1791191700,
"path": "/blog",
"assets": [
{ "asset_id": 1, "inherit": true, "name": "base", "kind": "css" },
{ "asset_id": 4, "inherit": false, "name": "blog", "kind": "js" }
],
"blocks": []
}
| Feld | Typ | Beschreibung |
|---|---|---|
parent_id |
int | null | Elternseite; null nur bei der Startseite |
slug |
string | URL-Segment: a-z, 0-9, ., _, -; muss mit Buchstabe/Ziffer beginnen. Eindeutig unter Geschwistern. Startseite: immer "" |
title |
string | Pflichtfeld; Name der Seite im Seitenbaum, in nav()/children() und Standard für <title> |
meta_title |
string | Optionaler SEO-Titel für <title>, wird als Template ausgewertet (z. B. Artikeltitel bei Catch-All-Seiten). Leer = title. Im Template als page.meta_title verfügbar |
html |
string | Seiteninhalt als pongo2-Template. Wird in <body> eingesetzt. Enthält er selbst ein <html>-Element, wird er unverändert als ganzes Dokument genutzt und CSS/JS werden vor </head> bzw. </body> eingefügt |
head |
string | Zusätzliches Template für <head> |
description |
string | Wird als <meta name="description"> ausgegeben; Template-Syntax möglich |
html_attrs |
string | Attribute des <html>-Elements, roh ausgegeben (kein Template). Ohne lang= ergänzt IllusionCMS lang="<effective_lang>"; ein eigenes lang= gewinnt. Webflow-Importe setzen hier z. B. data-wf-page/data-wf-site |
lang |
string | Eigene Sprache der Seite (BCP 47, klein normalisiert, z. B. de, en, de-at); leer = erbt von der nächsten Seite darüber. PUT ohne Feld = unverändert. Ungültig: 400 translation.langInvalid. Siehe Sprachfassungen |
effective_lang |
string | Nur lesend: wirksame Sprache (eigene, geerbte oder de) – im Template page.lang |
translation_group |
string | Nur lesend: Übersetzungsgruppe (leer = keine); geändert über /pages/{id}/translations |
body_attrs |
string | Attribute des <body>-Elements, roh ausgegeben |
published |
bool | Unveröffentlichte Seiten (Entwürfe) liefern im Frontend 404 (außer für Admins mit ?preview=1). Ein Entwurf blendet nur sich selbst aus – seine Unterseiten bleiben erreichbar, z. B. ein Ordner /blog ohne eigene Seite über /blog/mein-artikel |
catch_all |
bool | Die Seite beantwortet auch nicht existierende Unterpfade. Der Rest steht im Template als route.slug / route.rest bereit – ideal für Detailseiten. Existiert der Eintrag nicht, liefert {{ not_found() }} die 404-Seite |
no_cache |
bool | Seite nie im Seiten-Cache halten, sondern bei jedem Aufruf neu rendern |
sitemap_exclude |
bool | Seite nicht in /sitemap.xml aufführen (z. B. Danke-Seiten, Landingpages) |
noindex |
bool | Gibt <meta name="robots" content="noindex"> aus; die Seite fehlt außerdem in /sitemap.xml |
nofollow |
bool | Gibt <meta name="robots" content="nofollow"> aus (zusammen mit noindex: noindex, nofollow) |
sitemap_source |
object | null | Nur Catch-All: Quelle der Detailseiten für /sitemap.xml und den statischen Export. null/fehlt = automatisch (Collections über item(…, route.slug)). IllusionDB: { "type": "illusiondb", "connection": "shop", "table": "landingpages", "column": "slug", "lastmod": "geaendert", "view": "", "rules": [{ "col": "aktiv", "op": "eq", "val": "true" }], "match": "and" } (rules/op wie im Workflow-Node, ohne Platzhalter; view = gespeicherte Ansicht als Filter). Workflow: { "type": "workflow", "path": "orte", "column": "slug", "lastmod": "" } – der GET-Eingang bekommt sitemap=1 und liefert eine Liste von Objekten oder Texten (oder { "records": [...] }). column Standard slug, Werte mit / ergeben mehrstufige Pfade; lastmod optional (ISO-Datum oder Unix-Zeit). Bei IllusionDB werden nur diese beiden Spalten abgefragt (?fields=). Ungültig: 400 |
hide_in_nav |
bool | Seite fehlt in den Template-Funktionen nav()/children() (mit nav(true)/children(id, true) dabei), bleibt aber erreichbar und in der Sitemap |
source_url |
string | Nur lesend: Herkunft bei importierten Seiten |
import_root |
int | Nur lesend: Zielseite des Imports, aus dem die Seite stammt |
root_id |
int | Nur lesend: Website (Root-Seite), zu der die Seite gehört |
renders_blocks |
bool | Nur lesend, nur in der Antwort auf einzelne Seiten (GET/POST/PUT /pages/{id}): Gibt das Template der Seite samt extends/include einen Blockbereich ({% blocks page.blocks %}) aus? Statische Auswertung wie bei has_blocks der Seitenlayouts. Der Editor nutzt es für den Hinweis im Reiter Blöcke |
domains |
string | Nur Root-Seiten: Domains der Website, eine pro Zeile. Wird normalisiert (klein, ohne Schema/Port/Pfad); eine Domain gehört höchstens zu einer Website (409) |
default_site |
bool | Nur Root-Seiten: Standard-Website für alle Domains ohne eigene Website. Setzen mit true nimmt die Markierung anderen Websites weg. Ohne Markierung gilt die älteste Website |
notfound_page_id |
int | Nur Root-Seiten: Seite dieser Website, die als 404-Seite gerendert wird (0 = automatisch: Seite mit Slug 404 direkt unter der Startseite). Muss eine Unterseite derselben Website sein, sonst 400; bei Unterseiten ignoriert. Ist die Seite ein Entwurf oder gelöscht, greift die Automatik |
og_image_file_id |
int | Vorschaubild für Link-Vorschauen (og:image) – ID einer Bilddatei, 0 = das der nächsten Seite darüber. Jede Seite bekommt automatisch Open-Graph- und twitter:card-Tags (Titel, Beschreibung, absolute URL über die erste Domain der Website oder die Anfrage); Tags, die schon im Head-Feld/HTML stehen, werden nicht doppelt ausgegeben. Keine Bilddatei: 400 |
favicon_file_id |
int | Nur Root-Seiten: Website-Icon – ID einer Bilddatei aus der Dateiverwaltung (SVG, PNG, ICO …; 0 = keins). Jede Seite der Website bekommt <link rel="icon"> (große PNG/JPEG ab 120 px zusätzlich apple-touch-icon), /favicon.ico liefert die Datei. Keine Bilddatei: 400; bei Unterseiten ignoriert |
robots_txt |
string | Nur Root-Seiten: eigener Inhalt für /robots.txt. Leer = automatisch (User-agent: * / Allow: /). Fehlt eine Sitemap:-Zeile, wird sie mit der aufgerufenen Domain ergänzt |
local_modified |
bool | Nur lesend: Seite wurde nach dem Import im CMS bearbeitet (Vergleich des Inhalts mit dem Import-Stand; bei nie importierten Seiten: überhaupt schon bearbeitet) |
sort |
int | Position unter den Geschwistern (wird über /move gesetzt) |
path |
string | Berechneter URL-Pfad (nur lesend) |
assets |
array | CSS/JS-Zuordnungen in Ladereihenfolge (siehe unten) |
blocks |
array | Seitenaufbau aus Blöcken (Blockbaum), im Template per {% blocks page.blocks %} ausgegeben. Ohne Blöcke []; fehlt in GET /pages (Liste ohne Inhalte). PUT ohne blocks lässt den Baum unverändert, sonst wird er geprüft und normalisiert (400 mit block.*-Codes). Versioniert und im Bundle |
Reservierte Slugs auf oberster Ebene (direkt unter der Startseite): cms-admin, api, media, _assets, sitemap.xml.
Asset-Zuordnungen
| Feld | Typ | Beschreibung |
|---|---|---|
asset_id |
int | ID eines CSS/JS-Assets |
inherit |
bool | true = wird auch auf allen Unterseiten geladen |
name, kind |
string | Nur lesend, zur Anzeige |
Beim Rendern werden zuerst die vererbten Assets der Vorfahren (von der Startseite abwärts) und dann die eigenen geladen; Duplikate werden entfernt. CSS landet im <head>, JS am Ende von <body>.
GET /pages 🔑
Alle Seiten als flache Liste, sortiert nach Eltern und sort – ohne html und head (für Baumansichten). assets ist enthalten. Den Baum baust du über parent_id auf.
Mit ?roots=1 kommen nur die Root-Seiten, also die Websites.
GET /pages/{id} 🔑
Eine Seite inklusive html, head und assets.
POST /pages ✏️
Legt eine Unterseite an (am Ende der Geschwisterliste).
{
"parent_id": 1,
"title": "Über uns",
"slug": "ueber-uns",
"html": "<main><h1>{{ page.title }}</h1></main>",
"published": true,
"assets": [{ "asset_id": 1, "inherit": true }]
}
Pflicht: parent_id, title, slug. Antwort 201 mit dem vollständigen Seiten-Objekt.
Mit layout_id statt html entsteht die Seite aus einem Seitenlayout: Ihr HTML ist dann {% extends "<name des layouts>" %} (auch im Batch unter data). layout_id und ein nicht leeres html zusammen lehnt der Server ab (400 page.layoutWithHTML), ein Snippet ohne Kennzeichen Seitenlayout ebenso (400 page.layoutUnknown).
Fehler: 400 ungültiger/reservierter Slug, 404 Elternseite fehlt, 409 Slug unter Geschwistern vergeben.
PUT /pages/{id} ✏️
Merge-Update. Ist assets im Body enthalten, werden die Asset-Zuordnungen vollständig ersetzt; fehlt das Feld, bleiben sie unverändert. parent_id und sort werden hier ignoriert – zum Verschieben dient /move.
{ "title": "Über uns", "html": "<main>…</main>", "published": true }
Antwort 200 mit dem aktualisierten Objekt.
DELETE /pages/{id} ✏️
Löscht die Seite inklusive aller Unterseiten. Bei einer Root-Seite wird die ganze Website gelöscht – auch die letzte. 204.
POST /sites ✏️
Legt eine neue Website (Root-Seite mit Standard-HTML, veröffentlicht) an.
{ "title": "Shop", "domains": "shop.firma.de\nwww.shop.firma.de" }
Antwort 201 mit dem Seiten-Objekt der neuen Root-Seite. 409, wenn eine Domain schon zu einer anderen Website gehört.
GET /sites/{id}/static 🔑
Exportiert die Website {id} (Root-Seite) als statische Website: fertige HTML-Dateien mit CSS, JS und Medien als ZIP – zum Entpacken ins Wurzelverzeichnis eines Webservers wie Apache.
- Seiten: Gerendert werden alle veröffentlichten Seiten des Baums. Dazu kommen die Detailseiten hinter Catch-All-Seiten, die auch in der Sitemap stehen (Collections bzw.
sitemap_source, z. B. Tausende Landingpages aus IllusionDB). Zusätzlich folgt der Export allen internen Links (<a href>), so kommen auch andere verlinkte Detailseiten mit (z. B./blog/mein-artikel). Jede Seite wird zu<pfad>/index.html(/→index.html); Pfade mit Dateiendung wie/feed.xmlbleiben Dateien. Apache leitet/pfaddann auf/pfad/um. - Weitere Dateien:
404.html(die 404-Seite der Website),.htaccess(ErrorDocument 404 /404.html, keine Verzeichnislisten, die Weiterleitungen der Website alsRedirectMatch– mod_alias genügt),robots.txt, alle CSS/JS-Assets unter_assets/…,sitemap.xml(wenn eine Basis-URL bekannt ist; bei mehr als 50 000 Adressen als Index mitsitemap-1.xml,sitemap-2.xml, …) und die Medien untermedia/…. - URLs bleiben unverändert (absolute Pfade ab
/) – der Export gehört ins Wurzelverzeichnis der Domain, nicht in einen Unterordner. - Query-Varianten (
?seite=2), Formulare an das CMS,/api/v1/…-Aufrufe aus JavaScript und REST-Daten nach dem Export funktionieren statisch nicht; REST-Daten werden mit dem Stand beim Export eingefroren.
| Query | Bedeutung |
|---|---|
dry=1 |
Nur prüfen: liefert den Bericht als JSON statt des ZIPs |
media=all |
Alle Dateien der Medienverwaltung; Standard: nur die, die in den gerenderten Seiten oder in CSS/JS vorkommen (inklusive srcset-Varianten) |
base |
Basis-URL für sitemap.xml, z. B. https://www.firma.de. Leer = erste Domain der Website mit https://, ohne Domain keine Sitemap |
Bericht (dry=1):
{
"pages": ["/", "/blog", "/blog/hallo", "/kontakt"],
"errors": {},
"broken_links": { "/kaputt": "/" },
"media_files": 29, "media_bytes": 7900000, "missing_media": [],
"asset_files": 11, "base": "https://www.firma.de", "too_many": false,
"private_media": 0
}
errors: Seiten, die sich nicht rendern lassen – dann liefert der Export ohne dry 422 mit { "error", "report" } statt eines unvollständigen ZIPs. broken_links: interne Links ohne Ziel (Pfad → Seite mit dem Link). missing_media: verwendete, aber nicht vorhandene Dateien. private_media: weggelassene Dateien privater Medien (Original, Varianten, WebP) – private Dateien fehlen immer, auch mit media=all. Über 20.000 Seiten bricht das Verfolgen ab (too_many).
PUT /pages/{id}/assets ✏️
Ersetzt nur die Asset-Zuordnungen. Body ist ein Array in Ladereihenfolge:
[
{ "asset_id": 1, "inherit": true },
{ "asset_id": 4, "inherit": false }
]
Antwort 200 mit dem Seiten-Objekt.
POST /pages/{id}/move ✏️
Verschiebt eine Seite im Baum.
{ "parent_id": 5, "index": 0 }
| Feld | Beschreibung |
|---|---|
parent_id |
Neue Elternseite |
index |
Position unter den neuen Geschwistern (0 = erste). Ungültige Werte (< 0 oder zu groß) hängen ans Ende |
Antwort 200: die komplette Seitenliste wie bei GET /pages.
Fehler: 400 Startseite verschieben, Verschieben in den eigenen Teilbaum, reservierter Slug auf oberster Ebene; 409 Slug unter den neuen Geschwistern vergeben.
POST /pages/{id}/preview 🔑
Rendert eine Seite mit ungespeicherten Inhalten – genutzt für die Live-Vorschau im Admin. Alle Felder sind optional; fehlende werden aus der gespeicherten Seite genommen.
{
"html": "<h1>Entwurf</h1>",
"head": "",
"title": "Neuer Titel",
"description": "",
"assets": [{ "asset_id": 1, "inherit": true }],
"overrides": { "1": "body { background: hotpink }" },
"query": { "seite": "2" },
"rest": ["mein-artikel"],
"blocks": [{ "id": "b1", "type": "hero", "params": { "titel": "Ungespeichert" } }],
"item": { "collection": "blog", "id": 12, "data": { "titel": "Ungespeichert", "slug": "mein-artikel" } }
}
| Feld | Beschreibung |
|---|---|
assets |
Ungespeicherte Asset-Zuordnungen dieser Seite |
overrides |
Ungespeicherter Inhalt von Assets (asset_id → Inhalt) |
query |
Simulierte Query-Parameter (route.query) |
rest |
Simulierter Unterpfad bei Catch-All-Seiten (route.rest) |
blocks |
Ungespeicherter Blockbaum der Seite (page.blocks) – ungeprüft; unbekannte Bausteine erscheinen als Hinweis-Kasten |
item |
Ungespeicherter Collection-Eintrag: ersetzt den Eintrag id (bzw. kommt mit id: 0 neu dazu) in data()/item() – für die Vorschau beim Bearbeiten eines Eintrags auf seiner Detailseite. Blöcke-Felder darin werden wie gespeicherte gerendert |
In der Vorschau werden alle CSS/JS-Assets inline ausgegeben und auch unveröffentlichte Collection-Einträge berücksichtigt; Blöcke tragen Vorschau-Markierungen. Adressen privater Dateien aus image()/file() (auch Website-Icon und og:image) sind signiert (?exp=…&sig=…, ein bis zwei Stunden gültig), damit sie im iframe ohne Cookies laden – ebenso in Vorschau-Links und bei ?preview=1, nie auf Live-Seiten.
Antwort: 200 text/html mit dem fertigen Dokument, bei Template-Fehlern 422 text/html mit einer Fehlerseite. Ruft das Template not_found() auf, kommt 200 mit einem Hinweis (bei Catch-All-Seiten mit vorhandenen Unterpfaden).
POST /pages/{id}/sitemap-preview ✏️
Prüft eine (auch ungespeicherte) Sitemap-Quelle der Seite: Body { "sitemap_source": { … } }, Antwort { "count": 3920, "sample": [{ "path": "/leistungen/berlin", "lastmod": "2026-09-02" }, …], "duration_ms": 46 } (die ersten 10). Fehler der Quelle (IllusionDB nicht erreichbar, Workflow fehlt): 502. Leert den Seiten-Cache nicht.
POST /pages/{id}/template-data 🔑
Liefert, was ein Template der Seite sieht – für das Panel Template-Daten im Editor. Optional mit ungespeichertem title, meta_title, description sowie query und rest wie bei der Vorschau; gerechnet wird wie in der Vorschau (Entwürfe sichtbar). Leert den Seiten-Cache nicht.
{
"variables": { "page": { "id": 2, "title": "Blog", "meta_title": "Blog | Firma", … }, "route": { … }, "site": { … }, "today": "2026-10-08", "now": "2026-10-08T14:03:00+02:00", "preview": true },
"nav": [{ "id": 2, "title": "Blog", "path": "/blog", … }],
"children": [],
"collections": [{ "slug": "blog", "name": "Blog", "fields": [{ "name": "titel", "type": "text" }], "count": 42, "items": [{ "id": 1, "titel": "…", "_created": 1791457748, "_updated": 1791457748 }] }],
"sources": [{ "slug": "wetter", "name": "Wetter", "url": "https://…" }]
}
variables enthält nur Werte, keine Funktionen. items zeigt die ersten 20 Einträge so, wie data("slug") sie liefert, count die Gesamtzahl. REST-Quellen werden nicht abgerufen (das Admin holt sie auf Klick über GET /data/{slug}). Antwort 422 bei Fehlern im SEO-Titel oder in der Beschreibung.
POST /pages/{id}/reimport ✏️
„Aus Quelle aktualisieren“: lädt eine importierte Seite erneut von source_url – mit den Download-Hosts des letzten Imports dieser Quelle in ihre Website (ohne gespeichertes Profil: abgeleitet aus den Hosts der bereits importierten Dateien der Quelle). Die Seite wird auch dann geschrieben, wenn sich die Quelle nicht geändert hat; Dateien und CSS/JS werden wie beim Re-Import wiederverwendet.
{ "overwrite_modified": true }
overwrite_modified ist nötig, wenn die Seite im CMS bearbeitet wurde (local_modified: true) – sonst bleibt sie unverändert. Antwort 202 mit einem Import-Job.
GET /pages/{id}/render 🔑
Rendert die gespeicherte Seite und liefert das HTML als JSON – praktisch für Headless-Szenarien oder statische Exporte. Query-Parameter der Anfrage werden als route.query durchgereicht.
{ "html": "<!doctype html>…", "path": "/blog" }
422 mit { "error": "HTML: … line 3 …" } bei Template-Fehlern.
Vorschau-Links ✏️
GET /pages/{id}/preview-links – gültige Links der Seite. POST /pages/{id}/preview-links mit { "days": 7 } (1–90, Standard 7) legt einen an (201). DELETE /preview-links/{id} widerruft ihn (204).
{ "id": 3, "page_id": 12, "token": "9faf061b27c6…", "author": "erik", "created_at": 1791368754, "expires_at": 1791973554 }
Aufruf: <Seitenpfad>?ic_preview=<token> – rendert die Seite des Links oder eine darunter (auch Catch-All-Details) mit Entwürfen (Seiten und Collection-Einträge), ohne Anmeldung, Cache-Control: no-store, X-Robots-Tag: noindex, nofollow, ohne Statistik. Die Website ergibt sich aus dem Link, nicht aus der Domain. Ungültig, abgelaufen oder fremde Seite: 404. Links gehören zur Instanz (nicht im Bundle).
GET /pages/{id}/mhtml 🔑
Die gespeicherte Seite als MHTML-Archiv (multipart/related, Download als <slug>-<datum>.mhtml) – Seite plus Stylesheets, Bilder, Schriften und Icons in einer Datei, auch von fremden Hosts. Skripte werden nicht eingepackt (Browser führen sie in MHTML nicht aus), srcset/sizes/loading und <picture><source> werden entfernt. Die Verweise im HTML bleiben unverändert; aufgelöst wird über die Content-Location der Teile – Basis ist die erste Domain der Website (https://…), sonst die Adresse der Anfrage.
Grenzen: 25 MB je Datei, 250 MB insgesamt, 1.000 Teile. Was nicht passt oder nicht abrufbar ist, fehlt im Archiv. Die Antwort-Header X-IC-MHTML-Parts und X-IC-MHTML-Skipped nennen die Anzahl eingepackter und ausgelassener Dateien.
curl -OJ "https://example.com/api/v1/pages/12/mhtml?token=$TOKEN"
422 bei Template-Fehlern.
Sprachfassungen
Mehrsprachige Websites bestehen aus eigenen Unterbäumen (/en/…) oder eigenen Websites (firma.de ↔ firma.com). Damit Suchmaschinen und Sprachumschalter wissen, welche Seiten dieselben Inhalte in anderer Sprache sind, verknüpfst du sie zu einer Übersetzungsgruppe.
- Sprache einer Seite: Feld
lang(eigene Sprache, BCP 47, z. B.de,en,de-at; wird klein geschrieben, ungültig →400 translation.langInvalid). Die wirksame Spracheeffective_langist die eigene oder die der nächsten Seite darüber mitlang, sonstde. Sie steht im<html lang="…">(außerhtml_attrssetzt selbstlang=) und im Template alspage.lang. - Gruppenregeln: Eine Gruppe enthält nur Seiten oder nur Einträge, je Sprache höchstens ein Mitglied (bei Seiten die wirksame Sprache). Seiten verschiedener Websites dürfen in eine Gruppe. Ändern der Sprache oder Verschieben einer Seite, das in einer Gruppe (der Seite oder einer Unterseite) eine Sprache doppelt belegen würde, gibt
409 translation.langTaken; ebenso das Wiederherstellen einer Version mit anderer Sprache. - Ausgabe: Hat die gerenderte Seite eine Gruppe mit mindestens zwei veröffentlichten Mitgliedern, schreibt IllusionCMS je Mitglied
<link rel="alternate" hreflang="<sprache>" href="<absolute URL>">in den<head>(inklusive der Seite selbst) undhreflang="x-default"für das Mitglied in der Sprache der eigenen Website, wenn alle Mitglieder in derselben Website liegen (Sprach-Unterbäume), sonst in der Sprache der Standard-Website. Absolute URLs über die erste Domain der jeweiligen Website, für die eigene Website ersatzweise über die Anfrage (wie bei Open Graph); Mitglieder ohne absolute URL fallen weg. Steht im Head-Feld oder HTML schon ein<link … hreflang>, entfällt die automatische Ausgabe. Catch-All-Detailseiten nutzen die Gruppe des angezeigten Eintrags (erkannt überitem(…, route.slug)oder die Detailseite der Collection)./sitemap.xmlergänzt dieselben Angaben als<xhtml:link>(auch im Sitemap-Index und im statischen Export). translation_groupsteht nur lesend an Seiten und Einträgen;PUTignoriert es. Neue Seiten/Einträge (auch per CSV-Import) haben keine Gruppe. Gruppen kommen im Bundle mit. Löschen räumt auf: Gruppen mit nur noch einem Mitglied werden geleert.
Sprachfassungs-Objekt (Antwort aller Endpunkte, sortiert nach Sprachcode, inklusive des angefragten Objekts mit current: true):
[
{ "id": 3, "lang": "de", "title": "Kontakt", "path": "/kontakt", "site_id": 1, "url": "/kontakt", "published": true, "current": true },
{ "id": 9, "lang": "en", "title": "Contact", "path": "/contact", "site_id": 4, "url": "https://www.company.com/contact", "published": true, "current": false }
]
| Feld | Beschreibung |
|---|---|
lang |
Wirksame Sprache der Seite bzw. Sprache der Collection |
path, site_id |
Pfad innerhalb der Website und Website (Root-Seite); bei Einträgen die Detailseite der Collection, ohne Detailseite ""/0 |
url |
Absolut über die erste Domain der Website, ohne Domain = path |
collection |
Nur bei Einträgen: Slug der Collection |
| Endpunkt | Zugriff | Beschreibung |
|---|---|---|
GET /pages/{id}/translations |
🔑 | Sprachfassungen der Seite (ohne Gruppe nur die Seite selbst) |
POST /pages/{id}/translations |
✏️ | Body { "page_id": 9 } – nimmt Seite 9 in die Gruppe von {id} auf (legt sie bei Bedarf an). Mit "move": true wechselt Seite 9 aus ihrer bisherigen Gruppe. Antwort 200 mit der Liste |
DELETE /pages/{id}/translations |
✏️ | Löst die Seite aus ihrer Gruppe (bleibt nur ein Mitglied, wird auch dessen Gruppe geleert). Antwort 200 mit der Liste |
| Code | Status | Bedeutung |
|---|---|---|
translation.langInvalid |
400 | Ungültiger Sprachcode (lang) |
translation.targetRequired |
400 | page_id bzw. item_id fehlt |
translation.sameLang |
400 | Gleiche Sprache wie das Objekt selbst (lang) |
translation.alreadyLinked |
409 | Ziel ist schon in einer anderen Gruppe (title) – mit "move": true verschieben |
translation.langTaken |
409 | Die Gruppe hat schon ein Mitglied in dieser Sprache (lang) |
translation.noLang |
400 | Einträge: Collection ohne Sprache (collection) |
translation.collectionLinked |
409 | Sprache einer Collection entfernen, deren Einträge verknüpft sind (count = Gruppen) |