Zum Hauptinhalt springen

Inhalte API

Überblick

Die Inhalte API ermöglicht den Zugriff auf redaktionelle Inhalte des Kulturpools. Sie liefert strukturierte Content-Seiten mit verschiedenen Block-Typen, SEO-Metadaten und mehrsprachigen Übersetzungen für die Website-Darstellung.

Basis-URL​

https://api.kulturpool.at/content

Endpunkte​

Alle Inhalte abrufen​

GET /content

Ruft alle Einträge als schlanken Index ab — mit Übersetzungen, SEO-Metadaten und Kategorien, aber ohne die verschachtelten Inhaltsblöcke.

Index und Detail sind getrennt

translations[].items ist in dieser Antwort immer null. Das ist Absicht: mit den Inhaltsblöcken wäre die Liste rund 7,4 MB groß, ohne sie sind es etwa 0,16 MB — ein Faktor von knapp 50.

Die Inhaltsblöcke einer bestimmten Seite holen Sie gezielt über ?slug= oder /content/{content_id}.

Der Index enthält alle Einträge auf einmal; Paginierungsparameter gibt es nicht. Die Antwort wird serverseitig drei Stunden lang gecacht.

warnung
status ist kein Veröffentlichungsfilter

Der Endpunkt filtert nicht nach status. Sie erhalten Einträge mit draft, published und archived gemischt.

Wichtig: status wird in dieser Installation nicht als Veröffentlichungs-Schalter gepflegt — auch produktiv ausgespielte Seiten wie die Startseite tragen draft. Filtern Sie Inhalte daher nicht über status === "published", sonst verlieren Sie live genutzte Seiten. Verwenden Sie stattdessen den slug der jeweiligen Übersetzung, um gezielt die Seite zu holen, die Sie darstellen wollen.

Antwort​

{
"data": [
{
"id": 1,
"status": "published",
"sort": 1,
"date_published": "2024-01-15T09:00:00Z",
"color_text": "#333333",
"color_background": "#ffffff",
"seo": {
"id": 1,
"title": "Willkommen im Kulturpool - Digitale Kunst entdecken",
"meta_description": "Entdecken Sie die Vielfalt österreichischer Kultur im digitalen Raum",
"canonical_url": "https://kulturpool.at/willkommen"
},
"category": {
"id": 1,
"sort": 1,
"translations": []
},
"translations": [
{
"id": 1,
"languages_code": "de",
"title": "Willkommen im Kulturpool",
"slug": "willkommen",
"summary": "Entdecken Sie die Vielfalt österreichischer Kultur",
"items": null
}
]
}
]
}

items ist hier null — die Inhaltsblöcke liefert der Index bewusst nicht mit.

Einzelne Seite per Slug abrufen​

GET /content?slug={slug}

Liefert den Eintrag, dessen Übersetzung den angegebenen Slug trägt — inklusive aller Inhaltsblöcke. Das ist der empfohlene Weg, um eine einzelne Seite darzustellen.

Parameter​

ParameterTypBeschreibung
slugstringSlug einer Übersetzung, z.B. home oder datenschutz

Beispiel​

curl -X GET "https://api.kulturpool.at/content?slug=home"

Die Antwort hat dieselbe Hülle wie der Index ({"data": [...]}), enthält aber den vollständigen Eintrag mit befüllten items:

{
"data": [
{
"id": 54,
"status": "draft",
"translations": [
{
"languages_code": "de",
"slug": "home",
"title": "Erforsche Österreichs digitales Kulturerbe",
"items": [
{
"id": 1,
"collection": "block_text",
"item": {
"id": 1,
"type": "hero",
"heading": "Erforsche Österreichs digitales Kulturerbe",
"heading_tag": "h1",
"is_enabled": true
}
}
]
}
]
}
]
}
Verhalten im Detail
  • Es wird der gesamte Eintrag zurückgegeben, also alle Übersetzungen — nicht nur die, deren Slug übereinstimmt. Slugs sind über Sprachen hinweg oft identisch.
  • Findet sich kein Treffer, antwortet die API mit 200 und einer leeren Liste: {"data": []} — nicht mit 404.
  • Ein leerer Slug (?slug=) wird mit 422 abgewiesen.

Einzelnen Content-Eintrag abrufen​

GET /content/{content_id}

Ruft einen spezifischen Content-Eintrag anhand seiner ID ab.

Parameter​

ParameterTypBeschreibung
content_idintegerDie eindeutige ID des Content-Eintrags (Pfad-Parameter)

