Публичный API чтения (v1)
Публичный API чтения отдаёт опубликованный контент вашему сайту. Все эндпоинты ниже определяют сайт только по заголовку Host. Ни параметра, ни отдельного заголовка для выбора сайта нет. Токен сайта привязан к определённому сайту. Явное разрешение общего контента может добавить опубликованную публичную проекцию исходного сайта; произвольные запросы к исходному сайту и его редактирование недоступны.
Этот API предназначен только для чтения. Чтобы создавать, редактировать и публиковать контент или загружать файлы из скрипта либо с помощью ИИ-агента, используйте API управления CMS с учётными данными управления.
Для развёрнутого API с аргументами Prisma см. запросы к API приложения: стандартные маршруты, сохранённые запросы и полные возможности prisma-generator-express имеют разные контракты. Этот API чтения /v1 использует собственный ограниченный язык запросов.
Имена хостов, CORS и ограничения частоты запросов
Заголовок раздела «Имена хостов, CORS и ограничения частоты запросов»Публичный API работает как отдельный Worker. Он обслуживает сайт по имени хоста, указанному
в качестве домена сайта: значение Host после приведения к нижнему регистру и удаления порта
должно в точности совпадать с доменом. Псевдонимы www и переход на другой сайт не поддерживаются.
Источники браузерных запросов
Заголовок раздела «Источники браузерных запросов»В разделе Настройки → API → Источники браузерных запросов администратор инфраструктуры может разрешить прямое чтение из браузера. Укажите по одному источнику HTTP(S) в строке, без пути и завершающего /: https://www.example.com или http://localhost:5173. Пустой список запрещает чтение с других источников. * разрешает публичное чтение с любого источника. Передача cookie браузера не разрешается; приватные токены API сайта храните на сервере.
Для разрешённых источников заголовки CORS добавляются и к успешным ответам, и к ошибкам. Предварительный запрос OPTIONS разрешает только GET, HEAD, OPTIONS и заголовки Authorization, Content-Type, X-Site-Key, X-Site-Token, X-Dee-Wan-Preview-Challenge. Для остальных источников заголовок разрешения не добавляется. Сайт по-прежнему определяется по имени хоста, а правила публикации и токенов сохраняются. Настройка действует только на действующий API чтения /v1, а не на административный API, API управления или сгенерированный API приложения. Серверным запросам и сборкам настройка CORS не нужна.
Worker не применяет собственного ограничения частоты запросов. Если оно вам нужно, настройте его в зоне Cloudflare, которая обслуживает публичное имя хоста.
GET /v1/_sync
Заголовок раздела «GET /v1/_sync»Синхронизировать опубликованный контент. Получите ограниченный постраничный снимок, затем упорядоченные изменения записей и публичных медиа с подписанными контрольными точками. Действуют обычные токены сайта с ограниченными правами и правила CORS.
| Параметр | Обязателен | Значение |
|---|---|---|
cursor |
нет | Неизменённый next_cursor или checkpoint из предыдущей страницы. |
limit |
нет | Размер страницы от 1 до 100; по умолчанию 50. |
model |
нет | Ограничить записи одной доступной моделью по её slug. |
locale |
нет | Ограничить записи одним языком. |
| Статус | Значение |
|---|---|
| 200 | Снимок или упорядоченные изменения с продолжением либо контрольной точкой. |
| 400 | sync_query_invalid: недопустимая модель или запрос либо нет прав на модель. |
| 404 | Неизвестный хост или недействительный токен API сайта. |
| 409 | reset_required: начните новый снимок. |
| 503 | sync_dependency_unavailable: повторите запрос с той же контрольной точкой. |
GET /v1/_sync?limit=50{ "data": [], "deleted": [], "changes": [], "phase": "entries", "next_cursor": "opaque", "checkpoint": null, "reset_reason": null }GET /health
Заголовок раздела «GET /health»Обслуживает ли этот хост сайт. Отвечает только для хоста, который известен этому развёртыванию. Неизвестный хост получает здесь 404 точно так же, как и на любом другом маршруте, поэтому этот маршрут нельзя использовать, чтобы выяснить, какие сайты существуют.
| Статус | Значение |
|---|---|
| 200 | Этот хост обслуживает сайт. Тело ответа содержит slug сайта. |
| 404 | unknown_host: на этом хосте не отвечает ни один активный сайт. |
curl https://example.com/health{ "ok": true, "site": "acme" }GET /v1/:model
Заголовок раздела «GET /v1/:model»Список опубликованных записей одной модели. Записи сортируются по времени публикации, затем по id, если поле сортировки не задано через sort. Отбор по локали, фильтрация и пагинация выполняются в базе данных до ограничения размера страницы. Поэтому отбрасывание записей после выборки не может сократить страницу ниже limit. Страница короче limit с has_more: false означает конец списка.
| Параметр | Обязательный | Значение |
|---|---|---|
locale |
нет | Язык ответа: код, соответствующий ^[A-Za-z0-9_-]{1,64}$. Если не задан, используется язык сайта по умолчанию. При отсутствии перевода записи действует политика локалей сайта; этот параметр её не меняет. См. «Локали» ниже. |
origin |
нет | Широта,долгота для сортировки по расстоянию; обязателен при сортировке по полю точки, а с другими видами сортировки отклоняется. |
limit |
нет | Размер страницы, целое число от 1 до 100. По умолчанию 20. Всё остальное, включая значение больше 100, получает отказ 400 public_filter_invalid. Значение не уменьшается автоматически до допустимого. |
cursor |
нет | Непрозрачный курсор. Возьмите его из page.next_cursor и передайте без изменений с тем же запросом: путь модели, locale, order или sort, фильтры и их значения, fields и expand должны совпадать с исходными. Менять можно только limit. Курсор для другого запроса отклоняется с 400 public_cursor_mismatch. Если курсор не декодируется или выдан до появления привязки курсоров к запросу, возвращается 400 public_filter_invalid. В этом случае начните с первой страницы. Внутренняя структура курсора не входит в контракт и будет меняться. |
order |
нет | asc, desc, manual — направление по времени публикации; по умолчанию desc (сначала новые). manual запрашивает общий для всех языков ручной порядок коллекции, если он включён. Не используйте order вместе с sort. После изменения ручного порядка курсор больше не подходит: начните с первой страницы. |
filter |
нет | filter[<field>]=<value> оставляет элементы, у которых поле равно значению в отдаваемой локали. Повторите его для альтернатив: filter[kind]=guide&filter[kind]=news оставляет элементы с любым из этих значений, не больше 10 значений на поле. В текстовых полях, полях slug и select сравнивается текст. Булево поле принимает true или false. Числовое поле принимает десятичное число. Поле даты принимает YYYY-MM-DD и сравнивает календарный день. Для полей чисел и дат filter[<field>][gt] и [lt] — строгие границы, [gte] и [lte] — включающие. Поле принимает одну нижнюю границу (gt или gte) и одну верхнюю (lt или lte); gt вместе с gte, lt вместе с lte и границы вместе с равенством не принимаются. Диапазон, которому не удовлетворяет ни одно значение, например [gt]=5&[lt]=5, — public_filter_invalid. Разные поля объединяются через AND, не больше 4 полей на запрос. Каждое значение не длиннее 255 символов. Больше полей — public_filter_too_many, больше значений — public_filter_too_many_values, поле, тип которого нельзя так фильтровать, — public_filter_type_unsupported, неизвестное поле или некорректное значение — public_filter_invalid. Часть условий запроса никогда не отбрасывается. Текстовое значение длиннее 255 символов никогда не участвует в сравнении и сортировке. |
sort |
нет | Одно поле типа number, date, text, select или point: sort=<field> — по возрастанию, sort=-<field> — по убыванию, затем по id. Для точки sort=<field> ставит ближайшие записи первыми, sort=-<field> — самые дальние; оба варианта требуют origin=<latitude>,<longitude>. Записи без значения нужного типа идут последними в обоих направлениях. Не используйте вместе с order. |
fields |
нет | Ключи полей верхнего уровня через запятую, которые нужно вернуть, не больше 32, без повторов. id, slug и locale возвращаются всегда. |
relation |
нет | Фильтр по полю связи: relation=<field>:<target item id>. Оставляет записи, у которых указанная связанная запись возвращается в relations. Недоступная в рамках запроса связанная запись не подходит под фильтр. <field> должно быть полем связи этой модели с одной целевой моделью; связь с несколькими моделями отклоняется с public_filter_type_unsupported. Параметр можно повторять; условия объединяются через AND. |
tag |
нет | Фильтр по полю связи со сравнением по slug связанной записи: tag=<field>:<target slug>. Учитываются только записи, возвращаемые в relations, и их опубликованные адреса: сначала на языке исходной записи, затем по резервной цепочке сайта. При strict переводимая связанная запись без опубликованной версии на этом языке не подходит под фильтр. Запись без локализации подходит на любом языке. Отдельного типа поля для тегов нет. <field> должно быть полем связи этой модели с одной целевой моделью; связь с несколькими моделями отклоняется с public_filter_type_unsupported. Параметр можно повторять; условия объединяются через AND. Всего допускается не больше 16 пар relation и tag на запрос. Превышение возвращает 400 public_filter_too_many с max в теле. API не отбрасывает часть условий, чтобы уложиться в лимит. |
expand |
нет | Для обычного чтения и старых ссылок предпросмотра без разрешения на связанные черновики: Поля связи этой модели через запятую, не больше 3, без повторов. Каждая запись содержит expanded.<field>: { data, page }, где data — связанные записи целевой модели в порядке поля связи. Возвращаются только опубликованные записи этого сайта, доступные для чтения в рамках запроса, на запрошенном языке с учётом политики локалей — как при чтении самой целевой модели. Приватная целевая модель доступна только при чтении приватной исходной модели с токеном, разрешающим чтение целевой модели. В ответ включается не больше 25 связанных записей на поле; остальные можно получить из /v1/_related/:model по page.next_cursor. Допустим только один уровень: раскрытая запись содержит relations, но не expanded. Путь с точкой отклоняется с public_expand_too_deep, превышение числа полей — с public_expand_too_many, а поле, не являющееся связью этой модели или принимающее несколько моделей, — с public_expand_invalid. Новые общие ссылки предпросмотра автоматически раскрывают доступные сохранённые черновики. См. предпросмотр черновиков. |
| Статус | Значение |
|---|---|
| 200 | Страница записей в data, а также page.limit, page.has_more и page.next_cursor. На последней странице next_cursor равен null. |
| 400 | public_filter_invalid: неизвестный параметр, пустое значение, повторённый параметр, который не является relation, tag или filter[<field>], или некорректное значение. Эта причина также охватывает поле relation или tag, которое не является полем связи этой модели, или курсор, который не декодируется. public_filter_too_many, public_filter_too_many_values: превышен предел, который называет max. public_filter_type_unsupported: фильтр по полю, тип которого его не поддерживает. public_cursor_mismatch: курсор от другого запроса. public_expand_invalid, public_expand_too_many, public_expand_too_deep: см. expand. |
| 404 | Нет сайта на этом хосте (unknown_host), или нет модели с этим slug, или модель приватная и нет действительного API-токена сайта. Два последних случая дают одно и то же тело без reason, поэтому вызывающая сторона не может отличить приватную модель от отсутствующей. |
| 500 | site_locale_policy_invalid: у сайта не сохранена пригодная политика локалей. Администратор должен задать её в разделе Настройки → Языки. Непредвиденный сбой возвращает текстовый Internal Server Error. |
curl 'https://example.com/v1/article?locale=th&limit=10&order=desc'{ "contracts": { "page": { "served": 5 } }, "data": [ { "id": "ci_…", "slug": "hello", "model": "article", "locale": "th", "version": 3, "published_at": "2026-09-01T08:00:00.000Z", "fields": { "title": "สวัสดี" }, "relations": {}, "media": {} } ], "page": { "limit": 10, "has_more": true, "next_cursor": "…" } }GET /v1/:model/:slug
Заголовок раздела «GET /v1/:model/:slug»Получить одну опубликованную запись. Запись находится по slug опубликованной версии. У каждой языковой версии свой slug: /v1/post/bonjour?locale=fr возвращает французскую версию записи с английским slug hello. Языковая версия доступна только по собственному slug. Его уникальность ограничена моделью и языком внутри сайта и не распространяется на другие сайты. Изменение slug в черновике не меняет опубликованный адрес до публикации. Запись содержит alternates: [{ locale, slug }] — список всех опубликованных языковых версий с их собственными slug, включая возвращаемую версию.
| Параметр | Обязательный | Значение |
|---|---|---|
locale |
нет | Язык ответа: код, соответствующий ^[A-Za-z0-9_-]{1,64}$. Если не задан, используется язык сайта по умолчанию. При отсутствии перевода записи действует политика локалей сайта; этот параметр её не меняет. См. «Локали» ниже. |
preview |
нет | Токен предпросмотра (dw_prev_…), который CMS выпускает, когда редактор нажимает Предпросмотр. В течение 30 минут по нему можно получить текущий ЧЕРНОВИК ОДНОЙ исходной записи по slug черновика. Ответ предпросмотра никогда не кешируется. Токен не даёт доступа к приватной модели. Если токен не прошёл проверку, API возвращает 404, а не 403. Новые общие ссылки также читают доступные сохранённые черновики связанных записей и заново проверяют текущие права создателя. Права на поля и языки исходной записи не выходят за пределы выданного разрешения. См. предпросмотр черновиков. |
expand |
нет | Для обычного чтения и старых ссылок предпросмотра без разрешения на связанные черновики: Поля связи этой модели через запятую, не больше 3, без повторов. Каждая запись содержит expanded.<field>: { data, page }, где data — связанные записи целевой модели в порядке поля связи. Возвращаются только опубликованные записи этого сайта, доступные для чтения в рамках запроса, на запрошенном языке с учётом политики локалей — как при чтении самой целевой модели. Приватная целевая модель доступна только при чтении приватной исходной модели с токеном, разрешающим чтение целевой модели. В ответ включается не больше 25 связанных записей на поле; остальные можно получить из /v1/_related/:model по page.next_cursor. Допустим только один уровень: раскрытая запись содержит relations, но не expanded. Путь с точкой отклоняется с public_expand_too_deep, превышение числа полей — с public_expand_too_many, а поле, не являющееся связью этой модели или принимающее несколько моделей, — с public_expand_invalid. Новые общие ссылки предпросмотра автоматически раскрывают доступные сохранённые черновики. См. предпросмотр черновиков. |
| Статус | Значение |
|---|---|
| 200 | Запись на одном языке. |
| 400 | public_filter_invalid: параметр, отличный от locale, preview и expand, повторённый или пустой параметр или некорректный locale. public_expand_invalid, public_expand_too_many, public_expand_too_deep: см. expand. |
| 404 | unknown_host: нет сайта на этом хосте. locale_unavailable: элемент опубликован, но у него нет контента, который отдаст политика локалей сайта. ambiguous_slug: два опубликованных элемента претендуют на этот slug, поэтому не отдаётся ни один. Без reason: нет такой модели, нет опубликованного элемента с этим slug, или токен предпросмотра не прошёл проверку. Для приватной модели без действительного токена возвращается тот же ответ. |
| 422 | Только предпросмотр страницы: медиафайлы черновика страницы не удаётся представить. reason — одно из unreadable_page, page_media_unavailable, media_presentation_unreadable, media_presentation_revision_unavailable, media_presentation_mismatch, media_size_missing, а error содержит читаемое сообщение. |
| 500 | site_locale_policy_invalid: у сайта не сохранена пригодная политика локалей. Администратор должен задать её в разделе Настройки → Языки. Непредвиденный сбой возвращает текстовый Internal Server Error. |
curl 'https://example.com/v1/article/hello?locale=th'{ "contracts": { "page": { "served": 5 } }, "data": { "id": "ci_…", "slug": "hello", "model": "article", "locale": "th", "version": 3, "published_at": "2026-09-01T08:00:00.000Z", "fields": { "title": "สวัสดี" }, "relations": {}, "media": {} } }GET /v1/_related/:model
Заголовок раздела «GET /v1/_related/:model»Получить остальные записи раскрытой связи. Возвращает связанные записи, не вошедшие в первую страницу. :model — модель исходной записи, содержащей поле связи. Курсор указывает эту запись, её опубликованную версию, поле и локаль. Данные отбираются по тем же правилам, что и для expand. Курсор действует только для этого сайта, этой модели и токена с тем же набором доступных моделей, что и у запроса, выдавшего курсор. Для приватной модели здесь тоже нужен токен.
| Параметр | Обязательный | Значение |
|---|---|---|
cursor |
да | expanded.<field>.page.next_cursor элемента или page.next_cursor предыдущего ответа здесь. Отправляйте его обратно без изменений. Курсор с другого сайта, другой модели или с другим набором моделей токена получает отказ public_cursor_mismatch. После повторной публикации элемента он получает отказ public_cursor_stale: прочитайте элемент заново. |
limit |
нет | Размер страницы, целое число от 1 до 100. По умолчанию 25. |
| Статус | Значение |
|---|---|
| 200 | Страница связанных элементов в data, с page.limit, page.has_more и page.next_cursor. |
| 400 | public_filter_invalid: нет cursor, параметр, отличный от cursor и limit, повторённый или пустой параметр, плохой limit или курсор, который не декодируется. public_cursor_mismatch: курсор с другого сайта, другой модели или с другим набором моделей токена. public_cursor_stale: элемент опубликован снова, или поле больше не является раскрываемым полем связи. |
| 404 | unknown_host: нет сайта на этом хосте. Без reason: нет такой модели, приватная модель без действительного токена, или элемент больше не опубликован в этой локали. |
| 500 | site_locale_policy_invalid: у сайта не сохранена пригодная политика локалей. Администратор должен задать её в разделе Настройки → Языки. Непредвиденный сбой возвращает текстовый Internal Server Error. |
curl 'https://example.com/v1/_related/article?cursor=…&limit=25'{ "contracts": { "page": { "served": 5 } }, "data": [ { "id": "ci_…", "slug": "ana", "model": "author", "locale": "en", … } ], "page": { "limit": 25, "has_more": false, "next_cursor": null } }GET /v1/_client
Заголовок раздела «GET /v1/_client»Скачать типизированный TypeScript-клиент. Клиент без зависимостей для сайта на этом хосте и идентификатор схемы, по которой клиент сгенерирован. Нужен токен API сайта, даже если все модели публичные. Клиент охватывает все публичные модели и приватные модели, доступные по токену. schema_identity — SHA-256-дайджест contract: локалей, политики локалей, а также видимости и типов полей каждой модели. Подписи и порядок полей его не меняют. source начинается с комментария с теми же значениями. Этот маршрут вызывает команда из раздела Настройки → API. Ответ никогда не кешируется.
| Статус | Значение |
|---|---|
| 200 | data содержит contract_version (1), schema_identity, site, url, models, contract и source. |
| 400 | public_filter_invalid: в запросе была строка запроса. |
| 404 | Нет сайта на этом хосте (unknown_host) или нет действительного токена API этого сайта. Отсутствующий, истёкший, отозванный токен или токен другого сайта получает то же тело без reason. |
curl -H "Authorization: Bearer $DEE_WAN_SITE_TOKEN" https://example.com/v1/_client{ "data": { "contract_version": 1, "schema_identity": "sha256:9f2c…", "site": "acme", "url": "https://example.com", "models": ["article"], "contract": { "site": "acme", "default_locale": "en", "locales": ["en", "th"], "locale_policy": "strict", "models": { "article": { "visibility": "public", "fields": { "title": { "type": "text", "required": true, "multiple": false, "options": null } } } } }, "source": "/* @dee-wan/public-client …" } }GET /v1/_redirects
Заголовок раздела «GET /v1/_redirects»Получить принятый список перенаправлений. Правила из принятой версии «Меню и SEO»: созданные вручную и добавленные при публикации, изменившей адрес опубликованной страницы. Рендерер читает этот маршрут, когда путь не совпал ни с одной страницей. match: "exact" задаёт один путь; match: "pattern" захватывает сегменты {name} и последний {name*}, которые может повторно использовать to. Точное правило имеет приоритет над шаблоном; среди шаблонов выбирается тот, у которого больше сегментов с точным значением. status — 301, 302, 307 или 308 с to (путь на этом сайте или https-адрес), либо 410 (удалено) или 451 (недоступно по юридическим причинам) с to: null. Непринятый черновик «Меню и SEO» здесь никогда не появляется. Ответ одинаков с токеном и без него. Он кешируется; кеш очищается при принятии «Меню и SEO».
| Статус | Значение |
|---|---|
| 200 | data.redirects: список { from, match, status, to }, возможно пустой. |
| 400 | public_filter_invalid: в запросе была строка запроса. |
| 404 | unknown_host: на этом хосте нет сайта. |
| 500 | site_chrome_unreadable: принятая версия «Меню и SEO» больше не совпадает со своим записанным дайджестом. Администратор должен снова принять версию. |
curl https://example.com/v1/_redirects{ "data": { "redirects": [ { "from": "/tours-old", "match": "exact", "status": 301, "to": "/tours" }, { "from": "/news/{rest*}", "match": "pattern", "status": 308, "to": "/blog/{rest}" }, { "from": "/old-offer", "match": "exact", "status": 410, "to": null } ] } }GET /v1/_chrome
Заголовок раздела «GET /v1/_chrome»Получить меню и области шаблона на одном языке. Возвращает меню, области шаблона и примечание в подвале из принятой версии «Меню и SEO» с учётом запрошенного языка. Пункт меню со ссылкой на страницу, запись блога, список блога или архив содержит текущий опубликованный адрес этой записи на выбранном языке. Резервный язык определяется политикой локалей сайта. Если страница не опубликована или модель приватная, пункт пропускается; заголовок без оставшихся пунктов тоже пропускается. Пункты меню задаются отдельно для каждого языка. При отсутствии собственной версии применяется политика strict или fallback сайта.
placements указывает меню для шапки и подвала на этом языке. regions содержит объявленные в шаблоне области, в которых есть содержимое на выбранном языке. Элементы каждой области перечислены по порядку: опубликованные секции из общей секции, меню, страница поиска, свежие записи блога, категории, метки или «Записи по месяцам». Общая секция содержит записи каждого своего блока «Последние записи», отобранные по тем же правилам, что и в GET /v1/:model: только опубликованные, на запрошенном или резервном языке, в пределах лимита секции. «Записи по месяцам» содержат ссылки не более чем на 30 последних записей, сгруппированных не более чем по 12 месяцам; у месяца нет отдельного адреса. Управляемый рендерер читает этот маршрут для каждой страницы, поэтому изменения меню, областей и адресов страниц не требуют нового релиза. Ответ одинаков с токеном и без него. Он кешируется, а кеш очищается при принятии «Меню и SEO» или публикации в модели либо общей секции, на которые есть ссылка.
Ответ также содержит robots: { allow_indexing, disallow } — принятую политику индексации сайта. Управляемые страницы читают её из этого маршрута и хранят успешный ответ не более 30 секунд; если политику прочитать не удалось, страница получает noindex. Сгенерированный сайт закрепляет политику при генерации; для её изменения нужны повторная генерация и развёртывание. Когда индексация выключена, публичные страницы получают noindex, но обход остаётся разрешённым, чтобы поисковики могли прочитать тег. Сохранённые запрещённые пути применяются при включённой индексации.
home содержит данные опубликованной главной страницы { model, slug, locale, path } или null. Необязательный объект branding содержит title, tagline, logo_media_id, icon_media_id и, если они есть, записанные изображения logo/icon с полями url, alt, width, height.
| Параметр | Обязательный | Значение |
|---|---|---|
locale |
нет | Язык этого сайта. Без него — язык по умолчанию. |
| Статус | Значение |
|---|---|
| 200 | data: { locale, default_locale, home, branding?, robots: { allow_indexing, disallow }, menus: { [key]: { lang, items: [{ id, label, href, children: [menu-node], new_tab? }] } }, placements: { header, footer }, footer_note: { lang, text } | null, regions: { [key]: { lang, items } } }. lang — язык, который фактически отдан: собственная версия меню или области для locale, иначе, по fallback-правилу сайта, первая по его цепочке. Строгий сайт отдаёт только locale; меню без версии на нём отсутствует. У заголовка href: null. Элемент области — это { kind: "shared_section", entry, lang, document, collections? }, { kind: "menu", menu, name, lang, items }, { kind: "search", href }, { kind: "recent_posts" | "categories" | "tags", links: [{ label, href }] } или { kind: "monthly_archive", months: [{ month: "YYYY-MM", links }] } («Записи по месяцам»). collections сопоставляет id каждой секции «Последние записи» с { ok: true, entries } или { ok: false, code }; его нет, если в общей секции таких блоков нет. |
| 400 | public_chrome_invalid: locale не является языком этого сайта. public_filter_invalid: другой параметр запроса или locale дважды. |
| 404 | unknown_host: на этом хосте нет сайта. |
| 500 | site_chrome_unreadable: принятая версия «Меню и SEO» больше не совпадает со своим записанным дайджестом. Администратор должен снова принять версию. |
curl https://example.com/v1/_chrome?locale=th{ "data": { "locale": "th", "default_locale": "en", "home": null, "robots": { "allow_indexing": true, "disallow": [] }, "menus": { "main": { "lang": "th", "items": [ { "id": "mi_about", "label": "เกี่ยวกับ", "href": "/th/about", "children": [] }, { "id": "mi_help", "label": "Help", "href": null, "children": [ { "id": "mi_call", "label": "Call", "href": "tel:+6621234567", "children": [] } ] } ] } }, "placements": { "header": "main", "footer": null }, "footer_note": null, "regions": { "page_end": { "lang": "th", "items": [ { "kind": "search", "href": "/th/search" } ] } } } }Меню рекурсивны: до пяти уровней, 24 пунктов верхнего уровня, 12 дочерних пунктов у одного родителя и 60 пунктов всего. Каждый узел имеет форму { id, label, href, children, new_tab? }. new_tab: true открывает ссылку в новой вкладке; отсутствие свойства означает текущую вкладку. Рендерер добавляет target="_blank" и rel="noopener noreferrer".
default_locale — основной язык сайта. home содержит { model, slug, locale, path } для настроенной и опубликованной главной страницы на запрошенном языке или на разрешённом языке подстановки. Если такой страницы нет, возвращается null. Цель определяется явным маршрутом / в разделе «Меню и SEO»; черновики, закрытые, неоднозначные и недоступные цели не возвращаются. Управляемый рендерер строит адрес по этой записи и своей закреплённой карте маршрутов, а не добавляет языковой префикс наугад.
GET /v1/_embeds
Заголовок раздела «GET /v1/_embeds»Получить источники встраиваний. HTTPS-источники, которые администратор добавил в разделе Настройки → Интеграции, помимо встроенных плееров YouTube и Vimeo. Рендерер показывает фрейм встраивания из rich text с такого источника, только пока он есть в этом списке, иначе отображает обычную ссылку на тот же адрес. Каждый id — хост источника; блок встраивания указывает его в data-provider. Рендереры Dee Wan читают этот ответ только для документа с таким встраиванием. Ответ одинаков с токеном и без него. Он кешируется; кеш очищается при сохранении списка.
| Статус | Значение |
|---|---|
| 200 | data.providers: список { id, origin }, возможно пустой. |
| 400 | public_filter_invalid: в запросе была строка запроса. |
| 404 | unknown_host: на этом хосте нет сайта. |
curl https://example.com/v1/_embeds{ "data": { "providers": [ { "id": "maps.example.com", "origin": "https://maps.example.com" } ] } }GET /v1/_rich-blocks
Заголовок раздела «GET /v1/_rich-blocks»Получить определения блоков форматированного текста из плагинов. Типы блоков, объявленные активными плагинами сайта: поля, подписи и дерево отрисовки из фиксированного набора элементов, без готовой разметки. Сохранённый блок указывает свои type и version в атрибутах data-type и data-version элемента <figure data-block="plugin">. Рендерер отображает блок по дереву render из определения, экранируя каждое значение. Блок не отображается, если его типа нет в списке, в определении нет render или версия блока новее указанной. Рендереры Dee Wan читают этот ответ только для документа с блоком плагина. Ответ одинаков с токеном и без него. Он кешируется; кеш очищается при установке, обновлении, отключении, включении или удалении плагина.
| Статус | Значение |
|---|---|
| 200 | data.blocks: список определений, возможно пустой. |
| 400 | public_filter_invalid: в запросе была строка запроса. |
| 404 | unknown_host: на этом хосте нет сайта. |
curl https://example.com/v1/_rich-blocks{ "data": { "blocks": [ { "type": "com.example.notes/callout", "plugin": "com.example.notes", "name": "callout", "version": 1, "label": "Callout", "fields": [ { "key": "title", "control": "text", "label": "Title" } ], "render": { "nodes": [ { "element": "strong", "children": [ { "field": "title" } ] } ] } } ] } }GET /v1/_sitemap
Заголовок раздела «GET /v1/_sitemap»Получить политику robots и список частей карты сайта. Данные для /robots.txt и /sitemap.xml этого сайта. robots — политика принятой версии «Меню и SEO». sitemap.chunks перечисляет части опубликованных адресов по моделям, не больше 5 000 адресов в каждой; рендерер выдаёт каждую по адресу /sitemap-<model>-<number>.xml. Пусто, когда карта сайта выключена. Учитываются только публичные модели и опубликованные версии; записи с seo.robots: "noindex" не попадают в список. Кешируется и очищается при принятии «Меню и SEO» или публикации в модели из списка.
| Статус | Значение |
|---|---|
| 200 | data.robots: { allow_indexing, disallow }; data.sitemap: { enabled, change_frequency, chunks: [{ model, number, lastmod }] }. |
| 400 | public_filter_invalid: в запросе была строка запроса. |
| 404 | unknown_host: на этом хосте нет сайта. |
curl https://example.com/v1/_sitemap{ "data": { "robots": { "allow_indexing": true, "disallow": [] }, "sitemap": { "enabled": true, "change_frequency": "weekly", "chunks": [ { "model": "page", "number": 1, "lastmod": "2026-10-01T09:00:00.000Z" } ] } } }GET /v1/_sitemap/:model/:number
Заголовок раздела «GET /v1/_sitemap/:model/:number»Получить одну часть карты сайта. Опубликованные адреса части из списка /v1/_sitemap: каждая языковая версия по своему адресу, с датой последней публикации и не более чем 20 изображениями (сначала изображение для соцсетей, затем изображения опубликованной версии). model и number указывают часть из списка /v1/_sitemap. Адреса списков блога идут первыми в частях post.
В карту входят только адреса, указывающие сами на себя как на канонические. Абсолютный канонический адрес сравнивается только с доменом из активной привязки сайта; если такого домена нет, адрес не включается. Для изображений действуют те же текущие права доступа к полям и языковые оригиналы, что и для публичного содержимого, включая приватные поля вложенных компонентов.
| Статус | Значение |
|---|---|
| 200 | data.entries: список { path, lastmod, images }. |
| 400 | public_filter_invalid: в запросе была строка запроса. |
| 404 | sitemap_chunk_missing: такой части нет, или карта сайта выключена. |
curl https://example.com/v1/_sitemap/page/1{ "data": { "model": "page", "number": 1, "entries": [ { "path": "/tours", "lastmod": "2026-10-01T09:00:00.000Z", "images": ["https://media.example.com/boat.jpg"] } ] } }GET /v1/_search
Заголовок раздела «GET /v1/_search»Поиск по опубликованному контенту сайта. Поиск среди текущих опубликованных версий: страниц, записей блога на сайте с блогом и других публичных моделей, указанных в настройках поиска. В результаты не попадают черновики, неопубликованные языковые версии, записи в корзине, общие секции и приватные модели. Публикация, снятие с публикации и изменение адреса отражаются сразу. Каждый результат возвращается на запрошенном языке или, при политике fallback, на первом доступном языке резервной цепочки; slug — опубликованный адрес на этом языке.
Каждое слово q должно совпасть по заданному для модели правилу поиска: word (по умолчанию) — целые слова; english_stem — формы английских слов (tours находит tour) для результата на английском (en, en-* или en_*) и целые слова для остальных языков; substring — любая часть слова. Словами считаются последовательности букв, диакритических знаков и цифр; остальные символы служат разделителями. Порядок определяется итоговым рангом, затем датой публикации от новых к старым и идентификатором. Для модели, настроенной в разделе Настройки → Код и публикация, поиск идёт только по выбранным полям. Ранг определяется наибольшим весом (от 1 до 10) среди этих полей, содержащих все слова. Совпадение в заголовке ненастроенной модели имеет вес 10.
Поиск по полю связи учитывает подписи связанных записей, возвращаемых публичным API в relations; подписи читаются в момент поиска. Черновая подпись не участвует в поиске, как и связанная запись, снятая с публикации, помещённая в корзину или принадлежащая приватной модели. summary никогда не содержит подпись связи. Без q возвращается пустой ответ, который лишь подтверждает доступность поиска. Ответ одинаков для всех клиентов, кешируется не дольше пяти минут и очищается при каждой публикации.
| Параметр | Обязательный | Значение |
|---|---|---|
q |
нет | Что искать: от 1 до 200 символов и не больше 6 слов по 64 символа, включая повторы; двойные кавычки задают фразу. Регистр не учитывается. |
locale |
нет | Язык ответа: код, соответствующий ^[A-Za-z0-9_-]{1,64}$. Если не задан, используется язык сайта по умолчанию. При отсутствии перевода записи действует политика локалей сайта; этот параметр её не меняет. См. «Локали» ниже. |
limit |
нет | Результатов на странице, от 1 до 20. По умолчанию 10. |
cursor |
нет | page.next_cursor предыдущего ответа на те же q и locale при тех же настройках, правилах поиска, цепочке резервных языков и ревизии опубликованного контента. |
| Статус | Значение |
|---|---|
| 200 | query, data: список { kind, id, model, slug, locale, title, summary, title_parts?, summary_parts?, published_at } (kind — page, post или entry), page: { limit, has_more, next_cursor }, locale: { requested, default }. |
| 400 | public_search_invalid: неизвестный или повторённый ключ, пустое значение, запрос вне границ, неверный limit, пустая или незакрытая фраза, либо курсор от другого запроса или предыдущей ревизии опубликованного контента, настроек, правил поиска или цепочки резервных языков. |
| 404 | search_off: поиск по сайту выключен. locale_unknown: у сайта нет такого языка. unknown_host: на этом хосте нет сайта. |
| 500 | site_locale_policy_invalid: у сайта не сохранена пригодная политика локалей. Администратор должен задать её в разделе Настройки → Языки. Непредвиденный сбой возвращает текстовый Internal Server Error. |
curl "https://example.com/v1/_search?q=lighthouse&locale=en"{ "query": "lighthouse", "data": [ { "kind": "page", "id": "ci_1", "model": "page", "slug": "tours", "locale": "en", "title": "Lighthouse tours", "summary": "Walk the cliffs at dawn.", "published_at": "2026-10-01T09:00:00.000Z" } ], "page": { "limit": 10, "has_more": false, "next_cursor": null }, "locale": { "requested": "en", "default": "en" } }Двойные кавычки задают фразу: "blue harbour" требует соседних слов в указанном порядке внутри одного индексируемого поля. Повторяющиеся слова сохраняются; незакрытые и пустые кавычки отклоняются. Остальные слова тоже должны совпасть по правилу модели.
К базовому рангу прибавляется опубликованный приоритет записи seo.search_priority от −10 до 10 (по умолчанию 0). Приоритет не создаёт совпадений. Изменение в черновике не влияет на поиск до публикации. Курсор привязан также к ревизии опубликованного контента; после её изменения начните поиск заново.
Необязательные title_parts и summary_parts — списки { text, match }. Соединение text даёт исходную строку. Выводите текст обычным текстом и оборачивайте совпадения собственным <mark>; ответ не содержит HTML для вставки.
GET /v1/_search/suggest
Заголовок раздела «GET /v1/_search/suggest»Подсказки при вводе. До limit опубликованных записей для ещё не завершённого запроса. Используются те же данные и правила, что и для /v1/_search: опубликованные страницы, записи блога на сайте с блогом и выбранные публичные модели, на запрошенном языке или первом доступном языке резервной цепочки. Черновики, неопубликованные языковые версии, записи в корзине, общие секции и приватные модели не возвращаются.
Для каждого слова q применяется правило поиска модели, как в /v1/_search. Исключение — последнее слово: оно может быть неполным и совпадать с началом слова (при substring — с любой его частью). Для настроенной модели поиск идёт только по выбранным полям. Базовый ранг — 2 для заголовка, начинающегося с q, 1 для заголовка со всеми словами и 0 для совпадения только по тексту. К нему прибавляется опубликованный приоритет записи; далее идут самые новые записи и идентификатор. Закрытые фразы проверяются целиком, без неполного последнего слова. slug — опубликованный адрес на возвращаемом языке, как в /v1/_search. Данные о запросе не сохраняются. Ответ одинаков для всех клиентов, кешируется не дольше пяти минут и очищается при каждой публикации. Управляемые и сгенерированные сайты обращаются к нему через собственный /search?suggest=<q>&locale=<code>, который возвращает { data: [{ title, summary, title_parts?, summary_parts?, href, kind }] }. Каждый адрес соответствует ссылке на этом сайте.
| Параметр | Обязательный | Значение |
|---|---|---|
q |
да | Что набирается: от 2 до 100 символов и не больше 6 слов по 64 символа, включая повторы; двойные кавычки задают фразу. Регистр не учитывается. |
locale |
да | Какой язык отдавать, в виде кода, соответствующего ^[A-Za-z0-9_-]{1,64}$. Fallback следует политике локалей сайта, как в /v1/_search. |
limit |
нет | Сколько подсказок вернуть, от 1 до 10. По умолчанию 5. |
| Статус | Значение |
|---|---|
| 200 | query, data: список { kind, id, model, slug, locale, title, summary, title_parts?, summary_parts?, published_at } (kind — page, post или entry), limit, locale: { requested, default }. |
| 400 | public_search_invalid: отсутствует q или locale, неизвестный или повторённый ключ, пустое значение, запрос вне границ или неверный limit. |
| 404 | search_off: поиск по сайту выключен. locale_unknown: у сайта нет такого языка. unknown_host: на этом хосте нет сайта. |
| 500 | site_locale_policy_invalid: у сайта не сохранена пригодная политика локалей. Администратор должен задать её в разделе Настройки → Языки. Непредвиденный сбой возвращает текстовый Internal Server Error. |
curl "https://example.com/v1/_search/suggest?q=light&locale=en"{ "query": "light", "data": [ { "kind": "page", "id": "ci_1", "model": "page", "slug": "tours", "locale": "en", "title": "Lighthouse tours", "summary": "Walk the cliffs at dawn.", "published_at": "2026-10-01T09:00:00.000Z" } ], "limit": 5, "locale": { "requested": "en", "default": "en" } }GET /v1/_feed
Заголовок раздела «GET /v1/_feed»Прочитать ленту блога. 20 последних опубликованных записей блога на одном языке, ровно так, как их выдаёт список блога, чтобы рендерер записал их в RSS по адресу /rss.xml (язык по умолчанию) и /<code>/rss.xml. summary — поисковое описание записи или начало её текста. Запись покидает ленту, когда её снимают с публикации или переносят в корзину. Кешируется не дольше пяти минут и очищается при каждой публикации.
| Параметр | Обязательный | Значение |
|---|---|---|
locale |
нет | Язык ответа: код, соответствующий ^[A-Za-z0-9_-]{1,64}$. Если не задан, используется язык сайта по умолчанию. При отсутствии перевода записи действует политика локалей сайта; этот параметр её не меняет. См. «Локали» ниже. |
| Статус | Значение |
|---|---|
| 200 | data: { title, locale, default_locale, items }, каждый элемент { id, slug, locale, title, summary, authors, date, published_at }. |
| 400 | public_feed_invalid: ключ, отличный от locale, повторённый или пустой. |
| 404 | feed_unavailable: сайт не является блогом или его записи не публичны. locale_unknown: у сайта нет такого языка. |
| 500 | site_locale_policy_invalid: у сайта не сохранена пригодная политика локалей. Администратор должен задать её в разделе Настройки → Языки. Непредвиденный сбой возвращает текстовый Internal Server Error. |
curl "https://example.com/v1/_feed?locale=en"{ "data": { "title": "Harbour notes", "locale": "en", "default_locale": "en", "items": [ { "id": "ci_9", "slug": "first-light", "locale": "en", "title": "First light", "summary": "Fog lifted at six.", "authors": ["Ana Lima"], "date": "2026-10-01", "published_at": "2026-10-01T09:00:00.000Z" } ] } }GET /v1/_comments
Заголовок раздела «GET /v1/_comments»Прочитать комментарии к записи. Одобренные комментарии посетителей к одной опубликованной записи модели, которая принимает комментарии (пост блога или любая модель с включённой настройкой комментариев), на языке указанной записи: комментарии верхнего уровня в запрошенном порядке, у каждого не больше 20 одобренных ответов, от старых к новым. Комментарий содержит только id, отображаемое имя, сайт, текст, время и счётчики реакций. Почта, статус модерации, авторы реакций и всё, что служит защите от злоупотреблений, никогда не отдаются. count учитывает одобренные комментарии и ответы до 999 включительно (count_capped). Комментарии скрыты, пока запись снята с публикации или в корзине. Пока модель не принимает комментарии, возвращается страница с закрытыми комментариями (open: false, без веток). Новые комментарии отправляются на POST /v1/interactions/comment.submit, а реакции — на POST /v1/interactions/comment.react на хосте сайта, их принимает Worker приёма. Кешируется не дольше пяти минут и очищается при каждом изменении модерации, каждой публикации и изменении настройки комментариев модели.
| Параметр | Обязательный | Значение |
|---|---|---|
entry |
да | id опубликованной записи. |
order |
нет | oldest (по умолчанию), newest или best: сначала больше реакций helpful, при равном числе — сначала старые, затем по id. |
cursor |
нет | next_cursor предыдущей страницы. Курсор работает только для записи и порядка, из которых он получен. |
limit |
нет | Комментариев верхнего уровня на странице, от 1 до 50. По умолчанию 20. |
| Статус | Значение |
|---|---|
| 200 | data: { entry, locale, order, open, count, count_capped, threads, next_cursor }, каждая ветка { id, name, website, body, created_at, reactions, replies, more_replies } и каждый ответ { id, name, website, body, created_at, reactions }. reactions — это { helpful }. |
| 400 | public_comments_invalid: отсутствующий или неверный entry, неизвестный order, другой ключ, повторённое или пустое значение, неверный limit или курсор другой записи или другого порядка. |
| 404 | comments_unavailable: запись не опубликована на своём языке или её модель приватная. Никогда не кешируется. |
curl "https://example.com/v1/_comments?entry=ci_9&order=best&limit=20"{ "data": { "entry": "ci_9", "locale": "en", "order": "best", "open": true, "count": 2, "count_capped": false, "threads": [ { "id": "cm_1", "name": "Ana", "website": null, "body": "Lovely light.", "reactions": { "helpful": 3 }, "created_at": "2026-10-02T08:00:00.000Z", "more_replies": false, "replies": [ { "id": "cm_2", "name": "Bo", "website": "https://bo.example/", "body": "Agreed.", "reactions": { "helpful": 0 }, "created_at": "2026-10-02T09:00:00.000Z" } ] } ], "next_cursor": null } }Оба маршрута /v1 возвращают записи следующей структуры:
| Ключ | Тип | Значение |
|---|---|---|
id |
string | id элемента. Стабилен при правках и переименованиях. |
slug |
string или null | Slug возвращённой версии; при предпросмотре — черновика. null, если адреса нет. |
model |
string | slug модели. |
locale |
string | Локаль, в которой были отданы поля. При политике fallback она может отличаться от запрошенной. |
version |
number | Номер возвращённой сохранённой версии; при предпросмотре — черновика. |
published_at |
string или null | Время публикации в ISO 8601. |
fields |
object | Значения полей для отданной локали, по ключам полей. У поля, которое модель не переводит, одно значение: оно отдаётся в каждой локали и используется в sort и filter в каждой локали. |
relations |
object | Для обычного чтения и старых ссылок предпросмотра без разрешения на связанные черновики: Для каждого поля связи — упорядоченный список { instance_id, model, slug, display }. В нём только доступные в рамках запроса связанные записи: опубликованные, вне корзины, в публичной модели; записи приватной модели возвращаются только при чтении приватной исходной модели с токеном, разрешающим обе модели. slug и display — опубликованный адрес и подпись связанной записи на момент запроса. Если поле подписи приватное, display равно null. model — slug целевой модели; связь с несколькими моделями возвращает записи каждой из них в порядке списка. Остальные записи пропускаются, как если бы связи не было. Переводимая связанная запись возвращается на языке исходной записи, затем по резервной цепочке сайта. При strict она пропускается, если на этом языке нет опубликованной версии. Связанная запись без локализации возвращается на любом языке. Новые общие ссылки предпросмотра используют последние сохранённые черновики доступных связанных записей, включая ещё не опубликованные. Адреса и подписи берутся из этих черновиков. Проверяются текущие права создателя на записи, поля и языки; приватные поля, недоступные записи и записи в корзине исключаются. См. предпросмотр черновиков. |
expanded |
object | Для обычного чтения и старых ссылок предпросмотра без разрешения на связанные черновики: Только с expand. Для каждого названного поля связи — { data, page }: до 25 связанных записей и page.next_cursor для остальных в /v1/_related/:model. Новые общие ссылки предпросмотра заполняют это поле автоматически для обычных связей и связей внутри компонентов: до четырёх уровней и 128 связанных записей на весь ответ исходной записи. Циклы обрезаются до идентификаторов. Публичные курсоры связей для раскрытия черновиков не используются. |
media |
object | Для каждого медиаполя — упорядоченный список { media_id, name, mime_type, kind, size, url, thumbnail_url, alt, width, height }. kind — это image, document, audio, video или file; показывайте изображение через <img>, аудио и видео — их элементами со ссылкой для скачивания, а документ — ссылкой с его именем, типом и размером. width и height равны null у всего, что не является изображением. Запись также содержит crop ({ x, y, width, height }) и focal ({ x, y }), если их задал редактор, в долях 0–1 сохранённого изображения. Они описывают, как показывать изображение, и никогда не задаются у других файлов. Байты и url не меняются, и масштабированная или обрезанная копия не отдаётся. Файл, скопированный из источника медиа, также несёт credit только для чтения: { author, source, license, url } (author и url могут быть null) в том виде, в каком он был при сохранении записи. Показывайте его под изображением. Приватный файл приватной модели (использовать его можно только там) приходит с visibility: "private", пустым url и thumbnail_url равным null: его адрес скрыт, а доступ к содержимому обеспечивает собственная аутентификация вашего приложения, а не этот API. |
Поля форматированного текста содержат один форматированный документ, сохранённый как его
канонический HTML: абзацы, заголовки, списки, цитаты, блоки кода, разделители, таблицы, галереи
изображений, блоки аудио, видео и файлов, а также встраивания. Каждая запись
проверяется, а разметка вне этого набора отклоняется, поэтому API никогда не отдаёт скрипты, стили,
атрибуты событий или фреймы. Блоки записаны как данные, а не как плееры: медиаблок — это
<figure data-block="video|audio|file"> с одной ссылкой, у которой есть data-media-id, type и
data-size, а встраивание — это <figure data-block="embed" data-provider data-url data-title> без
<iframe>. data-provider — это youtube, vimeo или хост настроенного источника, а data-url — его
канонический https-адрес фрейма. Передайте источники из /v1/_embeds, чтобы показать фрейм настроенного
провайдера; без них он рисуется ссылкой. Отрисовывайте его так же, как собственные рендереры Dee Wan, — функцией renderRichDocument из
контракта страницы (Сайты с блогом).
Ответ предпросмотра также содержит preview_identity, может содержать
preview_attestation, а для моделей страниц содержит mediaPresentation.
Опубликованные ответы никогда не содержат ни одного из этих трёх ключей.
Каждый язык группы переводов публикуется отдельно и отдаёт свою опубликованную версию, поэтому список возвращает по одному элементу на группу: язык, который отдаёт политика локалей, из собственной опубликованной версии этого языка. Неопубликованный язык никогда не отдаётся из версии другого языка.
Приватные поля
Заголовок раздела «Приватные поля»Поле, отмеченное как Private в конструкторе моделей, никогда не отдаётся: его нет в fields,
relations и media каждой записи, в том числе раскрытой, а также на страницах связанных записей, в ленте блога и
в поиске по сайту. Приватные поля компонента также исключаются из его значения. Запрос, который
называет приватное поле в filter[...], sort, fields, relation, expand или курсоре связанных
страниц, получает тот же отказ, что и для несуществующего поля, поэтому запрос не может узнать ни его
значение, ни факт его существования. Это относится и к приватным моделям, читаемым с токеном.
Отдаются только поля, которые модель объявляет сейчас. Опубликованная версия хранит старый ключ
переименованного или удалённого поля, но ни одно публичное чтение не отдаёт значение под ним: ни в fields,
relations, media, ни внутри значения компонента, ни в ленте блога, ни в поиске по сайту. Поэтому поле,
которое было приватным, а затем переименовано или удалено, никогда не появляется снова под старым ключом.
Поле не может занять такой ключ, пока хранимая версия ещё содержит значение под ним: сохранение модели
или компонента отклоняется с retained_key_reuse, поэтому старое значение никогда не появляется снова
под новым полем.
Модели с одной записью
Заголовок раздела «Модели с одной записью»Модель с одной записью содержит не больше одной записи. Её маршруты обычные:
GET /v1/<model> отвечает списком не более чем из одного элемента в обычной обёртке, а
GET /v1/<model>/<slug> отдаёт его по адресу. Читайте список, когда адрес неизвестен.
У каждого сайта есть политика локалей, которая задаётся в разделе Настройки → Языки:
- strict: элемент отдаётся только в запрошенной локали и только пока этот
язык опубликован. Список пропускает элементы без неё. Маршрут элемента
отвечает 404
locale_unavailable. Резервные языки не используются. - fallback: запрошенный язык, затем вся его цепочка резервных языков. Если она
не заканчивается отметкой Стоп, дальше проверяются язык сайта по умолчанию
и основной язык записи. Отдаются только опубликованные языки из этой цепочки.
Поле
localeуказывает язык ответа.
Резервные языки задаются по порядку для каждого языка в разделе Настройки → Языки → Отсутствующие переводы. Если остановка цепочки не включена, после резервных языков проверяются язык сайта по умолчанию и основной язык записи.
За резервным языком следуют его собственные резервные. Если цепочка запрошенного языка не заканчивается отметкой Стоп, pt-BR → pt и pt → es
дают порядок pt-BR, pt, es, затем язык по умолчанию и основной язык записи. Циклы и неизвестные языки отклоняются при сохранении.
Для запрошенного языка можно включить Остановиться после этих языков. Тогда после явно заданной цепочки поиск перевода прекращается: язык по умолчанию и основной язык записи не добавляются. Например, es-MX → es → Стоп. Это правило относится к запрошенному языку, не меняет другие цепочки и не влияет на строгую политику. Остановка у резервного языка не останавливает цепочку другого запрошенного языка. То же правило действует для подстановки языка записи и отдельных необязательных полей.
Вызывающая сторона не может переопределить политику или резервные языки.
Каждое тело JSON-ошибки имеет форму { "error": string, "reason"?: string }.
public_filter_too_many, public_filter_too_many_values,
public_expand_too_many и public_expand_too_deep добавляют max. Выбирайте способ обработки ошибки по
reason и HTTP-статусу, а не по тексту error.
| Статус | reason |
Когда |
|---|---|---|
| 400 | public_filter_invalid |
Любой отклонённый запрос: неизвестный или повторённый параметр, пустое значение, плохие limit, order, locale, фильтр или курсор. |
| 400 | public_filter_too_many |
Больше 16 пар relation/tag или filter больше чем по 4 полям. max находится в теле. |
| 400 | public_filter_too_many_values |
Больше 10 значений для одного filter[field]. max находится в теле. |
| 400 | public_filter_type_unsupported |
filter по полю, тип которого он не умеет сравнивать, диапазон для типа, у которого его нет, или relation либо tag по полю связи, принимающему несколько моделей. |
| 400 | public_cursor_mismatch |
Курсор отправлен с запросом, сайтом, моделью или набором моделей токена, отличными от тех, для которых он выдан. |
| 400 | public_cursor_stale |
Курсор /v1/_related/:model, элемент которого опубликован снова. |
| 400 | public_expand_invalid |
expand называет поле, которое не является полем связи модели, поле связи, принимающее несколько моделей, или повторяет поле. |
| 400 | public_expand_too_many |
expand называет больше 3 полей. max находится в теле. |
| 400 | public_expand_too_deep |
expand называет путь с точкой. Только один уровень; max равен 1. |
| 404 | unknown_host |
Нет активного сайта на этом хосте. |
| 404 | locale_unavailable |
У элемента нет контента, который отдаст политика локалей. |
| 404 | ambiguous_slug |
Два опубликованных элемента претендуют на slug. |
| 404 | нет | Нет такой модели, нет такого опубликованного элемента, токен предпросмотра не прошёл проверку или приватная модель без действительного токена. |
| 422 | причина представления медиафайлов | Только предпросмотр страницы. См. маршрут элемента выше. |
| 500 | site_locale_policy_invalid |
Политика локалей сайта отсутствует или недействительна, или сохранённые резервные языки повреждены. |
Правила проверки для обоих маршрутов /v1:
- Неизвестный параметр получает отказ, он никогда не игнорируется.
- Пустое значение (
?locale=) получает отказ. - Параметр может встречаться один раз, кроме
relation,tagиfilter[field](только равенство) на маршруте списка. - Коды локалей и ключи полей фильтров должны соответствовать
^[A-Za-z0-9_-]{1,64}$. - Значение фильтра (часть после первого
:) — не более 255 символов, и ни одна из сторон:не может быть пустой. - Маршрут элемента принимает только
locale,previewиexpand.
Приватные модели
Заголовок раздела «Приватные модели»
Модель бывает публичной или приватной. Это задаётся в разделе Настройки → API. Приватная модель доступна для чтения только с API-токеном сайта:
Authorization: Bearer dw_site_…Токены создаются в разделе Настройки → API. Каждый токен указывает доступные для чтения модели,
показывается один раз, может иметь необязательную дату истечения срока и может быть отозван там в любое
время. Отзыв и истечение срока вступают в силу со следующего запроса. Токены начинаются с
dw_site_. После ротации ключа подписи оператором до второго поколения новые
токены начинаются с dw_site2_. Публичному Worker нужна действительная конфигурация ключа подписи
(SITE_TOKEN_HMAC_KEY). Без неё ни одна приватная модель не читается.
Запрос без токена и запрос с неправильным токеном получают ОДИН И ТОТ ЖЕ ответ: 404
без reason, такой же, как для несуществующей модели. Это сделано намеренно:
различие ответов позволило бы клиенту проверить догадку о существовании модели.
Поэтому при ответе 404 от приватной модели сначала проверьте токен, затем URL.
Публичная модель игнорирует заголовок Authorization.

