Доступ
Запросы идут на https://prdomain.ru/api/v1 с токеном в заголовке:
Authorization: Bearer ваш-токен
Токен выдаётся на сайт, а не на аккаунт. Модуль стоит на одном сайте, и ключ, утёкший из настроек чужой админки, не должен открывать остальные сайты клиента. Для десяти сайтов — десять токенов.
Токен создаётся в кабинете, в карточке сайта, раздел «Доступ к API». Он показывается один раз: в базе хранится только отпечаток, восстановить значение нельзя. Потерян — выдайте новый, старый отзовите.
Сайт определяется по токену. Домен в запросах не передаётся нигде — иначе можно было бы спросить про чужой сайт.
Доступ к API входит в отдельные тарифы. Без него любой метод отвечает 403 с кодом api_not_in_plan.
Ошибки и лимиты
Ошибка у всех методов одной формы:
{
"error": {
"code": "unauthorized",
"message": "Токен не найден или отозван."
}
}
| Код | Статус | Когда |
|---|---|---|
unauthorized | 401 | Нет заголовка, токен не найден или отозван |
token_expired | 401 | Срок действия токена истёк |
site_disabled | 403 | Сайт отключён в кабинете |
api_not_in_plan | 403 | Тариф не включает доступ к API |
validation_failed | 422 | Запрос не прошёл проверку, подробности в details |
not_found | 404 | Объект не найден или принадлежит другому сайту |
too_many_requests | 429 | Больше 120 запросов в минуту на токен |
server_error | 500 | Внутренняя ошибка; текст исключения наружу не отдаётся |
Частота — 120 запросов в минуту на токен, а не на адрес: модули клиентов часто стоят на общем хостинге, и лимит по адресу наказывал бы соседей.
Вебхуков от нас на сайт клиента нет: о готовности проверки модуль узнаёт опросом. Это работает и там, где у сайта нет внешнего адреса.
Подключение модуля
POST /api/v1/register
Без токена. Платформа — в заголовке X-PrDomain-Platform.
Заводит аккаунт, сайт и токен прямо из админки CMS, без кабинета. Токена у модуля ещё нет, поэтому метод работает без него, а платформа указывается заголовком `X-PrDomain-Platform: readyscript/1.0.0`. Новый e-mail — токен сразу в ответе (`201`). E-mail уже есть в базе — токена в ответе нет: на почту уходит код, и модуль обменивает его на токен методом `POST register/confirm` (`202`).
| Поле | Тип | Зачем |
|---|---|---|
domain * |
string | Адрес сайта: example.ru или ссылка на любую его страницу |
email * |
string | Почта владельца: на неё заводится кабинет и приходит код при повторном подключении |
accept_terms * |
boolean | Согласие с офертой и на обработку персональных данных — чекбокс в модуле, не отмеченный заранее |
Ответ:
{
"status": "registered",
"token": "7|Kx3vQ9…",
"site": {
"domain": "romashka.ru",
"site_key": "abcdefgh12345678",
"snippet": "<script src=\"https://cdn.prdomain.ru/c/abcdefgh12345678.js\"></script>",
"verified": false
},
"verification": {
"file_name": "prdomain-3f9a1c7e5b2d4a60.txt",
"file_content": "3f9a1c7e5b2d4a60",
"meta_tag": "<meta name=\"prdomain-verification\" content=\"3f9a1c7e5b2d4a60\">"
}
}
Ответ 202 с status: confirmation_required и registration_id — e-mail уже зарегистрирован, код отправлен на почту.
Сразу после подключения модуль может выложить файл из verification в корень сайта и вызвать POST verification — так подтверждаются права.
domain_taken (409) — права на сайт уже подтверждены в другом аккаунте; sites_limit (409) — у аккаунта закончились сайты по тарифу; platform_unsupported (422) — нет заголовка платформы или она не поддерживается; platform_unavailable (503) — подключение этой платформы временно закрыто.
Лимит — 20 запросов подключения в час с одного адреса и 3 письма с кодом в час на один e-mail.
POST /api/v1/register/confirm
Без токена. Платформа — в заголовке X-PrDomain-Platform.
Обменивает код из письма на токен сайта. Нужен, когда e-mail уже был в базе: переустановка модуля или потерянный токен. Прежние токены этого модуля для сайта при этом отзываются — в старой копии настроек они больше не работают.
| Поле | Тип | Зачем |
|---|---|---|
registration_id * |
string | Номер из ответа `POST register` |
code * |
string | Шесть цифр из письма |
Ответ:
{
"status": "registered",
"token": "8|Lm4wR2…",
"site": {
"domain": "romashka.ru",
"site_key": "abcdefgh12345678",
"snippet": "<script src=\"https://cdn.prdomain.ru/c/abcdefgh12345678.js\"></script>",
"verified": true
},
"verification": {
"file_name": "prdomain-3f9a1c7e5b2d4a60.txt",
"file_content": "3f9a1c7e5b2d4a60",
"meta_tag": "<meta name=\"prdomain-verification\" content=\"3f9a1c7e5b2d4a60\">"
}
}
Код действует 30 минут, пять неверных попыток — и он сгорает. invalid_code (422) — код неверный, устарел или уже использован.
Сайт
GET /api/v1/site
Что видно с нашей стороны: подтверждены ли права, стоит ли скрипт, что даёт тариф и каков итог последней проверки. Модуль спрашивает этот метод при сохранении настроек и показывает ответ администратору CMS.
Ответ:
{
"domain": "romashka.ru",
"verified": true,
"verified_at": "2026-10-01T12:00:00+05:00",
"script": {
"snippet": "<script src=\"https://cdn.prdomain.ru/c/abcdefgh12345678.js\"></script>",
"config_version": 3,
"install_status": "ok",
"install_checked_at": "2026-10-01T12:05:00+05:00"
},
"plan": {
"name": "Агентский",
"api": true,
"full_report": true,
"checks_limit": 10,
"checks_remaining": 7
},
"last_scan": {
"id": 42,
"status": "done",
"score": 70,
"risk": "medium",
"finished_at": "2026-10-01T11:00:00+05:00",
"risks_count": 4
}
}
last_scan равен null, пока ни одна проверка не завершилась.
Проверки
POST /api/v1/scans
Ставит сайт в очередь проверки и возвращает её состояние. Если проверка этого сайта уже идёт, возвращается она же с `created: false` — повторное нажатие кнопки в админке CMS не создаёт вторую. Расходует лимит проверок тарифа.
Ответ:
{
"id": 43,
"status": "queued",
"score": null,
"risk": null,
"started_at": null,
"finished_at": null,
"risks_count": 0,
"created": true
}
Ответ 201, если проверка создана, и 200, если вернули уже идущую.
Код checks_exhausted (402) — на тарифе закончились проверки; suppressed (409) — владелец сайта попросил его не проверять.
GET /api/v1/scans
До двадцати последних проверок сайта, новые сверху. Нужен, чтобы показать историю в админке CMS и понять, когда сайт проверяли в последний раз.
Ответ:
{
"scans": [
{
"id": 43,
"status": "done",
"score": 70,
"risk": "medium",
"started_at": "2026-10-01T11:00:00+05:00",
"finished_at": "2026-10-01T11:01:30+05:00",
"risks_count": 4
}
]
}
GET /api/v1/scans/{scan}
Состояние одной проверки. Этот метод модуль опрашивает, пока статус не станет `done` или `failed`: обычно проверка занимает около минуты. Вебхуков от нас нет.
Ответ:
{
"id": 43,
"status": "running",
"score": null,
"risk": null,
"started_at": "2026-10-01T11:00:00+05:00",
"finished_at": null,
"risks_count": 0
}
Проверка другого сайта по номеру не открывается — 404.
GET /api/v1/scans/{scan}/report
Признаки риска с нормами закона и возможным штрафом. Состав зависит от тарифа и подтверждения прав: полный отчёт с доказательствами отдаётся владельцу подтверждённого сайта на тарифе с полным отчётом, иначе — открытая часть и число скрытых признаков.
Ответ:
{
"scan": {
"id": 43,
"status": "done",
"score": 70,
"risk": "medium",
"started_at": "2026-10-01T11:00:00+05:00",
"finished_at": "2026-10-01T11:01:30+05:00",
"risks_count": 4
},
"full": true,
"hidden_count": 0,
"fine_max": 700000,
"operator_form": "legal",
"findings": [
{
"code": "trackers.before_consent",
"group": "cookies",
"title": "Счётчики загружаются до выбора посетителя",
"summary": "До выбора посетителя загружаются сервисы аналитики или рекламы: Яндекс Метрика.",
"koap_part": "2",
"weight": 10,
"evidence": [
{
"label": "Сервисы",
"items": [
"Яндекс Метрика"
],
"mono": false
}
]
}
]
}
fine_max — возможный штраф по ст. 13.11 КоАП для формы оператора из operator_form, а не назначенный.
Пока проверка не завершена — 409, код scan_not_ready.
Права на сайт
GET /api/v1/verification
Файл и мета-тег, которыми подтверждаются права на сайт. Модуль может выложить файл сам, без участия человека, — тогда полный отчёт откроется без похода в кабинет.
Ответ:
{
"verified": false,
"verified_at": null,
"token": "f3a1c7d2e9b64a0c8d5e2f1a7b3c9d4e",
"file_name": "prdomain-f3a1c7d2e9b64a0c8d5e2f1a7b3c9d4e.txt",
"file_url": "https://romashka.ru/prdomain-f3a1c7d2e9b64a0c8d5e2f1a7b3c9d4e.txt",
"file_content": "f3a1c7d2e9b64a0c8d5e2f1a7b3c9d4e",
"meta_tag": "<meta name=\"prdomain-verification\" content=\"f3a1c7d2e9b64a0c8d5e2f1a7b3c9d4e\">"
}
Код у сайта один и не меняется: выложенный файл продолжает работать.
POST /api/v1/verification
Проверяет, что код выложен, и подтверждает права на сайт. Нужен, чтобы открыть полный отчёт: оплата тарифа сама по себе не доказывает, что сайт ваш.
Ответ:
{
"verified": true,
"verified_at": "2026-10-01T12:00:00+05:00"
}
Если кода на сайте нет — 409, код verification_failed.
Не чаще десяти попыток в час: проверка ходит на сторонний сайт.
Скрипт согласий
GET /api/v1/consent/config
Строка скрипта согласий для вставки в шаблон сайта и текущие опубликованные настройки баннера: макет, оформление, подключённые счётчики.
Ответ:
{
"snippet": "<script src=\"https://cdn.prdomain.ru/c/abcdefgh12345678.js\"></script>",
"version": 3,
"published_at": "2026-10-01T12:00:00+05:00",
"branding": false,
"ui": {
"layout": "bar",
"preset": "minimal"
},
"counters": {
"yandexMetrika": [
{
"id": 12345678
}
]
}
}
Строку скрипта ставят первым тегом в <head>, без async и defer.
PUT /api/v1/consent/config
Меняет оформление баннера и номера счётчиков. Новая версия появляется на сайте в течение пяти минут. Номера счётчиков проходят строгую проверку: в конфиг попадает только идентификатор, произвольный код через API подсунуть нельзя.
| Поле | Тип | Зачем |
|---|---|---|
ui |
object | Оформление: layout (bar, corner, modal), preset |
counters |
object | Номера счётчиков: metrika, tmr, vk, ytm, rambler, liveinternet, gtm, ga, tiktok |
Ответ:
{
"version": 4,
"applied_in": "до 5 минут"
}
Ссылку PrDomain в баннере отключить нельзя: это условие бесплатного тарифа.
POST /api/v1/consent/install-check
Загружает главную страницу сайта и смотрит: стоит ли наш скрипт первым, нет ли второго баннера согласий и трекеров прямо в разметке. Найденный скрипт с ключом сайта подтверждает права автоматически.
Ответ:
{
"status": "ok",
"verified": true,
"checks": [
{
"ok": true,
"title": "Стоит первым и без async",
"text": "Скрипт успевает поставить блокировку раньше счётчиков."
}
]
}
status: ok, warning, conflict, missing или unreachable.
Не чаще десяти проверок в минуту: каждая загружает сайт целиком.
Журнал согласий
GET /api/v1/consents
Журнал согласий сайта, новые сверху, с подсчётом решений за выбранный период. Страницы листаются курсором `before`, а не номером: журнал пополняется во время выгрузки, и нумерованные страницы при этом повторяли бы одни записи и теряли другие.
| Параметр | Тип | Зачем |
|---|---|---|
from |
date | С какого времени (ISO 8601) |
to |
date | До какого времени |
decision |
string | all — согласен со всем, partial — выбрал сам, reject — отказался |
source |
string | banner — наш баннер, api — форма CMS |
limit |
int | Сколько записей, до 500; по умолчанию 100 |
before |
int | Записи старше этого номера — из `next_before` прошлого ответа |
Ответ:
{
"consents": [
{
"id": 9120,
"consent_id": "k3m9p2q7x1",
"action": "custom",
"categories": [
"analytics"
],
"text_version": "2026-10",
"text_hash": null,
"source": "banner",
"page": "https://romashka.ru/catalog",
"ip": "95.165.12.0",
"created_at": "2026-10-01T12:00:00+05:00"
}
],
"next_before": 9120,
"summary": {
"total": 1840,
"accept_all": 1510,
"custom": 210,
"reject_all": 120
}
}
next_before равен null, когда записей больше нет.
Время передаётся закодированным: +05:00 в адресе иначе превратится в пробел, и отбор не сработает.
IP хранится усечённым, браузер — только хешем: журнал доказывает согласие, а не следит за посетителем.
POST /api/v1/consents
Записывает согласие, которое взяла форма CMS, а не наш баннер: форма заказа, обратный звонок, корзина. Иначе такие согласия остаются в базе магазина, где нет ни версии текста, ни его хеша, и доказывать их нечем. В журнале они помечены источником `api`.
| Поле | Тип | Зачем |
|---|---|---|
consent_id |
string | Номер согласия: 8–32 знака, строчные буквы и цифры. Его же модуль хранит у заказа |
action |
string | accept_all, reject_all или custom |
categories |
array | analytics, advertising, functional; при accept_all и reject_all можно не передавать |
text_version |
string | Версия текста согласия, который видел посетитель |
text_hash |
string | SHA-256 текста согласия — им доказывается, что текст не менялся |
page |
string | Адрес страницы; параметры запроса отбрасываются |
ip |
string | IP посетителя — мы усекаем его сами |
user_agent |
string | Браузер посетителя |
created_at |
date | Время события, если согласие записывают не сразу |
Ответ:
{
"id": 9121,
"created_at": "2026-10-01T12:00:00+05:00"
}
IP и браузер передаёт модуль: запрос идёт с сервера CMS, и адрес в нём — магазина, а не покупателя.
Время из будущего не принимается — ставится текущее.
Удаления записей в API нет: журнал — доказательство.
GET /api/v1/consents.csv
Тот же журнал файлом CSV — для ответа на запрос Роскомнадзора. Отбор задаётся теми же параметрами, что у списка. Разделитель — точка с запятой, в начале BOM: файл открывается в Excel без настройки кодировки.
| Параметр | Тип | Зачем |
|---|---|---|
from |
date | С какого времени |
to |
date | До какого времени |
decision |
string | all, partial или reject |
source |
string | banner или api |
Ответ — файл text/csv, а не JSON.
Ответ — файл text/csv, а не JSON.
Не чаще десяти выгрузок в минуту.
Документы
GET /api/v1/documents
Состояние анкеты и четыре документа: политика, cookie-политика, согласие для форм и уведомление в Роскомнадзор. По `published_url` видно, что уже лежит на самом сайте — его находит проверка, и создавать страницу заново модулю не нужно.
Ответ:
{
"questionnaire": {
"filled": true,
"complete": true,
"missing": [],
"revision": 2,
"updated_at": "2026-10-01T12:00:00+05:00"
},
"documents": [
{
"slug": "policy-generator",
"title": "Политика обработки персональных данных",
"ready": true,
"published_url": "https://romashka.ru/policy",
"checkable": true
}
]
}
checkable: false у уведомления в Роскомнадзор: это не страница сайта, обходом его не проверить.
GET /api/v1/documents/questionnaire
Вопросы анкеты с типами и вариантами — по ним модуль рисует форму в админке CMS, и при изменении перечня вопросов переписывать модуль не придётся. Вместе с ними текущие ответы: если анкету ещё не заполняли, отдаётся заготовка по данным последней проверки.
Ответ:
{
"steps": [
{
"title": "Оператор",
"hint": "Эти сведения войдут почти во все документы.",
"fields": [
{
"name": "operator_name",
"label": "Наименование или ФИО",
"type": "text",
"required": true
}
]
}
],
"answers": {
"operator_name": "ООО «Ромашка»",
"site_url": "https://romashka.ru"
},
"required": [
"operator_name",
"operator_inn",
"operator_address",
"site_url",
"contact_email"
],
"missing": [
"operator_inn"
]
}
Типы полей: text, email, textarea, radio, select, checkbox, repeater.
Анкета одна и та же в кабинете и в API: правка через модуль видна в кабинете и наоборот.
PUT /api/v1/documents/questionnaire
Сохраняет ответы анкеты. Ответы заменяются целиком: присылать часть анкеты и догадываться, что стало с остальным, — верный способ получить документ с прошлогодними реквизитами. Незнакомые поля отбрасываются.
| Поле | Тип | Зачем |
|---|---|---|
answers |
object | Ответы по именам полей из GET documents/questionnaire |
Ответ:
{
"saved": true,
"revision": 3,
"missing": []
}
Номер редакции повышается, только если изменились ответы, от которых зависят документы.
GET /api/v1/documents/{slug}
Готовый документ разметкой — её модуль кладёт в страницу CMS. Без стилей: страницу клиент оформит своим шаблоном. Тот же текст, что в кабинете и в скачанном файле.
Ответ:
{
"slug": "policy-generator",
"title": "Политика обработки персональных данных",
"filename": "politika-obrabotki-pdn",
"revision": 2,
"revised_at": "2026-10-01T12:00:00+05:00",
"html": "<h1>Политика обработки персональных данных</h1><p>…</p>"
}
slug: policy-generator, cookie-policy, consent-generator, uvedomlenie-rkn.
Пока обязательные поля анкеты не заполнены — 409, код questionnaire_incomplete, их список в details.missing.
GET /api/v1/documents/{slug}/file
Тот же документ файлом DOCX или PDF — когда он нужен на подпись. Файлы не хранятся: документ собирается из ответов анкеты при каждом обращении.
| Параметр | Тип | Зачем |
|---|---|---|
format |
string | docx (по умолчанию) или pdf |
Ответ — файл application/octet-stream, а не JSON.
Ответ — файл, а не JSON.
Не чаще двадцати обращений в минуту: сборка PDF заметно нагружает сервер.