Beispiel​

curl -X GET "https://api.kulturpool.at/content/1"

Antwort​

{
"data": {
"id": 1,
"status": "published",
"sort": 1,
"date_published": "2024-01-15T09:00:00Z",
"color_text": "#333333",
"color_background": "#ffffff",
"seo": {
"id": 1,
"title": "Willkommen im Kulturpool - Digitale Kunst entdecken",
"meta_description": "Entdecken Sie die Vielfalt österreichischer Kultur im digitalen Raum",
"canonical_url": "https://kulturpool.at/willkommen"
},
"category": {
"id": 1,
"sort": 1,
"translations": []
},
"translations": [
{
"id": 1,
"languages_code": "de",
"title": "Willkommen im Kulturpool",
"slug": "willkommen",
"summary": "Entdecken Sie die Vielfalt österreichischer Kultur",
"items": [
{
"id": 1,
"collection": "block_text",
"item": {
"id": 1,
"type": "hero",
"heading": "Willkommen im Kulturpool",
"body": "<p>Hier finden Sie digitalisierte Kunst und Kultur aus österreichischen Institutionen.</p>",
"heading_tag": "h1",
"is_enabled": true
}
}
]
}
]
}
}

Datenstruktur​

Content​

FeldTypBeschreibung
idintegerEindeutige ID des Content-Eintrags
statusstringVeröffentlichungsstatus ("published", "draft")
envsstring[] | nullUmgebungen, in denen der Eintrag ausgespielt wird
sortinteger | nullSortierreihenfolge
date_publishedstring | nullVeröffentlichungsdatum (ISO 8601)
date_unpublishedstring | nullDatum der Depublizierung (ISO 8601)
color_textstring | nullTextfarbe (Hex-Code)
color_backgroundstring | nullHintergrundfarbe (Hex-Code)
seoSEO | nullSEO-Metadaten
categoryContentCategory | nullZugeordnete Kategorie
translationsContentTranslation[] | nullMehrsprachige Versionen

SEO​

FeldTypBeschreibung
idintegerSEO-Eintrag ID
titlestring | nullMeta-Titel für Suchmaschinen
meta_descriptionstring | nullMeta-Beschreibung
canonical_urlstring | nullKanonische URL
no_indexboolean | nullSuchmaschinenindexierung verhindern
no_followboolean | nullLink-Verfolgung verhindern
og_imageFile | nullOpen Graph Bild (Asset-ID)
sitemap_change_frequencystring | nullÄnderungsfrequenz für die Sitemap
sitemap_prioritynumber | nullPriorität in der Sitemap
node_typeany | nullInterner Knotentyp

ContentCategory​

FeldTypBeschreibung
idintegerKategorie-ID
sortinteger | nullSortierreihenfolge
date_updatedstring | nullLetztes Änderungsdatum (ISO 8601)
translationsany | nullMehrsprachige Kategoriebezeichnungen

ContentTranslation​

FeldTypBeschreibung
idintegerÜbersetzungs-ID
languages_codestring | nullSprachcode (ISO 639-1)
titlestring | nullSeitentitel
slugstring | nullURL-Slug
summarystring | nullKurzzusammenfassung
itemsContentTranslationsItem[] | nullContent-Blöcke — im Index immer null, befüllt bei ?slug= und /content/{content_id}

ContentTranslationsItem​

FeldTypBeschreibung
idintegerItem-ID
content_translations_idinteger | nullID der übergeordneten Übersetzung
collectionstring | nullBlock-Typ ("block_text", "block_slider", …)
itemContentItem | nullDer eigentliche Content-Block
sortinteger | nullSortierreihenfolge
Blocktyp erkennen

Welches Schema item hat, ergibt sich aus dem Feld collection des umschließenden ContentTranslationsItem — nicht aus item.type. item.type bezeichnet lediglich die Variante innerhalb eines Blocktyps (z.B. "hero" bei block_text).

Content-Block-Typen​

Alle Blöcke besitzen die gemeinsamen Felder is_enabled, date_published und date_unpublished, mit denen sich Blöcke zeitgesteuert ein- und ausblenden lassen.

BlockText (block_text)​

Textblöcke für Überschriften, Fließtext und Hero-Bereiche.

