REST API · документация

REST API сервиса проверки сайтов на соответствие 152-ФЗ

Обновлено 06.11.2026 Справочник методов Коды ошибок Документация MCP Пакеты проверок

Документация предварительная: API и MCP откроются после запуска сервиса. Ключи будут создаваться в личном кабинете.

Версия API: v1. Версия документа 1.3 от 24.10.2026. Адрес сервиса — https://pdn-152.ru.

1. Что умеет API

API даёт программный доступ к той же проверке, что и сайт, и возвращает полный подробный отчёт: нарушения со ссылками на закон, статью и часть, доказательства, санкции, практику, рекомендации и задания разработчику.

Для кого: веб-студии и агентства (массовые проверки клиентских сайтов, отчёты в своих системах), разработчики CMS и конструкторов, юристы и консультанты, интеграторы CRM.

Что можно делать:

Возможность Стоит кредитов
Запустить проверку сайта 1 кредит за проверку
Получить статус, полный отчёт в JSON или PDF, список проверок нет
Справочники: проверки каталога, профили организаций, расчёт штрафов нет
Поиск по базе норм и практики нет
Настроить вебхуки о завершении проверок нет

Генерация пакета документов через API пока недоступна — только в личном кабинете.

2. Как начать

  1. Войдите в личный кабинет через Яндекс ID или VK ID.
  2. В разделе «API и MCP» купите пакет проверок (кредитов).
  3. Создайте ключ: укажите название, права и, по желанию, IP-адреса, с которых разрешены запросы. Ключ показывается один раз — сохраните его в хранилище секретов.
  4. Выполните первый запрос:
export BASE="https://pdn-152.ru/v1"
export KEY="ck_live_ab12cd34_…"

curl -s "$BASE/account" -H "Authorization: Bearer $KEY"
{
  "user_id": "01927f3a-5c1e-7b2a-9d4e-0a1b2c3d4e5f",
  "credits": {"balance": 48, "expiring": [{"amount": 48, "expires_at": "2027-09-23T00:00:00Z"}]},
  "key": {"prefix": "ab12cd34", "name": "CRM агентства", "scopes": ["scans:write", "scans:read", "kb:read"], "mode": "live"},
  "limits": {"requests_per_minute": 60, "scan_creates_per_minute": 10, "concurrent_scans": 5}
}

3. Основы

3.1 Адрес и формат

  • Базовый адрес: https://pdn-152.ru/v1. Только HTTPS.
  • Запросы и ответы — JSON в UTF-8 (Content-Type: application/json), кроме PDF.
  • Даты — ISO 8601 в UTC (2026-09-23T14:05:00Z).
  • Идентификаторы — UUID.
  • Деньги — рубли целыми числами (150000 = 150 000 ₽).

3.2 Аутентификация

Каждый запрос содержит заголовок:

Authorization: Bearer ck_live_<префикс>_<секрет>

Передавать ключ в адресе запроса нельзя: такие запросы отклоняются с кодом 400, а ключ рекомендуется перевыпустить.

Типы ключей:

Префикс Режим Поведение
ck_live_ боевой настоящие проверки, списываются кредиты
ck_test_ тестовый кредиты не тратятся, сайт не сканируется: через 5–10 секунд возвращается демонстрационный отчёт с фиксированными данными. До 100 проверок в сутки. Для отладки интеграции

Права ключа:

Право Разрешает
scans:write создавать проверки (тратит кредиты)
scans:read читать проверки и отчёты
kb:read справочники, штрафы, база норм и практики
webhooks:manage управлять вебхуками

3.3 Кредиты

  • 1 проверка = 1 кредит, списывается при создании проверки.
  • Если проверка завершилась статусом error (сайт недоступен, заблокировал обход, внутренняя ошибка), кредит возвращается автоматически. Проверка со статусом partial считается выполненной.
  • Повтор запроса с тем же Idempotency-Key в течение 24 часов не списывает кредит повторно и возвращает ту же проверку.
  • Кредиты действуют 365 дней с даты покупки; сначала расходуются те, что истекают раньше.
  • При нехватке кредитов — ошибка 402 insufficient_credits.

Пакеты кредитов покупаются в личном кабинете («API и MCP» → «Купить проверки»):

Пакет Скидка Цена одной проверки
10 проверок 10% 1 701 ₽
50 проверок 20% 1 512 ₽
100 проверок 30% 1 323 ₽

Скидка считается от цены подробной проверки на сайте (1 890 ₽). Актуальные цены — на странице /tarify.

3.4 Идемпотентность