Кеширование
Заголовок раздела «Кеширование»Можно ли кешировать ответ, зависит от модели, а не от запроса:
- Чтение опубликованных данных публичной модели: кешируется на edge, когда кеширование включено.
Это верно независимо от того, содержит ли запрос заголовок
Authorization. - Приватная модель или отданный предпросмотр: всегда
Cache-Control: private, no-store, никогда не сохраняется. Речь идёт о предпросмотре с прошедшим проверку токеном: как об ответе с записью, так и об отклонении с кодом 422 из-за представления медиа. Токен предпросмотра, не прошедший проверку на публичной модели, получает обычный публичный 404. - Ошибки 400 и 500
site_locale_policy_invalid:no-store.
По умолчанию кеширование выключено: PUBLIC_CACHE_MAX_AGE равен 0, и публичные ответы
содержат no-store. Чтобы включить его, установите PUBLIC_CACHE_MAX_AGE в число секунд, не более
31536000 (один год), и в публичном, и в административном Worker.
Тогда публикация или снятие с публикации очищает затронутые ответы по тегу кеша. Для этой
очистки нужны CF_PURGE_ZONE_ID и CF_PURGE_API_TOKEN (токен с правом
Zone → Cache Purge) в административном Worker. Без них изменение сообщает, что
очистка была пропущена, и закешированные ответы остаются до истечения срока.
Заголовки ответа:
| Заголовок | Публичная модель | Приватная модель или отданный предпросмотр |
|---|---|---|
Cache-Control |
no-store, когда кеширование выключено. public, max-age=<min(60, TTL)>, s-maxage=<TTL>, когда включено |
private, no-store |
X-Cache |
MISS от Worker, HIT от кеша edge |
BYPASS |
Cache-Tag |
site:<id>, model:<slug>, на маршруте элемента instance:<id>, и model:<slug> моделей, на которые ссылаются поля компонентов или которые раскрыты через expand |
не отправляется |
Vary |
Accept-Encoding |
Authorization |
Ключ кеша — это хост плюс нормализованные путь и строка запроса, поэтому тот же запрос,
записанный с другим порядком параметров, использует одну запись. Для модели с приватными полями
ключ также учитывает эти поля, поэтому после изменения приватности не используется ответ,
закешированный ранее. Изменение набора приватных полей также очищает model:<slug> и поиск по сайту.
Что стабильно, а что нет
Заголовок раздела «Что стабильно, а что нет»СТАБИЛЬНО в пределах v1: пути, имена параметров,
смысл кодов статуса и значений reason, а также структура ответа верхнего уровня:
contracts, data, а на маршруте списка — объект page, содержащий limit,
has_more и next_cursor. Основные ключи записи — id, slug,
model, locale, version, published_at, fields, relations и
media. Запись страницы также содержит page_contract. При expand запись
содержит expanded. Если в опубликованной версии заданы отдельные настройки
для поиска и публикации в соцсетях на возвращаемом языке, запись также содержит seo: { format: 1, title, description, image, canonical, robots, schema_type, search_priority? },
где image — { media_id, url, alt, width, height } или null, canonical —
https-адрес, путь на этом сайте или null, robots — index или noindex, а
schema_type — WebPage, AboutPage, ContactPage, Article или null
(рендерер использует его как тип страницы в структурированных данных).
Возвращаются только значения опубликованной версии; изменения черновика не включаются.
Необязательный search_priority — целое число от −10 до 10 (по умолчанию 0), которое влияет только на порядок результатов поиска по сайту и не создаёт метатегов.
Версионируемые дополнения плагинов могут добавить seo.renderer_metadata: объявленные метатеги,
ссылки и JSON-LD с идентификаторами плагина и дополнения. Основные SEO-настройки имеют приоритет.
Опубликованная иерархия может добавить ancestors[field] и seo.breadcrumbs[field]: упорядоченные
элементы { id, locale, label, url } с актуальными опубликованными названиями и принятыми маршрутами,
с учётом языковых правил сайта. Названия из черновиков и недоступные предки не включаются.
Цепочка содержит до 32 ближайших предков; возвращаются первые восемь публичных полей иерархии
в порядке их ключей. Отсутствие seo означает, что нет ни отдельных SEO-настроек, ни производных дополнений.
Запись со ссылками на публичные файлы библиотеки может содержать mediaMetadata, где ключ — идентификатор файла.
В значениях доступны только явно отмеченные как публичные поля файла. Они следуют текущей ревизии
файла независимо от опубликованной версии записи. Изменение публичности поля меняет ключ публичного
кеша. Генерируемые проекты сохраняют снимок этих публичных данных.
ВЕРСИИ КОНТРАКТА СТРАНИЦЫ: contracts.page.served — версия контракта страницы, по которой
сформирован ответ, сейчас 5. page_contract.required у записи страницы — версия
контракта, которую должен поддерживать рендерер для её отображения. Сейчас
для каждой читаемой страницы указывается версия, использованная в ответе, поэтому required равен
contracts.page.served. Это не хранимый format страницы — это другое число. Рендерер,
собранный под версию N, читает каждую страницу, у которой required не больше N,
а остальные отклоняет целиком. Он никогда не рендерит часть страницы. С fields=
ключ page_contract сохраняется, только когда выбран slides.
НЕСТАБИЛЬНЫ: содержимое next_cursor, текст
error и порядок ключей. Не используйте их внутреннюю структуру для обработки ответа. Новый необязательный параметр, новый reason или
новый ключ внутри элемента могут появиться в пределах v1. Ничто из
задокументированного здесь не изменит значения без новой версии в пути.
Примеры для клиентов API
Заголовок раздела «Примеры для клиентов API»Совместить фильтры, выбор полей и связанные записи
Заголовок раздела «Совместить фильтры, выбор полей и связанные записи»Действующий API CMS использует собственный язык запросов и не принимает Prisma-параметры where, select, include, orderBy, skip, take. Для развёрнутого API с аргументами Prisma см. запросы к API приложения.
В учебном примере предполагается модель article с текстовым полем или списком выбора kind, числом price, датой publication_date и связями categories, authors, каждая из которых допускает одну целевую модель. Подставьте реальные ключи полей сайта.
curl --get 'https://content.example.com/v1/article' \ --data-urlencode 'locale=en' \ --data-urlencode 'filter[kind]=guide' \ --data-urlencode 'filter[kind]=news' \ --data-urlencode 'filter[price][gte]=10' \ --data-urlencode 'filter[price][lt]=50' \ --data-urlencode 'tag=categories:architecture' \ --data-urlencode 'sort=-publication_date' \ --data-urlencode 'fields=title,price,publication_date' \ --data-urlencode 'expand=authors' \ --data-urlencode 'limit=10'Запрос читает опубликованные руководства или новости с ценой от 10 включительно до 50 исключительно и доступной категорией с опубликованным slug architecture. Разные поля и предикаты связей объединяются через AND. Ответ содержит выбранные поля, обязательные идентификаторы записи и доступных опубликованных авторов в expanded.authors.data. Сортировка — по убыванию значения publication_date; отсутствующие значения идут последними, при равенстве порядок задаёт ID. Раскрытие связей не делает черновики публичными.
У основного списка свой page.next_cursor, у каждой раскрытой связи — свой page.next_cursor для /v1/_related/:model. Не смешивайте их. Продолжайте основной запрос с неизменённым непрозрачным курсором и теми же параметрами; менять можно только limit. has_more указывает на продолжение, а не на общее количество.
Пределы одного запроса: 4 фильтруемых поля, 10 альтернатив равенства для каждого поля, 255 символов в значении фильтра, 16 пар relation/tag, 32 ключа выбранных полей, 3 раскрываемые связи, по 25 вложенных записей каждой связи. Раскрывается один уровень; пути с точками и произвольные вложенные запросы Prisma отклоняются. Точные правила и причины отказов перечислены выше в таблицах эндпоинтов.
Получить все записи с помощью курсора
Заголовок раздела «Получить все записи с помощью курсора»Запускайте это на сервере или во время сборки. Для запросов из браузера разрешите источник; приватные токены храните на сервере.
async function* everyArticle(origin, locale = 'en') { let cursor; do { const url = new URL(`${origin}/v1/article`); url.searchParams.set('locale', locale); url.searchParams.set('limit', '50'); // The cursor is opaque. Send back exactly what you were given; do not // decode it, and do not construct one. if (cursor) url.searchParams.set('cursor', cursor);
const response = await fetch(url); if (!response.ok) throw new Error(`${response.status} from ${url}`);
const body = await response.json(); yield* body.data; cursor = body.page.has_more ? body.page.next_cursor : undefined; } while (cursor);}Приватная модель
Заголовок раздела «Приватная модель»curl -H "Authorization: Bearer $DEE_WAN_SITE_TOKEN" \ 'https://example.com/v1/pricing'Храните токен на сервере. Всё, что отправлено в браузер, публично.
Использование сгенерированного TypeScript-клиента
Заголовок раздела «Использование сгенерированного TypeScript-клиента»Скачайте dee-wan-client.mjs в разделе Настройки → API и закоммитьте его. Нужен Node
18 или новее, больше ничего. Получите клиент по токену API сайта:
DEE_WAN_SITE_TOKEN=dw_site_… node dee-wan-client.mjs pull \ --url https://example.com --out src/dee-wan-public.tsПеред развёртыванием проверьте, что закоммиченный клиент всё ещё соответствует сайту:
DEE_WAN_SITE_TOKEN=dw_site_… node dee-wan-client.mjs check --file src/dee-wan-public.tscheck завершается с кодом 1 при изменении, нарушающем совместимость: удалена модель, поле или локаль, изменился
тип поля, модель стала приватной, поле стало необязательным, появилось новое значение select или
изменились локаль по умолчанию или политика локалей. Код 0 — ничего не изменилось или все изменения
совместимы, например добавлены модель, поле или локаль; он их перечисляет. Код 2 — проверка не
смогла выполниться. После изменения выполните pull и закоммитьте результат.
Клиент содержит оболочку ответа, union-тип локалей, точные типы полей, типы запросов с учётом модели и по одному методу на модель. В разделе Настройки → API его также можно скопировать или скачать вручную. Dee Wan не меняет ваш репозиторий.
Типы без сгенерированного клиента
Заголовок раздела «Типы без сгенерированного клиента»type RelationTarget = { instance_id: string; model: string; slug: string | null; display: string | null };type Media = { media_id: string; name: string; mime_type: string | null; kind: 'image' | 'document' | 'audio' | 'video' | 'file'; size: number | null; url: string; thumbnail_url: string | null; alt: string | null; width: number | null; height: number | null; visibility?: 'private';};type Item<F> = { id: string; slug: string | null; model: string; locale: string; version: number; published_at: string | null; fields: F; relations: Record<string, RelationTarget[]>; media: Record<string, Media[]>;};type Page<F> = { contracts: { page: { served: number } }; data: Item<F>[]; page: { limit: number; has_more: boolean; next_cursor: string | null };};type ApiError = { error: string; reason?: string; max?: number };
type Article = { title: string; body: string };
const response = await fetch(url);if (!response.ok) throw new Error(`${response.status} from ${url}`);const page: Page<Article> = await response.json();Пространственные запросы
Заголовок раздела «Пространственные запросы»Значение point: { "latitude": 13.75, "longitude": 100.5 }. Координаты — конечные числа в градусах. Радиус — в метрах.
GET /v1/places?filter[location][within_radius]=13.75,100.5,2000GET /v1/places?filter[location][within_bbox]=13.5,100.2,14,100.8GET /v1/places?filter[location][within_polygon]=<URL-encoded GeoJSON Polygon>GET /v1/places?sort=location&origin=13.75,100.5Границы: юг, запад, север, восток; включительно. Запад больше востока означает пересечение антимеридиана. Радиус и ближайшие объекты используют точное расстояние Хаверсина. sort=-location — дальние сначала. Пустые значения в конце, ID разрешает равенство. Курсор связан с точкой отсчёта и границами. Ограничения доступа к полю и модели сохраняются.
Для within_polygon передайте GeoJSON Polygon с URL-кодированием, например {"type":"Polygon","coordinates":[[[100,13],[101,13],[101,14],[100,14],[100,13]]]}. Координаты идут в порядке долгота, широта. Внешняя граница включается, внутренние области-отверстия и их границы исключаются. Поддерживаются вогнутые полигоны. Ограничения: 16 замкнутых колец, суммарно 256 координатных пар с учётом замыкающих точек и 16 КиБ входных данных. Неверные, вырожденные, самопересекающиеся или недопустимо пересекающиеся кольца отклоняются. Рёбра — прямые отрезки на плоскости долготы и широты; автоматического перехода через 180-й меридиан нет. MultiPolygon не принимается. Фильтр применяется до подсчёта и разбивки на страницы с теми же проверками доступа.
Инкрементальная синхронизация
Заголовок раздела «Инкрементальная синхронизация»GET /v1/_sync?limit=50 начинает постраничный снимок опубликованных записей и публичных медиа. model и locale ограничивают записи. Следуйте next_cursor до checkpoint. Для следующего опроса передайте cursor=<checkpoint>. Действуют обычные токены сайта и CORS. Токен с правами может включать разрешённые приватные модели; приватные медиа исключены.
Сначала примените data снимка, затем changes в том порядке, в котором они пришли. Изменение содержит operation: upsert|delete, entity: entry|media, id и язык записи; upsert содержит item. Поля source_site_id и source_sequence указывают событие журнала источника. sequence — номер в этом журнале, а не общий счётчик разных баз. Метка projection: true означает, что после изменения связанного контента обновлена родительская запись. Идентификаторы могут повторяться: применяйте изменения идемпотентно. Сохраняйте полученный checkpoint, не вычисляйте его по наибольшему номеру. Не применяйте вспомогательные массивы data и deleted отдельными пакетами: после удаления возможна повторная публикация.
Курсор непрозрачен и подписан. Передавайте его без изменений. Обычная публикация или снятие с публикации связанного контента, в том числе из общего источника, продолжают текущий поток: он возвращает обновлённые доступные родительские записи. Изменение другой записи целевой модели не требует нового снимка. Обработка зависимостей разбита на порции, поэтому страница может не содержать изменений, но вернуть next_cursor: продолжайте чтение по нему. 409 reset_required по-прежнему требует нового снимка после смены прав, приватности, политики публикации, определений компонентов, разрешений на общий доступ, поколения базы источника или границы хранения журнала. Перезапуск процесса сохраняет checkpoint. Скрытые поля связей и недоступные целевые модели не добавляют зависимостей. Если используемая база источника недоступна, сервер возвращает 503 sync_dependency_unavailable: повторите тот же курсор. Сбой неиспользуемого источника не блокирует поток. Один поток поддерживает до 32 фактически используемых баз источников и курсор размером до 12 КиБ. Если область слишком велика, сервер возвращает 400 sync_source_limit: создайте отдельные потоки с фильтром по модели. Автоматической очистки журнала нет. Журнал хранит идентификаторы и операции без тел черновиков и приватных полей.
Общая связь: shared[field] содержит { site_id, id, item }; недоступная цель возвращает item: null. Каждый ответ повторно проверяет разрешение и опубликованные публичные поля источника.
Один ответ разрешает не более 128 разных сочетаний разрешения и языка. Повторы, включая sync data и changes, используют одну текущую проекцию. При 422 shared_reference_limit уменьшите limit списка или sync и повторите запрос.
Любое изменение опубликованного контента на используемом исходном сайте также может сбросить checkpoint, включая изменения связанных публичных значений.
Стабильная версия ещё не выпущена (pre-MVP)
Коммит исходного кода: e207e63239a627fbd2ae9c414ef7c6eb19fcfe5a (с локальными изменениями)
Дата сборки (UTC): 2026-10-21T09:28:24.214Z
Коммит для обновления: не определён: в рабочей копии есть изменения. Примечания к выпуску