Manchmal brauchst du keine Seite und nicht mal eine Section — sondern genau ein Ding. Die Telefonnummer im Header. Das Promo-Banner über dem Shop. Den Preis, den das Marketing jeden Freitag ändert. Dieser Endpoint gibt dir einen einzelnen Block anhand seines Markers, und sonst nichts.
Typische Kandidaten:
- Eine Telefonnummer oder E-Mail im Header — ein
text-Block, der auf der Service-Seitecommonwohnt und überall auftaucht. - Ein Promo-Banner — ein
object-Block mit Titel, Bild und Link, eingesetzt in ein Layout, das du ohnehin aus dem Code renderst. - Ein Preis — ein
number-Block, den dein Checkout oder deine Landingpage liest, ohne die ganze Preisseite zu laden.
Einen Block abrufen
/v1/blocks/:markerhttps://back.sitecog.com/content/v1/blocks/:marker
content. Dieselbe Form wie ein Block in der Response einer Seite oder Section — nur ohne die Verpackung drumherum.Parameter
markerpathstringPflichtphone oder promo_banner. Lateinische Buchstaben, Ziffern und Unterstriche, 2–40 Zeichen, Groß- und Kleinschreibung zählt.sectionquerystringoptionalStandard: beliebige Section?section=header. Block-Marker sind nur innerhalb einer Section eindeutig — so sagst du genau, welchen title du meinst. Ohne ihn gewinnt der erste Treffer: der älteste Block mit diesem Marker. Siehe unten.langquerystringoptionalStandard: alle aktiven Sprachen?lang=en, ?lang=en,de oder ?lang=en&lang=de. Jeder Code muss eine aktive Sprache der Website sein, sonst bekommst du 400 unknown_lang. Wirkt sich nicht auf Werte ohne Übersetzungen aus, etwa Zahlen oder Farben.x-crm-keyheaderstringPflicht?key=, falls Header keine Option sind. Siehe Site-Keys.Ein ?empty gibt es hier nicht: Ein Block ohne seinen content wäre eine leere Schachtel. Der Parameter passt zu diesem Endpoint einfach nicht.
Beispiel-Request
curl "https://back.sitecog.com/content/v1/blocks/phone?section=header&lang=en" \
-H "x-crm-key: pk_3f9c2a7e1b4d4c0e8a6f5b2d9c1e7a40"const res = await fetch(
'https://back.sitecog.com/content/v1/blocks/phone?section=header&lang=en',
{ headers: { 'x-crm-key': 'pk_3f9c2a7e1b4d4c0e8a6f5b2d9c1e7a40' } },
);
if (!res.ok) throw new Error(`Content API: ${res.status}`);
const block = await res.json();
console.log(block.content.en); // "+66 2 123 4567"// components/HeaderPhone.tsx — eine Server Component
export async function HeaderPhone({ lang }: { lang: string }) {
const res = await fetch(
`https://back.sitecog.com/content/v1/blocks/phone?section=header&lang=${lang}`,
{
headers: { 'x-crm-key': process.env.CRM_KEY! },
next: { revalidate: 60 },
},
);
if (!res.ok) return null; // der Header überlebt auch ohne Telefonnummer
const phone = await res.json();
const value: string = phone.content[lang] ?? '';
if (!value) return null;
return <a href={`tel:${value.replace(/\s+/g, '')}`}>{value}</a>;
}Beispiel-Responses
Die Hülle ist immer gleich; nur content ändert sich je nach Block-Typ.
Ein Text-Block
{
"id": 88,
"marker": "phone",
"name": "Phone in header",
"type": "text",
"multilang": true,
"updatedAt": "2026-09-12T07:45:10.000Z",
"content": { "en": "+66 2 123 4567", "de": "+66 2 123 4567" }
}Text ist eine Sprach-Map: ein Key pro Sprache, in CRM-Reihenfolge. Eine Sprache ganz ohne gespeicherten Wert fehlt in der Map einfach; eine, die existiert, aber leer ist, kommt als "". Einen serverseitigen Fallback gibt es nicht, also such dir selbst eine Ersatzsprache aus — siehe den Helper unter Tipps.
Ein Object-Block
{
"id": 131,
"marker": "promo_banner",
"name": "Promo banner",
"type": "object",
"multilang": false,
"updatedAt": "2026-09-28T10:05:00.000Z",
"content": {
"title": { "en": "Autumn sale: 20% off", "de": "Herbst-Sale: 20 % Rabatt" },
"image": { "file": "https://cdn.example.com/storage/your-site/autumn.jpg" },
"link": {
"url": "https://example.com/sale",
"target": "_self",
"title": { "en": "Shop now", "de": "Jetzt kaufen" }
},
"ends": "2026-10-15",
"active": true
}
}Ein Objekt ist eine Menge von Feldern mit dem Feld-Marker als Key, und jedes Feld folgt denselben Regeln wie ein eigenständiger Block: Textfelder sind Sprach-Maps, ein Link ist { url, target, title }, ein Bild ist { file } (oder eine Map pro Sprache), Zahlen, Datumswerte und Booleans kommen so, wie sie gespeichert sind. Geliefert werden nur Werte — die Felddefinitionen bleiben im CRM.
Und der Preis
{
"id": 140,
"marker": "price",
"name": "Base plan price",
"type": "number",
"multilang": false,
"updatedAt": "2026-09-26T16:20:00.000Z",
"content": 149
}Zahlen, Farben, Datumswerte, Datumsbereiche und Booleans kommen als roher gespeicherter Wert zurück (oder null, wenn leer) — ohne Sprach-Map, also lässt ?lang sie in Ruhe. Alle Formen stehen auf der Seite Block-Typen.
Felder der Response
idnumbermarkerstringnamestringtypestringtext, html, image, video, link, number, color, date, date_range, boolean, object, array. Prüf ihn, bevor du content liest, wenn dieselbe Komponente verschiedene Blöcke rendert.multilangbooleantrue, wenn jede Sprache ihre eigene Datei hat — content ist dann eine Map von URLs pro Sprache statt eines einzelnen file.updatedAtstring (ISO 8601)contentdepends on type | nulltext und html, { file } für ein einzelnes Bild oder Video, { url, target, title } für einen Link, ein Objekt aus Feldern für object, ein Array solcher Objekte für array, ein roher Wert (oder null) für Zahlen, Farben, Datumswerte und Booleans. Details: Block-Typen.Marker sind pro Section eindeutig — nutz ?section
Block-Marker müssen nur innerhalb ihrer Section eindeutig sein. Genau deshalb kann die Redaktion vernünftige Namen wiederverwenden: Die Section hero hat einen title, die Section faq hat einen title, und niemand muss sich title_2_final ausdenken.
Die Kehrseite: /v1/blocks/title allein ist mehrdeutig. Ohne ?section liefert die API den ersten Treffer — den ältesten Block mit diesem Marker. Das kann der sein, den du meinst, oder auch nicht, und es kann sich ändern, wenn jemand einen Block neu anlegt. Gib die Section mit, und die Antwort ist exakt:
# Mehrdeutig: welcher "title" auch immer zuerst angelegt wurde
curl "https://back.sitecog.com/content/v1/blocks/title" -H "x-crm-key: pk_3f9c2a7e1b4d4c0e8a6f5b2d9c1e7a40"
# Exakt: der Titel der FAQ-Section
curl "https://back.sitecog.com/content/v1/blocks/title?section=faq" -H "x-crm-key: pk_3f9c2a7e1b4d4c0e8a6f5b2d9c1e7a40"Blöcke nicht einzeln abrufen
const title = await getBlock('title', 'promo');
const text = await getBlock('text', 'promo');
const image = await getBlock('image', 'promo');const promo = await fetch('https://back.sitecog.com/content/v1/sections/promo?lang=en', {
headers: { 'x-crm-key': 'pk_3f9c2a7e1b4d4c0e8a6f5b2d9c1e7a40' },
}).then((r) => r.json());
const { title, text, image } = promo.content;Der Einzelblock-Endpoint glänzt, wenn du wirklich einen Wert an einer Stelle brauchst, die sonst nichts mit dieser Seite zu tun hat — die Telefonnummer im globalen Header, ein Banner im Layout, ein Preis im Checkout-Widget.
Fehler
| Status | Body | Was passiert ist |
|---|---|---|
| 400 | {"message":"invalid_marker"} | Der Marker enthält Zeichen außerhalb von A–Z a–z 0–9 _ oder hat die falsche Länge. |
| 400 | {"message":"invalid_lang", …} | Ein Code in lang ist falsch formatiert. |
| 400 | {"message":"unknown_lang", …} | Eine Sprache in lang ist auf der Website nicht aktiv. Der Body listet die verfügbaren auf. |
| 401 | {"message":"invalid_key"} | Key fehlt, ist falsch formatiert oder widerrufen. |
| 404 | {"message":"block_not_found"} | Kein Block mit diesem Marker — oder keiner in der Section, die du in ?section angegeben hast. |
| 429 | {"message":"rate_limit_exceeded"} | Zu viele Requests in dieser Minute. Siehe Rate Limits. |
Die vollständige Liste samt Lösungen findest du auf der Seite Fehler.
Tipps aus der Praxis
// gewünschte Sprache → Standardsprache → erster nicht leerer Wert
export function t(map: Record<string, string> | undefined, lang: string, fallback = 'en') {
if (!map) return '';
return map[lang] || map[fallback] || Object.values(map).find(Boolean) || '';
}
t(phone.content, 'de'); // "+66 2 123 4567"