Для POST /scans рекомендуется заголовок Idempotency-Key — любая уникальная строка до 64 символов (например, UUID или crm-client-1842-2026-09-23). Если ответ не дошёл из-за сети, повторите запрос с тем же ключом: будет возвращена уже созданная проверка. Тот же ключ с другим телом запроса — ошибка 409 idempotency_conflict.

3.5 Лимиты

Лимит Значение
Запросов в минуту на ключ 60
Создание проверок в минуту 10
Одновременно выполняемых проверок 5 (остальные ждут в очереди со статусом queued)

Каждый ответ содержит заголовки X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset (секунд до сброса). При превышении — 429 rate_limited и заголовок Retry-After. Для тарифов агентств лимиты повышаются по запросу.

3.6 Пагинация

Списки возвращают до limit элементов (по умолчанию 20, максимум 100) и курсор следующей страницы:

{"data": [ … ], "next_cursor": "eyJpZCI6IjAxOTI3…", "has_more": true}

Следующая страница: ?cursor=<next_cursor>.

3.7 Ошибки

Формат — application/problem+json (RFC 9457):

{
  "type": "https://pdn-152.ru/docs/api/errors#insufficient_credits",
  "title": "Недостаточно кредитов",
  "status": 402,
  "code": "insufficient_credits",
  "detail": "На балансе 0 кредитов. Пополните баланс в личном кабинете.",
  "request_id": "req_01927f3b8c2d"
}
HTTP code Что значит Что делать
400 validation_error неверные параметры; в ответе поле errors со списком исправить запрос
400 invalid_url адрес не разобран или схема не http/https исправить адрес
400 key_in_url ключ передан в адресе перевыпустить ключ, передавать в заголовке
401 unauthorized нет ключа, ключ неверный или отозван проверить ключ
402 insufficient_credits нет кредитов пополнить баланс
403 forbidden_scope у ключа нет нужного права выпустить ключ с правом
403 ip_not_allowed запрос с IP вне списка ключа добавить IP в настройках ключа
404 not_found объекта нет или он принадлежит другому аккаунту проверить идентификатор
409 idempotency_conflict тот же Idempotency-Key с другим телом новый ключ идемпотентности
409 scan_not_ready отчёт ещё не готов дождаться done или partial
422 url_forbidden адрес ведёт во внутреннюю сеть, на IP-адрес или нестандартный порт проверять только публичные сайты по доменному имени
422 domain_opted_out владелец сайта отказался от автоматических проверок —
429 rate_limited превышен лимит повторить после Retry-After
500 internal_error ошибка сервиса повторить позже; сообщить request_id в поддержку
503 service_busy очередь перегружена повторить после Retry-After

3.8 Версии

Путь содержит версию (/v1). Внутри версии добавляются только новые необязательные поля и новые значения перечислений — клиенты должны игнорировать неизвестные поля. Несовместимые изменения выходят в /v2; старая версия работает не меньше 6 месяцев после объявления, её ответы содержат заголовки Deprecation и Sunset. История — в разделе 9.

4. Проверки

4.1 Создать проверку

POST /scans — право scans:write, 1 кредит.

Поле Тип Обязательное Описание
url строка да адрес сайта, например https://example.ru. Проверяется главная страница и до 70 страниц сайта
profile строка нет профиль организации (GET /profiles): general (по умолчанию), shop, services, clinic, education, b2b
org_type строка нет для расчёта штрафов: legal (по умолчанию), ip, self_employed, person
callback_url строка нет адрес для одноразового уведомления о завершении этой проверки (в дополнение к вебхукам из раздела 7); только https. Уведомление приходит без подписи (общего секрета нет), в теле — номер проверки, статус, external_id и краткие итоги: результат перепроверяйте запросом GET /scans/{id}
external_id строка нет ваш идентификатор (до 128 символов), возвращается в ответах и вебхуках
reuse_recent логический нет true — вернуть свою проверку этого домена не старше 4 часов без списания кредита, если она есть. По умолчанию false
curl -s -X POST "$BASE/scans" \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: crm-client-1842-2026-09-23" \
  -d '{"url": "https://example.ru", "profile": "shop", "org_type": "legal", "external_id": "client-1842"}'

Ответ 202 Accepted:

{
  "id": "01927f40-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
  "status": "queued",
  "url": "https://example.ru/",
  "domain": "example.ru",
  "profile": "shop",
  "org_type": "legal",
  "external_id": "client-1842",
  "credits_charged": 1,
  "created_at": "2026-09-23T14:05:00Z",
  "links": {"self": "/v1/scans/01927f40-…", "report": "/v1/scans/01927f40-…/report"}
}

