Каждый запрос к Content API несёт ключ сайта. По нему мы понимаем, чей контент вы читаете, — и, в общем-то, это всё, что он делает. Никакого OAuth, обновления токенов и подписи запросов в полночь. Одна строка в одном заголовке — и вы внутри.
Что такое ключ сайта
Выглядит ключ так:
pk_3f9c2a7e1b4d4c0e8a6f5b2d9c1e7a40- Префикс
pk_и ровно 32 шестнадцатеричных символа в нижнем регистре:^pk_[a-f0-9]{32}$. - Ключ принадлежит одному сайту. Только он определяет, чей контент вы получите, — параметра «id сайта» в API нет вообще.
- Он только читает и видит только опубликованное.
- У сайта может быть несколько ключей одновременно — на этом держится безболезненная замена ключа (о ней ниже).
pk_ — привет «публикуемым ключам» (publishable keys) платёжных сервисов: это ключ, который изначально рассчитан на жизнь в открытом коде. Почему это нормально, расскажем чуть дальше.
Где взять ключ
Откройте сайт в CRM
Войдите в Diil и выберите сайт, контент которого хотите читать.Перейдите в «Настройки → Ключи контентного API»
Здесь собраны все ключи сайта.Создайте ключ
Вы получите свежую строкуpk_…. Скопируйте её.Положите ключ в переменную окружения
Не прямо в код — вы же из будущего, меняющий ключ, скажете спасибо. Готовые варианты для популярных фреймворков — ниже.
Как передать ключ
Передавайте ключ в заголовке запроса x-crm-key. Это основной способ, и он одинаково работает и с сервера, и из браузера: CORS разрешает этот заголовок с любого домена.
curl "https://back.sitecog.com/content/v1/pages/home?lang=en" \
-H "x-crm-key: pk_3f9c2a7e1b4d4c0e8a6f5b2d9c1e7a40"const res = await fetch('https://back.sitecog.com/content/v1/pages/home?lang=en', {
headers: { 'x-crm-key': 'pk_3f9c2a7e1b4d4c0e8a6f5b2d9c1e7a40' },
});
const page = await res.json();GET /content/v1/pages/home?lang=en HTTP/1.1
Host: back.sitecog.com
x-crm-key: pk_3f9c2a7e1b4d4c0e8a6f5b2d9c1e7a40Запасной вариант: ?key=
Если заголовок поставить никак нельзя, передайте ключ параметром в адресе:
curl "https://back.sitecog.com/content/v1/pages/home?lang=en&key=pk_3f9c2a7e1b4d4c0e8a6f5b2d9c1e7a40"Когда так можно?
- Быстрая проверка — вставить адрес в строку браузера и посмотреть, что отдаёт запрос.
- Инструменты, которые принимают только адрес — no-code интеграция, импорт ленты, плагин генератора статических сайтов без настройки заголовков.
В остальных случаях лучше заголовок. Адреса любят оседать в логах сервера, истории браузера и аналитике, и хотя ключ не секрет, разбрасывать его повсюду незачем. К тому же адреса короче, а код аккуратнее.
Почему ключ не страшно держать в браузере
Коротко: он не умеет ничего такого, чего не может любой посетитель, открыв ваш сайт. Ключ сайта публичный по замыслу. Вот что ему доступно, а что нет:
| Ключ сайта… | |
|---|---|
| читает опубликованные страницы, секции, блоки и статьи блога своего сайта | да |
| что-то меняет, создаёт или удаляет | нет — API только для чтения |
| видит черновики и неопубликованные статьи | нет — только опубликованное |
| читает контент других ваших сайтов | нет — один ключ, один сайт |
Та же идея, что у публикуемого ключа платёжного сервиса: он говорит, чьи данные показать, но не даёт над ними власти. Всё, что он может прочитать, и так окажется на вашем публичном сайте.
Единственное, что можно сделать чужим ключом, — потратить ваш лимит запросов: у каждого ключа свои 600 запросов в минуту (см. Лимиты). Если кто-то этим увлёкся, замените ключ — это пара минут.
Ключ в переменных окружения
Раз ключ публичный, зачем переменные окружения? Затем, что ключи меняются, и замена ключа должна быть правкой настроек, а не кода. К тому же большинство фреймворков пускают переменную в браузерный код только с определённым префиксом:
# Next.js — серверные компоненты и Route Handlers (в браузер не попадает)
CRM_KEY=pk_3f9c2a7e1b4d4c0e8a6f5b2d9c1e7a40
# Next.js — клиентские компоненты (вшивается в бандл при сборке)
NEXT_PUBLIC_CRM_KEY=pk_3f9c2a7e1b4d4c0e8a6f5b2d9c1e7a40
# Vite (React, Vue, Svelte…) — вшивается при сборке
VITE_CRM_KEY=pk_3f9c2a7e1b4d4c0e8a6f5b2d9c1e7a40
# Nuxt — подменяет runtimeConfig.public.crmKey
NUXT_PUBLIC_CRM_KEY=pk_3f9c2a7e1b4d4c0e8a6f5b2d9c1e7a40// app/page.tsx — серверный компонент
export default async function Home() {
const res = await fetch('https://back.sitecog.com/content/v1/pages/home?lang=en', {
headers: { 'x-crm-key': process.env.CRM_KEY! },
next: { revalidate: 60 },
});
const page = await res.json();
return <h1>{page.content.hero.content.hero_title.content.en}</h1>;
}'use client';
import { useEffect, useState } from 'react';
export function HeroTitle() {
const [title, setTitle] = useState('');
useEffect(() => {
fetch('https://back.sitecog.com/content/v1/blocks/hero_title?section=hero&lang=en', {
headers: { 'x-crm-key': process.env.NEXT_PUBLIC_CRM_KEY! },
})
.then((r) => r.json())
.then((block) => setTitle(block.content.en ?? ''));
}, []);
return <h1>{title}</h1>;
}// src/content.ts
export async function getPage(marker: string, lang = 'en') {
const res = await fetch(`https://back.sitecog.com/content/v1/pages/${marker}?lang=${lang}`, {
headers: { 'x-crm-key': import.meta.env.VITE_CRM_KEY },
});
if (!res.ok) throw new Error(`Content API: ${res.status}`);
return res.json();
}export default defineNuxtConfig({
runtimeConfig: {
public: {
crmKey: '', // берётся из NUXT_PUBLIC_CRM_KEY
},
},
});<script setup>
const { public: { crmKey } } = useRuntimeConfig();
const { data: page } = await useFetch('https://back.sitecog.com/content/v1/pages/home', {
query: { lang: 'en' },
headers: { 'x-crm-key': crmKey },
});
</script>
<template>
<h1>{{ page.content.hero.content.hero_title.content.en }}</h1>
</template>Отзыв и замена ключа
Любой ключ можно отозвать в CRM, в том же списке «Настройки → Ключи контентного API». Отозванный ключ перестаёт работать: на каждый запрос с ним приходит 401 invalid_key. Чтобы сменить ключ без единого упавшего запроса, дайте старому и новому поработать вместе:
Создайте новый ключ
Старый пока не трогайте — оба работают параллельно.Выкатите сайт с новым ключом
Обновите переменную окружения везде, где был старый ключ, пересоберите, если фреймворк вшивает её в бандл, и выкатите.Убедитесь, что контент на месте
Откройте пару страниц, загляните в логи. Нет 401? Отлично.Отзовите старый ключ
Теперь его можно спокойно выключать.
Ошибки ключа
| Статус | Тело ответа | Что случилось |
|---|---|---|
| 401 | {"message":"invalid_key"} | Ключа нет, он неверного вида (не pk_ + 32 hex) или отозван. |
| 429 | {"message":"rate_limit_exceeded"} | Слишком много запросов — или слишком много неверных ключей с вашего IP за эту минуту (см. ниже). |
Блокировка за неверные ключи
Чтобы подбирать ключи было бессмысленно, мы считаем запросы с неверным ключом по каждому IP. Больше 20 таких запросов за минуту с одного IP — и до конца минуты этот IP получает 429, даже с правильным ключом. Заголовка Retry-After нет: счётчик обнуляется в начале следующей минуты.
Классический способ попасться случайно: старый ключ отозвали, а один сервер или забытая cron-задача всё ещё ходят с ним. Серверный рендеринг сыплет 401, и через минуту заблокирован весь сервер — вместе с новым ключом. Поэтому:
const res = await fetch(url, { headers: { 'x-crm-key': process.env.CRM_KEY } });
if (res.status === 401) {
// Неверный или отозванный ключ сам не починится. Повторы только
// расходуют счётчик неверных ключей и приводят к блокировке IP.
throw new Error('Content API: неверный ключ сайта, проверьте CRM_KEY');
}
if (res.status === 429) {
// Подождите до следующей минуты (около 60 с плюс немного случайной задержки).
}Остальные коды — на странице Ошибки.