FeldTypBeschreibung
idintegerBlock-ID
typestring | nullBlock-Untertyp ("hero", "text", "quote")
headingstring | nullÜberschrift
bodystring | nullHaupttext (HTML)
summarystring | nullZusammenfassung
heading_tagstring | nullHTML-Tag für Überschrift ("h1", "h2", …)
heading_sizestring | nullÜberschriftsgröße
is_enabledboolean | nullBlock ist aktiv
date_publishedstring | nullAb wann sichtbar (ISO 8601)
date_unpublishedstring | nullBis wann sichtbar (ISO 8601)

BlockSlider (block_slider)​

Bildergalerien und Slider-Komponenten.

FeldTypBeschreibung
idintegerBlock-ID
typestring | nullSlider-Typ
aspect_ratiostring | nullSeitenverhältnis (z.B. "16:9")
row_modeboolean | nullReihen-Modus aktiviert
pick_itemsinteger | nullAnzahl anzuzeigender Items
randomizeboolean | nullZufällige Reihenfolge
itemsany | nullSlider-Inhalte
is_enabledboolean | nullBlock ist aktiv
date_publishedstring | nullAb wann sichtbar (ISO 8601)
date_unpublishedstring | nullBis wann sichtbar (ISO 8601)

Linksammlungen und Navigationsblöcke.

FeldTypBeschreibung
idintegerBlock-ID
headingstring | nullBlock-Überschrift
typestring | nullLink-Block-Typ
summarystring | nullBeschreibung
itemany | nullLink-Daten
is_enabledboolean | nullBlock ist aktiv
date_publishedstring | nullAb wann sichtbar (ISO 8601)
date_unpublishedstring | nullBis wann sichtbar (ISO 8601)

BlockEmbed (block_embed)​

Eingebettete Inhalte wie Videos oder externe Widgets.

FeldTypBeschreibung
idintegerBlock-ID
typestring | nullEmbed-Typ
linkstring | nullURL des eingebetteten Inhalts
codestring | nullEmbed-Code
aspect_ratiostring | nullSeitenverhältnis
max_widthinteger | nullMaximale Breite in Pixeln
fileFile | nullEingebettete Datei (Asset-ID)
is_enabledboolean | nullBlock ist aktiv
date_publishedstring | nullAb wann sichtbar (ISO 8601)
date_unpublishedstring | nullBis wann sichtbar (ISO 8601)

BlockList (block_list)​

Listen-Darstellungen für Sammlungen und Aufzählungen.

FeldTypBeschreibung
idintegerBlock-ID
typestring | nullListen-Typ
headingstring | nullListen-Überschrift
summarystring | nullListen-Beschreibung
collectionstring | nullQuell-Collection
modestring | nullDarstellungsmodus
pick_itemsinteger | nullAnzahl Items
show_filterboolean | nullFilter anzeigen
filter_interfaceboolean | nullFilter-Oberfläche anzeigen
row_modeboolean | nullReihen-Layout
randomizeboolean | nullZufällige Sortierung
aspect_ratiostring | nullSeitenverhältnis
color_textstring | nullTextfarbe (Hex-Code)
color_backgroundstring | nullHintergrundfarbe (Hex-Code)
itemsany[] | nullListen-Items
is_enabledboolean | nullBlock ist aktiv
date_publishedstring | nullAb wann sichtbar (ISO 8601)
date_unpublishedstring | nullBis wann sichtbar (ISO 8601)

Beispiel-Code​

JavaScript/Fetch​

// Index aller Seiten (ohne Inhaltsblöcke, ca. 0,16 MB)
const response = await fetch('https://api.kulturpool.at/content');
const {data: contentPages} = await response.json();

// Einzelne Seite samt Inhaltsblöcken — per Slug ...
const bySlug = await fetch('https://api.kulturpool.at/content?slug=home');
const {data: [page]} = await bySlug.json();

// ... oder per ID
const byId = await fetch('https://api.kulturpool.at/content/54');
const {data: samePage} = await byId.json();

// Deutsche Übersetzung extrahieren
const germanTranslation = page.translations.find((t) => t.languages_code === 'de');
console.log(`Titel: ${germanTranslation.title}`);
console.log(`Blöcke: ${germanTranslation.items?.length ?? 0}`);

Python/requests​

import requests

# Index aller Seiten (ohne Inhaltsblöcke)
response = requests.get("https://api.kulturpool.at/content")
content_pages = response.json()["data"]

# Einzelne Seite samt Inhaltsblöcken — per Slug
page_response = requests.get(
"https://api.kulturpool.at/content", params={"slug": "home"}
)
matches = page_response.json()["data"]
page = matches[0] if matches else None