Проверка обычно занимает 1–10 минут, максимум 10 минут на сайт. По умолчанию каждый запрос запускает свежую проверку. Если передать "reuse_recent": true, а тот же домен с тем же профилем проверялся вашим аккаунтом менее 4 часов назад, результат выдаётся из этой проверки мгновенно и кредит не списывается; поле source_scan_id покажет исходную проверку, а created_at — время, когда она выполнена. Показывайте это время своим пользователям: сайт мог измениться после проверки, а новая полная проверка того же сайта при reuse_recent возможна через 4 часа после предыдущей. Чужие проверки (других аккаунтов и анонимные) через API никогда не выдаются. Если владелец сайта запретил проверки другими, возвращается 422 domain_opted_out, кредит не списывается.

4.2 Статус проверки

GET /scans/{id} — право scans:read.

{
  "id": "01927f40-…",
  "status": "done",
  "stage": "completed",
  "url": "https://example.ru/",
  "domain": "example.ru",
  "profile": "shop",
  "org_type": "legal",
  "external_id": "client-1842",
  "created_at": "2026-09-23T14:05:00Z",
  "finished_at": "2026-09-23T14:07:12Z",
  "summary": {
    "score": 41,
    "zone": "red",
    "check_status": "ok",
    "check_failed_reason": null,
    "pages_checked": 34,
    "violations": 9,
    "risks": 4,
    "manual": 7,
    "passed": 58,
    "fines_total": {"min": 520000, "max": 1360000, "currency": "RUB", "org_type": "legal"}
  },
  "kb_version": "нормы на 23.09.2026",
  "catalog_version": "2026-09-21"
}

Статусы:

status Значение
queued в очереди
crawling обход сайта
analyzing анализ правил и документов
done готово
partial готово частично: обход остановило время или лимит страниц, часть страниц не открылась или сработала защита от ботов; в отчёте указано, что не проверено
error проверка не выполнена; поле error.code: site_unreachable, blocked_by_site, timeout, internal; кредит возвращён

Рекомендуемый опрос: каждые 5–10 секунд, либо используйте вебхуки.

Поля summary, добавленные в версии 1.35 плана (расширение ответа, существующие поля не менялись):

Поле Значение
check_status ok — полная проверка; partial — частичная (причины в отчёте); failed — «Не удалось проверить сайт»
check_failed_reason только при failed: empty_page — главная не открылась или пуста; few_pages — просмотрено слишком мало страниц для проверки
pages_checked сколько страниц просмотрено
score, zone null, если check_status = failed: оценки нет

Если проверка не состоялась (check_status = failed), оценки и зоны нет (null), нарушений, рисков и штрафов нет (в summary нули), кредит возвращается автоматически, как при ошибке; статус при этом остаётся done или partial. Это не ошибка вашего запроса: повторите проверку позже. Сайт, у которого всего одна страница, проверкой, которая не состоялась, не считается: отчёт строится по ней по всем пунктам.

4.3 Список проверок

GET /scans?domain=example.ru&status=done&created_after=2026-09-01T00:00:00Z&external_id=client-1842&limit=20 — право scans:read. Возвращает проверки аккаунта (включая созданные на сайте и через MCP), новые первыми, в формате ответа 4.2.

4.4 Полный отчёт

GET /scans/{id}/report — право scans:read. Доступен при статусе done или partial.

Параметры:

Параметр Значения По умолчанию
format json, pdf json
include через запятую: findings, passed, manual, services, cookies, mismatches, pages, plan всё
status фильтр карточек: violation, risk, manual, pass все
law фильтр по закону: 152-FZ, ZOZPP, 38-FZ, 436-FZ, 168-FZ, 118n, 1493 все

PDF: GET /scans/{id}/report?format=pdf возвращает 302 на временную ссылку (5 минут) либо файл с Content-Type: application/pdf при заголовке Accept: application/pdf.

Как работают параметры. Итоги (summary), оценка и разбивка по законам (by_law) есть в любом ответе. include=findings — карточки со статусами violation, risk и error; include=passed добавляет в тот же список findings карточки pass; include=manual даёт списки manual_checks и recommended — пункты для ручной проверки. Фильтр status сужает карточки: violation, risk и pass — в findings, manual — в manual_checks. Фильтр law оставляет карточки и пункты, которые относятся к этому закону, и его строку в by_law; итоги при этом не пересчитываются. Без параметров в ответе всё, включая пройденные проверки.

Формат JSON — раздел 5.

4.5 Справочники

