Иногда не нужна ни страница, ни даже секция — нужна ровно одна вещь. Телефон в шапке. Промо-баннер над каталогом. Цена, которую маркетинг меняет каждую пятницу. Этот запрос отдаёт один блок по маркеру — и больше ничего.
Типичные кандидаты:
- Телефон или почта в шапке — блок
textна служебной страницеcommon, который виден на всех страницах. - Промо-баннер — блок
objectс заголовком, картинкой и ссылкой, который встраивается в вёрстку, собранную в коде. - Цена — блок
number, который корзина или посадочная страница читает, не загружая всю страницу тарифов.
Получить один блок
/v1/blocks/:markerhttps://back.sitecog.com/content/v1/blocks/:marker
content. Устроен так же, как блок внутри ответа страницы или секции, — только без обёртки.Параметры
markerpathstringобязательноphone или promo_banner. Латиница, цифры и подчёркивание, от 2 до 40 символов, регистр важен.sectionquerystringнеобязательноПо умолчанию: любая секция?section=header. Маркеры блоков уникальны только внутри секции, и так вы говорите, какой именно title имеете в виду. Без него побеждает первое совпадение — самый старый блок с таким маркером. Подробнее — ниже.langquerystringнеобязательноПо умолчанию: все активные языки?lang=en, ?lang=en,de или ?lang=en&lang=de. Каждый код должен быть активным языком сайта, иначе придёт 400 unknown_lang. На значения без переводов — числа, цвета — не влияет.x-crm-keyheaderstringобязательно?key=. Подробнее — в разделе Ключ сайта.?empty здесь нет: блок без content — это пустая коробка. К этому запросу параметр просто не относится.
Пример запроса
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 — серверный компонент
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; // шапка переживёт и без телефона
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>;
}Примеры ответов
Обёртка всегда одна и та же, от типа блока меняется только content.
Текстовый блок
{
"id": 88,
"marker": "phone",
"name": "Телефон в шапке",
"type": "text",
"multilang": true,
"updatedAt": "2026-09-12T07:45:10.000Z",
"content": { "en": "+66 2 123 4567", "de": "+66 2 123 4567" }
}Текст — это карта переводов: по ключу на язык, в порядке CRM. Языка, для которого значение ни разу не сохраняли, в карте просто нет; если значение есть, но пустое, придёт "". Запасного языка на стороне сервера нет — выбирайте его сами, помощник есть в разделе Советы.
Блок-объект
{
"id": 131,
"marker": "promo_banner",
"name": "Промо-баннер",
"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
}
}Объект — это набор полей, ключ — маркер поля, и каждое поле подчиняется тем же правилам, что и отдельный блок: текстовые поля — карты переводов, ссылка — { url, target, title }, картинка — { file } (или карта файлов по языкам), числа, даты и флаги — как сохранены. Приходят только значения, описание полей остаётся в CRM.
И цена
{
"id": 140,
"marker": "price",
"name": "Цена базового тарифа",
"type": "number",
"multilang": false,
"updatedAt": "2026-09-26T16:20:00.000Z",
"content": 149
}Числа, цвета, даты, периоды и флаги приходят как есть — сохранённым значением (или null, если пусто). Карты переводов у них нет, так что ?lang их не трогает. Все варианты — на странице Типы блоков.
Поля ответа
idnumbermarkerstringnamestringtypestringtext, html, image, video, link, number, color, date, date_range, boolean, object, array. Если один компонент выводит разные блоки, проверяйте тип, прежде чем читать content.multilangbooleantrue, если у каждого языка свой файл, — тогда в content не один file, а адреса по языкам.updatedAtstring (ISO 8601)contentзависит от type | nulltext и html, { file } у одиночной картинки или видео, { url, target, title } у ссылки, объект полей у object, массив таких объектов у array, сырое значение (или null) у чисел, цветов, дат и флагов. Подробности — на странице Типы блоков.Маркеры уникальны внутри секции — уточняйте ?section
Маркер блока обязан быть уникальным только в своей секции. Поэтому редакторы могут давать блокам нормальные имена: в секции hero есть title, в секции faq тоже есть title, и никому не приходится выдумывать title_2_final.
Обратная сторона: просто /v1/blocks/title — это неоднозначно. Без ?section API вернёт первое совпадение — самый старый блок с таким маркером. Не факт, что тот, который вы имели в виду, и он может смениться, если кто-то пересоздаст блок. Добавьте секцию — и ответ станет точным:
# Неоднозначно: какой "title" создали раньше, тот и придёт
curl "https://back.sitecog.com/content/v1/blocks/title" -H "x-crm-key: pk_3f9c2a7e1b4d4c0e8a6f5b2d9c1e7a40"
# Точно: заголовок секции FAQ
curl "https://back.sitecog.com/content/v1/blocks/title?section=faq" -H "x-crm-key: pk_3f9c2a7e1b4d4c0e8a6f5b2d9c1e7a40"Не собирайте блоки по одному
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;Запрос одного блока хорош, когда вам действительно нужно одно значение там, где остальная страница ни при чём: телефон в общей шапке, баннер в макете, цена в виджете оформления заказа.
Ошибки
| Статус | Тело ответа | Что случилось |
|---|---|---|
| 400 | {"message":"invalid_marker"} | В маркере есть символы кроме A–Z a–z 0–9 _ или не та длина. |
| 400 | {"message":"invalid_lang", …} | Код в lang записан неправильно. |
| 400 | {"message":"unknown_lang", …} | Язык из lang не активен на сайте. В теле ответа — список доступных. |
| 401 | {"message":"invalid_key"} | Ключа нет, он неправильного вида или отозван. |
| 404 | {"message":"block_not_found"} | Блока с таким маркером нет — или его нет в секции, указанной в ?section. |
| 429 | {"message":"rate_limit_exceeded"} | Слишком много запросов за эту минуту. См. Лимиты. |
Полный список с подсказками, что делать, — на странице Ошибки.
Советы из практики
// нужный язык → язык по умолчанию → первое непустое значение
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"