Inhalte API
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.
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.
status ist kein VeröffentlichungsfilterDer 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
| Parameter | Typ | Beschreibung |
|---|---|---|
slug | string | Slug 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
}
}
]
}
]
}
]
}
- 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
200und einer leeren Liste:{"data": []}— nicht mit404. - Ein leerer Slug (
?slug=) wird mit422abgewiesen.
Einzelnen Content-Eintrag abrufen
GET /content/{content_id}
Ruft einen spezifischen Content-Eintrag anhand seiner ID ab.
Parameter
| Parameter | Typ | Beschreibung |
|---|---|---|
content_id | integer | Die 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
| Feld | Typ | Beschreibung |
|---|---|---|
id | integer | Eindeutige ID des Content-Eintrags |
status | string | Veröffentlichungsstatus ("published", "draft") |
envs | string[] | null | Umgebungen, in denen der Eintrag ausgespielt wird |
sort | integer | null | Sortierreihenfolge |
date_published | string | null | Veröffentlichungsdatum (ISO 8601) |
date_unpublished | string | null | Datum der Depublizierung (ISO 8601) |
color_text | string | null | Textfarbe (Hex-Code) |
color_background | string | null | Hintergrundfarbe (Hex-Code) |
seo | SEO | null | SEO-Metadaten |
category | ContentCategory | null | Zugeordnete Kategorie |
translations | ContentTranslation[] | null | Mehrsprachige Versionen |
SEO
| Feld | Typ | Beschreibung |
|---|---|---|
id | integer | SEO-Eintrag ID |
title | string | null | Meta-Titel für Suchmaschinen |
meta_description | string | null | Meta-Beschreibung |
canonical_url | string | null | Kanonische URL |
no_index | boolean | null | Suchmaschinenindexierung verhindern |
no_follow | boolean | null | Link-Verfolgung verhindern |
og_image | File | null | Open Graph Bild (Asset-ID) |
sitemap_change_frequency | string | null | Änderungsfrequenz für die Sitemap |
sitemap_priority | number | null | Priorität in der Sitemap |
node_type | any | null | Interner Knotentyp |
ContentCategory
| Feld | Typ | Beschreibung |
|---|---|---|
id | integer | Kategorie-ID |
sort | integer | null | Sortierreihenfolge |
date_updated | string | null | Letztes Änderungsdatum (ISO 8601) |
translations | any | null | Mehrsprachige Kategoriebezeichnungen |
ContentTranslation
| Feld | Typ | Beschreibung |
|---|---|---|
id | integer | Übersetzungs-ID |
languages_code | string | null | Sprachcode (ISO 639-1) |
title | string | null | Seitentitel |
slug | string | null | URL-Slug |
summary | string | null | Kurzzusammenfassung |
items | ContentTranslationsItem[] | null | Content-Blöcke — im Index immer null, befüllt bei ?slug= und /content/{content_id} |
ContentTranslationsItem
| Feld | Typ | Beschreibung |
|---|---|---|
id | integer | Item-ID |
content_translations_id | integer | null | ID der übergeordneten Übersetzung |
collection | string | null | Block-Typ ("block_text", "block_slider", …) |
item | ContentItem | null | Der eigentliche Content-Block |
sort | integer | null | Sortierreihenfolge |
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.
| Feld | Typ | Beschreibung |
|---|---|---|
id | integer | Block-ID |
type | string | null | Block-Untertyp ("hero", "text", "quote") |
heading | string | null | Überschrift |
body | string | null | Haupttext (HTML) |
summary | string | null | Zusammenfassung |
heading_tag | string | null | HTML-Tag für Überschrift ("h1", "h2", …) |
heading_size | string | null | Überschriftsgröße |
is_enabled | boolean | null | Block ist aktiv |
date_published | string | null | Ab wann sichtbar (ISO 8601) |
date_unpublished | string | null | Bis wann sichtbar (ISO 8601) |
BlockSlider (block_slider)
Bildergalerien und Slider-Komponenten.
| Feld | Typ | Beschreibung |
|---|---|---|
id | integer | Block-ID |
type | string | null | Slider-Typ |
aspect_ratio | string | null | Seitenverhältnis (z.B. "16:9") |
row_mode | boolean | null | Reihen-Modus aktiviert |
pick_items | integer | null | Anzahl anzuzeigender Items |
randomize | boolean | null | Zufällige Reihenfolge |
items | any | null | Slider-Inhalte |
is_enabled | boolean | null | Block ist aktiv |
date_published | string | null | Ab wann sichtbar (ISO 8601) |
date_unpublished | string | null | Bis wann sichtbar (ISO 8601) |
BlockLinks (block_links)
Linksammlungen und Navigationsblöcke.
| Feld | Typ | Beschreibung |
|---|---|---|
id | integer | Block-ID |
heading | string | null | Block-Überschrift |
type | string | null | Link-Block-Typ |
summary | string | null | Beschreibung |
item | any | null | Link-Daten |
is_enabled | boolean | null | Block ist aktiv |
date_published | string | null | Ab wann sichtbar (ISO 8601) |
date_unpublished | string | null | Bis wann sichtbar (ISO 8601) |
BlockEmbed (block_embed)
Eingebettete Inhalte wie Videos oder externe Widgets.
| Feld | Typ | Beschreibung |
|---|---|---|
id | integer | Block-ID |
type | string | null | Embed-Typ |
link | string | null | URL des eingebetteten Inhalts |
code | string | null | Embed-Code |
aspect_ratio | string | null | Seitenverhältnis |
max_width | integer | null | Maximale Breite in Pixeln |
file | File | null | Eingebettete Datei (Asset-ID) |
is_enabled | boolean | null | Block ist aktiv |
date_published | string | null | Ab wann sichtbar (ISO 8601) |
date_unpublished | string | null | Bis wann sichtbar (ISO 8601) |
BlockList (block_list)
Listen-Darstellungen für Sammlungen und Aufzählungen.
| Feld | Typ | Beschreibung |
|---|---|---|
id | integer | Block-ID |
type | string | null | Listen-Typ |
heading | string | null | Listen-Überschrift |
summary | string | null | Listen-Beschreibung |
collection | string | null | Quell-Collection |
mode | string | null | Darstellungsmodus |
pick_items | integer | null | Anzahl Items |
show_filter | boolean | null | Filter anzeigen |
filter_interface | boolean | null | Filter-Oberfläche anzeigen |
row_mode | boolean | null | Reihen-Layout |
randomize | boolean | null | Zufällige Sortierung |
aspect_ratio | string | null | Seitenverhältnis |
color_text | string | null | Textfarbe (Hex-Code) |
color_background | string | null | Hintergrundfarbe (Hex-Code) |
items | any[] | null | Listen-Items |
is_enabled | boolean | null | Block ist aktiv |
date_published | string | null | Ab wann sichtbar (ISO 8601) |
date_unpublished | string | null | Bis 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: ..."
}