Запрос Что возвращает
GET /checks все проверки каталога: id, category, name, law, severity, priority, automation (auto, auto+ai+review, manual), applies_to_profiles, verification
GET /checks/{id} одна проверка с описанием метода, нормами и связанными делами практики
GET /profiles профили организаций и какие группы проверок в них включены
POST /fines/estimate расчёт диапазона штрафов по списку проверок или составов КоАП (тот же расчёт, что в отчётах)
curl -s -X POST "$BASE/fines/estimate" -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"check_ids": ["DOC-01", "FRM-03", "CKI-02"], "org_type": "ip"}'
{
  "org_type": "ip",
  "items": [
    {"fine_key": "13.11.3", "article": "13.11", "part": "3", "min": 10000, "max": 20000, "from_checks": ["DOC-01"]},
    {"fine_key": "13.11.2", "article": "13.11", "part": "2", "min": 100000, "max": 300000, "from_checks": ["FRM-03", "CKI-02"]}
  ],
  "total": {"min": 110000, "max": 320000},
  "disclaimer": "Теоретический диапазон по составам КоАП РФ; не прогноз и не юридическое заключение."
}

Суммы в примере иллюстративные; действующие значения берутся из справочника штрафов, сверенного юристом.

4.6 База норм и практики

Запрос Что возвращает
GET /kb/search?q=согласие на распространение&type=norm,case&limit=10 найденные фрагменты норм (акт, статья, часть, текст, редакция) и дела практики (орган, дата, номер, итог, достоверность A/B/C)
GET /kb/norms/{norm_id}/{article} текст статьи по частям, например /kb/norms/152-FZ/9
GET /kb/cases/{id} карточка дела практики

Право kb:read, кредиты не тратятся, лимит — 30 запросов в минуту.

5. Формат полного отчёта (JSON)

Сокращённый пример; полная схема — в справочнике /docs/api/reference.

