API для модулей CMS

Всё, что клиент делает в кабинете, модуль Битрикса или ReadyScript может делать из админки его сайта: запускать проверки, читать отчёт, настраивать баннер согласий, вести журнал согласий и собирать документы. Переходить на prdomain.ru для этого не нужно.

Тарифы

Для модуля ReadyScript

0 ₽

разово

Перейти к модулю Перейти к модулю
  • Проверок: 100
  • Все наблюдения с доказательствами — владельцу подтверждённого сайта
  • Доступ к API: модули Битрикса и ReadyScript
  • Уведомления о регрессиях
  • Скрипт согласий в облаке
Для модуля Битрикс

0 ₽

разово

Перейти к модулю Перейти к модулю
  • Проверок: 100
  • Все наблюдения с доказательствами — владельцу подтверждённого сайта
  • Доступ к API: модули Битрикса и ReadyScript
  • Уведомления о регрессиях
  • Скрипт согласий в облаке

Доступ

Запросы идут на https://prdomain.ru/api/v1 с токеном в заголовке:

Authorization: Bearer ваш-токен

Токен выдаётся на сайт, а не на аккаунт. Модуль стоит на одном сайте, и ключ, утёкший из настроек чужой админки, не должен открывать остальные сайты клиента. Для десяти сайтов — десять токенов.

Токен создаётся в кабинете, в карточке сайта, раздел «Доступ к API». Он показывается один раз: в базе хранится только отпечаток, восстановить значение нельзя. Потерян — выдайте новый, старый отзовите.

Сайт определяется по токену. Домен в запросах не передаётся нигде — иначе можно было бы спросить про чужой сайт.

Доступ к API входит в отдельные тарифы. Без него любой метод отвечает 403 с кодом api_not_in_plan.

Ошибки и лимиты

Ошибка у всех методов одной формы:

{
    "error": {
        "code": "unauthorized",
        "message": "Токен не найден или отозван."
    }
}
КодСтатусКогда
unauthorized401Нет заголовка, токен не найден или отозван
token_expired401Срок действия токена истёк
site_disabled403Сайт отключён в кабинете
api_not_in_plan403Тариф не включает доступ к API
validation_failed422Запрос не прошёл проверку, подробности в details
not_found404Объект не найден или принадлежит другому сайту
too_many_requests429Больше 120 запросов в минуту на токен
server_error500Внутренняя ошибка; текст исключения наружу не отдаётся

Частота — 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/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 заметно нагружает сервер.