Секция — это горизонтальный кусок страницы: первый экран, таблица цен, вопросы и ответы — вместе со всеми своими блоками. Обычно секции и так приходят внутри /v1/pages/:marker. Этот запрос нужен, когда хочется один кусок, а не весь торт.
Когда секция выгоднее целой страницы
По умолчанию по-прежнему берите страницу целиком — для большинства страниц это правильный выбор: один запрос, и всё внутри. Отдельная секция выигрывает в нескольких конкретных случаях:
- Ленивая загрузка ниже первого экрана. Страница длинная, карусель отзывов живёт где-то на четвёртом скролле, и до неё доходят немногие. Возьмите страницу с
?emptyради заголовка и мета-тегов, секции первого экрана — по маркеру, а тяжёлые подгружайте, только когда посетитель до них почти доскроллил. - Общие секции. Подвал, полоса подписки на рассылку, блок «Остались вопросы?», который стоит на каждой странице. Храните его в CRM в одном месте и запрашивайте по маркеру там, где он нужен.
- Обновление одной части. Виджету в браузере, которому важна одна секция — скажем, промо-блок, который вы перепроверяете по таймеру, — незачем каждый раз скачивать всю страницу.
Получить одну секцию
/v1/sections/:markerhttps://back.sitecog.com/content/v1/sections/:marker
content. Устроена ровно так же, как секция внутри ответа страницы, поэтому один и тот же код вывода подходит для обоих случаев.Параметры
markerpathstringобязательноhero или reviews. Правила те же, что у маркеров страниц: латиница, цифры и подчёркивание, от 2 до 40 символов, регистр важен. Секция ищется только по маркеру — параметра страницы здесь нет, — поэтому секциям, которые вы запрашиваете отдельно, давайте маркеры, не повторяющиеся на других страницах.langquerystringнеобязательноПо умолчанию: все активные языки?lang=en, ?lang=en,de или ?lang=en&lang=de. Каждый код должен быть активным языком сайта, иначе придёт 400 unknown_lang. См. Языки и запасной язык.emptyqueryflagнеобязательноПо умолчанию: выключенcontent — только id, маркер, название, порядок и show. Включается самим присутствием: ?empty, ?empty=1, ?empty=true; выключается значениями 0 или false. Удобно, чтобы дёшево проверить «а секция вообще включена?», прежде чем грузить что-то тяжёлое.x-crm-keyheaderstringобязательно?key=. Подробнее — в разделе Ключ сайта.Пример запроса
curl "https://back.sitecog.com/content/v1/sections/reviews?lang=en" \
-H "x-crm-key: pk_3f9c2a7e1b4d4c0e8a6f5b2d9c1e7a40"const res = await fetch('https://back.sitecog.com/content/v1/sections/reviews?lang=en', {
headers: { 'x-crm-key': 'pk_3f9c2a7e1b4d4c0e8a6f5b2d9c1e7a40' },
});
if (!res.ok) throw new Error(`Content API: ${res.status}`);
const section = await res.json();
if (section.show) {
console.log(section.content.reviews_title.content.en); // "What people say"
}'use client';
import { useEffect, useRef, useState, type ReactNode } from 'react';
// Ключ сайта публичный по замыслу, в браузере ему можно
const KEY = process.env.NEXT_PUBLIC_CRM_KEY!;
export function LazySection({ marker, lang, render }: {
marker: string;
lang: string;
render: (section: any) => ReactNode;
}) {
const ref = useRef<HTMLDivElement>(null);
const [section, setSection] = useState<any>(null);
useEffect(() => {
const el = ref.current;
if (!el) return;
const io = new IntersectionObserver(async ([entry]) => {
if (!entry.isIntersecting) return;
io.disconnect(); // грузим один раз
const res = await fetch(`https://back.sitecog.com/content/v1/sections/${marker}?lang=${lang}`, {
headers: { 'x-crm-key': KEY },
});
if (res.ok) setSection(await res.json());
}, { rootMargin: '400px' }); // начинаем чуть раньше, чем секция покажется
io.observe(el);
return () => io.disconnect();
}, [marker, lang]);
return <div ref={ref}>{section?.show ? render(section) : null}</div>;
}Пример ответа
{
"id": 47,
"marker": "reviews",
"name": "Отзывы покупателей",
"index": 4,
"show": true,
"content": {
"reviews_title": {
"id": 120,
"marker": "reviews_title",
"name": "Заголовок",
"type": "text",
"multilang": true,
"updatedAt": "2026-09-21T11:02:15.000Z",
"content": { "en": "What people say" }
},
"reviews_list": {
"id": 121,
"marker": "reviews_list",
"name": "Отзывы",
"type": "array",
"multilang": false,
"updatedAt": "2026-09-25T08:40:03.000Z",
"content": [
{
"author": { "en": "Maria, Berlin" },
"text": { "en": "Finally I can hear my podcast on the U-Bahn." },
"rating": 5
}
]
}
}
}С ?empty тот же запрос вернёт только «шапку» секции — этого хватает, когда нужно лишь узнать, включена ли она:
{
"id": 47,
"marker": "reviews",
"name": "Отзывы покупателей",
"index": 4,
"show": true
}Поля ответа
idnumbermarkerstringnamestringindexnumbershowbooleancontent{ [blockMarker]: Block }?empty поля нет.content →
idnumbermarkerstringreviews_title.namestringtypestringtext, html, image, video, link, number, color, date, date_range, boolean, object, array. От него зависит вид content.multilangbooleanupdatedAtstring (ISO 8601)contentзависит от type{ file } у одиночной картинки, массив элементов у array и так далее. Все варианты — на странице Типы блоков.Флаг show: скрыта — не значит удалена
Редактор может выключить секцию в CRM, не удаляя её: сезонная акция закончилась, FAQ дописан наполовину, текст ждёт юристов. API всё равно отдаёт такую секцию — с "show": false и всем содержимым. Не выводить её — забота вашего шаблона:
const reviews = await getSection('reviews');
return (
<>
<Hero />
{reviews.show && <Reviews section={reviews} />}
</>
);Ошибки
| Статус | Тело ответа | Что случилось |
|---|---|---|
| 400 | {"message":"invalid_marker"} | В маркере есть символы кроме A–Z a–z 0–9 _ или не та длина. |
| 400 | {"message":"invalid_lang", …} | Код в lang записан неправильно (скажем, english вместо en). |
| 400 | {"message":"unknown_lang", …} | Язык из lang не активен на сайте. В теле ответа — список доступных. |
| 401 | {"message":"invalid_key"} | Ключа нет, он неправильного вида или отозван. |
| 404 | {"message":"section_not_found"} | На этом сайте нет секции с таким маркером. Опечатка? Ключ от другого сайта? |
| 429 | {"message":"rate_limit_exceeded"} | Слишком много запросов за эту минуту. См. Лимиты. |
Полный список с подсказками, что делать, — на странице Ошибки.