{
  "scan_id": "01927f40-…",
  "site": {"url": "https://example.ru/", "domain": "example.ru", "ip_country": "RU", "hosting": "…", "tls": {"valid": true, "expires_at": "2027-01-10"}},
  "profile": "shop",
  "org_type": "legal",
  "generated_at": "2026-09-23T14:07:12Z",
  "kb_version": "нормы на 23.09.2026",
  "disclaimer": "Результат автоматической проверки на дату. Не является юридическим заключением.",
  "summary": {
    "score": 41, "zone": "red",
    "check_status": "ok", "check_failed_reason": null, "pages_checked": 34, "pages_unvisited": 0,
    "counts": {"violation": 9, "risk": 4, "recommendation": 2, "manual": 7, "pass": 58, "na": 21},
    "fines_total": {"min": 520000, "max": 1360000},
    "fines_risk_extra": {"max": 300000},
    "top_issues": ["CKI-02", "FRM-03", "LOC-01"],
    "top_blocks": ["G-COOKIE-BEFORE", "FRM-03", "LOC-01"],
    "rkn_bot_visible": ["CKI-02", "FRM-03"]
  },
  "blocks": [
    {"id": "G-COOKIE-BEFORE", "group": "G-COOKIE-BEFORE", "kind": "violation",
     "title": "Счётчики и реклама работают до согласия посетителя",
     "check_ids": ["CKI-02", "CKI-09"], "priority": "P1", "urgency": "Срочно",
     "services": ["Google Analytics", "Яндекс Метрика"]}
  ],
  "by_law": [
    {"law": "152-FZ", "title": "152-ФЗ «О персональных данных»", "violation": 6, "risk": 2, "fines": {"min": 450000, "max": 1000000}},
    {"law": "ZOZPP", "title": "Закон о защите прав потребителей", "violation": 2, "risk": 1, "fines": {"min": 50000, "max": 300000}}
  ],
  "findings": [
    {
      "check_id": "CKI-02",
      "title": "Аналитика срабатывает до согласия на cookie",
      "category": "Cookie и трекеры",
      "status": "violation",
      "kind": "violation",
      "urgency": "Срочно",
      "confidence": "technical",
      "severity": 1,
      "priority": "P1",
      "requirement_plain": "Счётчики и трекеры нельзя запускать, пока посетитель не согласился.",
      "legal": [
        {"norm_id": "152-FZ", "article": "9", "part": "1", "point": null, "label": "152-ФЗ «О персональных данных», ст. 9, ч. 1", "text_excerpt": "…", "text_length": 650, "kb_ref": "152-FZ/9/1"},
        {"norm_id": "KOAP-RF", "article": "13.11", "part": "2", "fine": {"min": 300000, "max": 700000}}
      ],
      "evidence": {
        "scenario": "no_action",
        "page_url": "https://example.ru/",
        "requests": ["https://mc.yandex.ru/watch/…", "https://www.google-analytics.com/g/collect?…"],
        "screenshot_url": "/v1/scans/01927f40-…/artifacts/scr_7f2"
      },
      "notes": [],
      "explanation": "Счётчики отправляют данные посетителя до того, как он выразил согласие.",
      "practice": [{"case_id": "PR-001", "summary": "…", "outcome": "Штраф 50 000 ₽", "body": "Арбитражный суд города Москвы", "case_no": "А40-342666/25-93-2864", "date": "04.02.2026", "norm": "КоАП РФ, ст. 13.11, ч. 10", "amount_rub": 50000, "fine_replaced_by_warning": false, "source": "…"}],
      "how_to_fix": ["Подключать счётчики только после нажатия «Принять»…", "Для Tilda: …"],
      "developer_task": "Вынести инициализацию счётчиков в обработчик согласия…",
      "effort_hours": 2,
      "ai_used": false
    },
    {
      "check_id": "DOC-12",
      "title": "Перечислены cookie/аналитика/третьи лица-получатели",
      "category": "Документы ПДн",
      "status": "risk",
      "confidence": "probable",
      "severity": 2,
      "priority": "P1",
      "legal": [
        {"norm_id": "152-FZ", "article": "9", "part": "1", "text_excerpt": "…", "kb_ref": "152-FZ/9/1"}
      ],
      "evidence": {
        "page_url": "https://example.ru/privacy",
        "ai": {
          "verdict": "partial",
          "confidence": "medium",
          "comment": "В политике названа Яндекс Метрика, но не названы Google Analytics и чат-виджет.",
          "quote": "Мы используем сервис Яндекс Метрика для анализа посещаемости",
          "norm_ref": "152-FZ/9/1"
        }
      },
      "notes": ["ai_reviewed"],
      "explanation": "Политика перечисляет не все сервисы, которые работают на сайте.",
      "how_to_fix": ["Перечислить в политике все сервисы, которые работают на сайте."],
      "ai_used": true
    }
  ],
  "manual_checks": [{"check_id": "ORG-03", "title": "…", "question": "Ведётся ли перечень систем и мест, где хранятся персональные данные?", "hint": "Это внутренний список: почта, CRM, таблицы, сайт, архивы.", "urgency": "Важно", "audience": null}],
  "mismatches": [{"check_id": "DOC-15", "document": "Политика", "statement": "Мы не используем сервисы аналитики", "observed": "На сайте работает Яндекс Метрика", "conclusion": "Политика не соответствует фактической обработке"}],
  "services": [{"name": "Google Analytics", "domain": "google-analytics.com", "country": "US", "foreign_entity": true, "category": "analytics", "before_consent": true}],
  "cookies": {"no_action": [ … ], "accept": [ … ], "reject": [ … ]},
  "cookie_no_reject": false,
  "cms": ["WordPress"],
  "profile_hint": {"code": "clinic", "title": "Клиника", "current_title": "Общий", "reason": "На страницах сайта много слов о лечении, врачах и пациентах."},
  "plan": {"P1": ["CKI-02", "FRM-03"], "P2": [ … ], "P3": [ … ]},
  "pages": [{"url": "https://example.ru/privacy", "kind": "policy", "http_status": 200}]
}

Значения полей:

Поле Значения
status pass — требование выполнено; violation — нарушение; risk — спорная позиция или норма без устойчивой практики; manual — автоматически не проверить, нужен ответ человека; na — не применимо к сайту или профилю; error — проверку выполнить не удалось. Это вывод проверки; статус сверки нормы юристом на него не влияет
kind Вид записи для человека (добавлено 23.10.2026): violation, risk, recommendation, manual, pass, na, error. Совпадает со status, кроме «Рекомендации»: проверка без нормы и штрафа (заголовки безопасности, флаги cookie) показывается как recommendation, хотя её status — violation или risk. «Рекомендация» не имеет штрафа и не влияет на оценку. Отчёты, собранные раньше, поля не имеют: считайте kind = status
urgency «Срочно» (P1), «Важно» (P2), «Желательно» (P3 и все рекомендации); только у карточек вида violation, risk, recommendation. Срок «10 рабочих дней» — ст. 20 ч. 4 152-ФЗ (ответ оператора на запрос Роскомнадзора), «месяц» — рекомендация сервиса
counts violation, risk, recommendation — число записей отчёта (blocks): смежные проверки (например, «счётчики до согласия» и «российские счётчики до согласия») слиты в одну запись, поэтому числа могут быть меньше числа карточек findings. manual — число вопросов в manual_checks (только те, что действительно нужно проверить человеку), pass, na, error — число карточек. Поле recommendation добавлено 23.10.2026
blocks Записи отчёта для человека: kind, title, check_ids (проверки, слитые в запись), priority, urgency, services (названия сервисов, на которых основан вывод). Порядок: нарушения, риски, рекомендации; внутри — по срочности
legal[].text_excerpt Полный текст части нормы без обрезки (раньше — до 400 знаков). Нет текста в базе норм — null, и остаётся только ссылка (label). Если text_length больше 1500, в интерфейсе целиком его лучше не показывать, а вынести в приложение
practice[] Только дела с судом (body) и номером дела (case_no) и результатом; confidence не заполняется. Нет подходящих дел — список пуст
manual_checks[] Только вопросы, которые нельзя проверить с сайта и которые относятся к этому сайту; question, hint и urgency — короткий вопрос с ответом «да/нет», пояснение и срочность, если ответ «нет». Проверки, которые сервис не смог выполнить (не реализовано, сбой ИИ, недоступный реестр), в список не попадают; их карточки со статусом manual остаются в findings
cookie_no_reject true — на сайте нет кнопки «Отклонить»: сценарий reject не проводится и в cookies отсутствует
cms Система управления сайтом, если сканер её определил (WordPress, Tilda, 1С-Битрикс…); советы «Для Tilda/WordPress…» относятся только к ней
profile_hint Подсказка профиля (с версии 1.3, поле добавлено, прежние не менялись): по признакам сканера сайт похож на другой профиль, чем выбранный. code, title — подходящий профиль, current_title — выбранный, reason — что нашёл сканер. null — подсказки нет. Профиль и набор проверок подсказка не меняет; в кратком отчёте самой подсказки нет (только нейтральная строка без названия профиля). Тексты reason берутся из данных отчёта (strings.profile_hint)
confidence technical — установлено техническими средствами; probable — вероятно (в том числе по разбору текста ИИ с цитатой); manual — требует проверки человеком
severity 1 — критичная, 2 — высокая, 3 — средняя, 4 — низкая
priority P1 — в течение 10 рабочих дней; P2 — в течение месяца; P3 — по возможности
zone green (85–100), yellow (60–84), red (0–59)
mismatches Расхождения «документ ↔ сайт»; заполняются, только если текст документа разбирал ИИ, иначе список пуст. check_id — проверка, из ответа которой получилось расхождение (DOC-10, DOC-12, DOC-15); document — документ; statement — дословная цитата утверждения из документа (наличие её в тексте проверяет программа); observed — что установил сканер (строку пишет программа по найденным сервисам и странам серверов); conclusion — пояснение ИИ
notes Список кодов пометок карточки: почему проверка получила такой статус или чего сканер не смог. Может быть пустым. Коды и их смысл — в разделе 5.1
evidence.ai Разбор текста документа ИИ; есть только у карточек с ai_used: true. Поля и правила превращения вывода ИИ в статус — в разделе 5.1

Тексты, взятые с проверяемого сайта (цитаты, заголовки страниц, фрагменты документов), и тексты ИИ (evidence.ai.quote и evidence.ai.comment) передаются как есть. Если вы показываете их в своём интерфейсе, экранируйте HTML.

Скриншоты и другие файлы: GET /scans/{id}/artifacts/{artifact_id} — 302 на временную ссылку. Файлы хранятся 90 дней, JSON-отчёт — 365 дней.

5.1 Пометки notes и разбор ИИ evidence.ai

Пометки. В карточках findings, manual_checks и recommended поле notes — список кодов: почему проверка получила такой статус или чего сканер не смог. Показывайте пользователю смысл кода, а не сам код. Набор кодов пополняется: неизвестный код пропускайте, а не считайте ошибкой.