# Deutsche Übersetzung finden
german_translation = next(
(t for t in page["translations"] if t["languages_code"] == "de"),
None,
)

if german_translation:
print(f"Titel: {german_translation['title']}")
print(f"Slug: {german_translation['slug']}")

Content-Blöcke verarbeiten​

function processContentBlocks(translation) {
return translation.items.map((item) => {
const {collection, item: block} = item;

switch (collection) {
case 'block_text':
return {
type: 'text',
heading: block.heading,
body: block.body,
tag: block.heading_tag || 'h2',
};

case 'block_slider':
return {
type: 'slider',
aspectRatio: block.aspect_ratio,
items: block.items || [],
};

case 'block_embed':
return {
type: 'embed',
url: block.link,
code: block.code,
maxWidth: block.max_width,
};

default:
return {type: 'unknown', data: block};
}
});
}

Anwendungsfälle​

Dynamische Website-Seiten​

async function renderContentPage(slug) {
// Gezielt die eine Seite holen — inklusive Inhaltsblöcke
const response = await fetch(
`https://api.kulturpool.at/content?slug=${encodeURIComponent(slug)}`,
);
const {data: pages} = await response.json();

const page = pages[0];

if (!page) {
throw new Error(`Seite mit Slug "${slug}" nicht gefunden`);
}

const translation = page.translations.find((t) => t.languages_code === 'de');

return {
title: translation.title,
seoTitle: page.seo?.title || translation.title,
metaDescription: page.seo?.meta_description || translation.summary,
canonicalUrl: page.seo?.canonical_url,
blocks: processContentBlocks(translation),
theme: {
textColor: page.color_text,
backgroundColor: page.color_background,
},
};
}

SEO-Metadaten extrahieren​

function extractSEOMetadata(page, language = 'de') {
const translation = page.translations.find((t) => t.languages_code === language);

return {
title: page.seo?.title || translation?.title,
description: page.seo?.meta_description || translation?.summary,
canonical: page.seo?.canonical_url,
noIndex: page.seo?.no_index || false,
noFollow: page.seo?.no_follow || false,
ogImage: page.seo?.og_image?.id
? `https://api.kulturpool.at/assets/${page.seo.og_image.id}?format=jpg&width=1200&height=630&fit=cover`
: null,
};
}

Content-Management für Headless CMS​

class ContentManager {
constructor(baseUrl = 'https://api.kulturpool.at') {
this.baseUrl = baseUrl;
this.cache = new Map();
}

async getPage(id) {
if (this.cache.has(id)) {
return this.cache.get(id);
}

const response = await fetch(`${this.baseUrl}/content/${id}`);
const {data: page} = await response.json();

this.cache.set(id, page);
return page;
}

async getPageBySlug(slug, language = 'de') {
// Serverseitig filtern statt den gesamten Index zu laden
const response = await fetch(
`${this.baseUrl}/content?slug=${encodeURIComponent(slug)}`,
);
const {data: pages} = await response.json();

return pages.find((page) =>
page.translations.some(
(t) => t.slug === slug && t.languages_code === language,
),
);
}

getBlocks(page, language = 'de') {
const translation = page.translations.find(
(t) => t.languages_code === language,
);
return translation?.items || [];
}

getActiveBlocks(page, language = 'de') {
return this.getBlocks(page, language)
.filter((item) => item.item.is_enabled !== false)
.sort((a, b) => (a.sort || 0) - (b.sort || 0));
}
}

Mehrsprachige Navigation​

async function buildNavigation() {
const response = await fetch('https://api.kulturpool.at/content');
const {data: pages} = await response.json();

const navigation = {};

pages.forEach((page) => {
page.translations.forEach((translation) => {
const {languages_code, title, slug} = translation;

if (!navigation[languages_code]) {
navigation[languages_code] = [];
}

navigation[languages_code].push({
title,
slug,
url: `/${languages_code}/${slug}`,
sort: page.sort || 999,
});
});
});

// Sortieren nach sort-Wert
Object.keys(navigation).forEach((lang) => {
navigation[lang].sort((a, b) => a.sort - b.sort);
});

return navigation;
}

Fehlerbehandlung​

404 — Content nicht gefunden​

{
"detail": "Content not found"
}

500 — Validierungsfehler​

Tritt auf, wenn die Antwort des CMS nicht dem erwarteten Schema entspricht.

{
"detail": "Response validation error for content 1: ..."
}