Код Что означает
ai_reviewed Текст документа разобрал ИИ; цитату и ссылку на норму проверила программа, статус выставила программа
ai_not_found ИИ не нашёл требуемого в тексте документа. Отсутствие цитатой не подтвердить, поэтому статус risk, а не violation
ai_capped ИИ нашёл в тексте нарушение, но точность разбора ещё не проверена на наборе документов, размеченном юристом, поэтому показан risk. Проверьте цитату вручную
ai_no_quote_needed Для проверок вида «лишних полей нет, данных детей нет» цитата невозможна: статус pass без цитаты
ai_unclear, ai_low_confidence ИИ не смог однозначно оценить текст или остались сомнения (confidence: low); статус manual
ai_no_quote, ai_bad_ref Цитата ИИ не найдена в тексте документа или ИИ сослался на норму, которой не было в запросе: вывод отброшен, статус manual
ai_failed, ai_bad_schema Разбор не удался или ответ ИИ не удалось разобрать; статус manual
ai_budget, ai_text_too_long, ai_task_missing Суточный лимит разбора исчерпан, документ слишком длинный для разбора или разбор для этой проверки не реализован; статус manual
ai_unavailable Проверке нужен разбор текста, а ИИ выключен или недоступен; статус manual
ai_forms_truncated, ai_text_truncated Форм больше, чем помещается в разбор, или длинный текст прочитан не целиком (только начало)
partial_scan Сканирование было частичным: отсутствие чего-либо на сайте не доказано
unverified_norm Норма ещё не сверена юристом с первоисточником: вместо violation показан risk
cdn Сайт за сетью доставки контента: страна сервера может отличаться от страны IP
no_document, document_not_loaded, short_text, script_link, no_consent_document Документ на сайте не найден, не открылся, очень короткий, открывается скриптом или отдельный документ согласия не найден
no_pd_forms, no_checkout, no_publication Для проверки нет предмета: на сайте нет форм сбора персональных данных, оформления заказа или публикации сведений о людях; статус na
photos_not_checked, staff_exception Фотографии программа не проверяет; сведения о работниках клиник и образовательных организаций публикуются в обязательном порядке (приказ № 118н, ПП № 1802) — проверьте вручную, нет ли лишнего
requisites_mismatch, no_inn, not_found_in_egrul Реквизиты в документе и на сайте не совпадают, ИНН на сайте не найден или не найден в ЕГРЮЛ/ЕГРИП
registry_unavailable, registry_disabled, individual_not_searchable Реестр операторов Роскомнадзора не ответил, поиск в нём отключён или оператор — физическое лицо (по ИНН не ищется)
contract_form, consent_in_widget, scanner_does_not_fill_forms Форма договорная (заказ, запись, оплата); согласие во всплывающем окне, которое сканер не видит; пробной отправки формы не было
service_country_unknown, country_unknown, location_unknown, not_found Страна сервиса или расположение серверов не определены; на сайте не найдено

Разбор ИИ. У карточек, где текст документа разбирал ИИ (ai_used: true), в evidence есть блок ai:

Поле Значение
verdict present — требование есть в тексте; partial — есть частично; absent — не найдено; contradicts — текст противоречит требованию; unclear — ИИ не смог оценить
confidence Уверенность ИИ: high, medium, low
comment Пояснение ИИ, до 300 знаков: описывает, что найдено на странице или в документе, и не содержит юридической оценки («нарушает требования» и подобных)
quote Дословная цитата из документа, до 500 знаков. Есть только если программа нашла её в тексте документа
norm_ref Норма в виде 152-FZ/9/1. Есть только если эта норма была в запросе к ИИ

Статус карточки выставляет программа, а не ИИ, поэтому confidence карточки — probable. Правила:

  • present с цитатой даёт pass; partial — risk; absent — risk с пометкой ai_not_found;
  • contradicts даёт violation только при confidence: high и если для этой проверки разрешён такой статус; сейчас потолок статуса от ИИ — risk, поэтому приходит risk (с пометкой ai_capped, если понижен);
  • unclear, низкая уверенность, нет цитаты или ссылка на чужую норму дают manual. Блок ai в таких карточках показывает отброшенный вывод ИИ — не считайте его фактом.

6. Пример полного цикла

Python

import time, uuid, httpx

BASE = "https://pdn-152.ru/v1"
KEY = "ck_live_…"
client = httpx.Client(base_url=BASE, headers={"Authorization": f"Bearer {KEY}"}, timeout=30)

r = client.post("/scans",
                json={"url": "https://example.ru", "profile": "shop", "org_type": "legal"},
                headers={"Idempotency-Key": str(uuid.uuid4())})
r.raise_for_status()
scan_id = r.json()["id"]

while True:
    s = client.get(f"/scans/{scan_id}").json()
    if s["status"] in ("done", "partial", "error"):
        break
    time.sleep(7)

if s["status"] == "error":
    print("Проверка не выполнена:", s["error"]["code"], "— кредит возвращён")
else:
    report = client.get(f"/scans/{scan_id}/report", params={"status": "violation"}).json()
    print("Оценка:", report["summary"]["score"])
    for f in report["findings"]:
        print(f["priority"], f["check_id"], f["title"])

JavaScript (Node.js 18+)

const BASE = "https://pdn-152.ru/v1";
const headers = { Authorization: `Bearer ${process.env.KEY}`, "Content-Type": "application/json" };

const created = await fetch(`${BASE}/scans`, {
  method: "POST",
  headers: { ...headers, "Idempotency-Key": crypto.randomUUID() },
  body: JSON.stringify({ url: "https://example.ru", profile: "general" }),
}).then(r => r.json());

let scan;
do {
  await new Promise(r => setTimeout(r, 7000));
  scan = await fetch(`${BASE}/scans/${created.id}`, { headers }).then(r => r.json());
} while (!["done", "partial", "error"].includes(scan.status));

console.log(scan.summary);

PHP

$base = "https://pdn-152.ru/v1";
$ch = curl_init("$base/scans");
curl_setopt_array($ch, [
  CURLOPT_POST => true,
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer " . getenv("KEY"), "Content-Type: application/json",
                         "Idempotency-Key: " . bin2hex(random_bytes(16))],
  CURLOPT_POSTFIELDS => json_encode(["url" => "https://example.ru"]),
]);
$scan = json_decode(curl_exec($ch), true);

7. Вебхуки

Вместо опроса можно получать уведомления. Адреса настраиваются в кабинете («API и MCP» → «Вебхуки») или через API (право webhooks:manage):

Запрос Действие
GET /webhooks список адресов
POST /webhooks создать: {"url": "https://crm.example/hooks/152", "events": ["scan.completed", "scan.failed"]}; ответ 201, в нём один раз возвращается secret; события можно не указывать — тогда все четыре
DELETE /webhooks/{id} удалить
POST /webhooks/{id}/test отправить тестовое событие webhook.test (подписанное, как настоящее) и вернуть результат: delivered или failed, код ответа вашего сервера. Повторов нет

События:

Событие Когда
scan.completed проверка завершена со статусом done или partial
scan.failed проверка завершена со статусом error, кредит возвращён
credits.low на балансе осталось меньше 10% последнего купленного пакета или меньше 5 кредитов; приходит один раз, в момент, когда баланс опустился ниже порога
credits.expiring пакет кредитов истекает не позже чем через 14 дней; один раз на пакет

Тело запроса:

{
  "id": "evt_01927f44…",
  "type": "scan.completed",
  "created_at": "2026-09-23T14:07:13Z",
  "data": {"scan_id": "01927f40-…", "status": "done", "external_id": "client-1842", "summary": {"score": 41, "zone": "red", "check_status": "ok", "violations": 9}}
}

Отчёт в вебхук не вкладывается — получите его запросом GET /scans/{id}/report.

Подпись. Заголовок X-Signature: t=1790172433,v1=<hex>, где v1 = HMAC-SHA256(secret, "<t>.<тело запроса как есть>"). Проверяйте подпись и отклоняйте запросы с t старше 5 минут:

import hmac, hashlib, time

def verify(secret: str, header: str, body: bytes) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    if abs(time.time() - int(parts["t"])) > 300:
        return False
    expected = hmac.new(secret.encode(), f'{parts["t"]}.'.encode() + body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts["v1"])

Доставка: ответ 2xx в течение 10 секунд считается успешным. Иначе повторы через 1 минуту, 5 минут, 30 минут, 2 часа, 6 часов, 24 часа. После 20 неудачных попыток доставки подряд (по всем событиям адреса) адрес отключается, владелец получает письмо. События по демонстрационным проверкам тестового ключа приходят с полем data.test: true. Возможна повторная доставка одного события — используйте id для защиты от дублей. Адреса вебхуков должны быть публичными (https, не внутренние сети).

8. Безопасность и рекомендации

  • Храните ключ в переменных окружения или хранилище секретов, не в коде и не в браузере. API не предназначен для вызова из браузера: запросы с сайтов (CORS) не разрешены.
  • Выпускайте отдельный ключ на каждую систему и ограничивайте права: для чтения отчётов достаточно scans:read.
  • Ограничьте ключ списком IP-адресов ваших серверов.
  • При подозрении на утечку отзовите ключ в кабинете — он перестанет работать сразу. При первом использовании ключа с нового IP владелец получает письмо.
  • Проверяйте только сайты, которые вы вправе проверять (свои или клиентов с их согласия). Сервис открывает только публичные страницы и никогда не отправляет формы.

9. История изменений

Дата Версия Изменения
— v1 первая публичная версия
09.10.2026 документ 1.2 описаны поле notes с кодами пометок и блок evidence.ai (раздел 5.1); в пример полного отчёта добавлена карточка с разбором ИИ; окно reuse_recent — 4 часа вместо 6

10. Поддержка

Вопросы по API — через форму в кабинете. Указывайте request_id из ответа об ошибке.