Подготавливаем страницу…
Подготавливаем страницу…
Все эндпоинты приёма согласий, формы запросов и ответов, защита ключей и коды ошибок. Примеры — в том виде, в каком их принимает и отдаёт платформа.
https://v2.1opd.ru/api/v2. Он один для всех клиентов: к какой компании относится запрос, платформа понимает по ключу.POST с JSON-телом. Ключ — в заголовке API-KEY.create-agreement — объект с hash, успех get-fields — сразу массив. Ошибка — объект с полем reason на русском, иногда с машинным признаком error.Ключи выпускаются в кабинете: раздел «Интеграция». Там же лежит готовый код с уже подставленными ключом и идентификаторами форм.
Частая ошибка подключения — не тот ключ или не та защита: например, IP-список для кода в браузере, где адрес у каждого посетителя свой. Сверьтесь с таблицей.
| Ключ | Откуда отправляете | Защита | Инструкция |
|---|---|---|---|
| pk_ | Код в браузере: Тильда, лендинг, статичный сайт | Список доменов (Origin) | Открыть |
| pk_ | SPA: React, Vue, client-side Next.js | Список доменов, при поддоменах — *.domain.ru | Открыть |
| sk_ | Сервер с постоянным адресом: VPS, хостинг с PHP | Список IP | Открыть |
| sk_ | Контейнеры и serverless: адрес меняется | HMAC-подпись | Открыть |
| sk_ | Битрикс, WordPress-хук | Список IP, по желанию плюс HMAC | Открыть |
| sk_ | Максимум: постоянный адрес и защита от утечки ключа | Список IP и HMAC вместе | Открыть |
POST /api/v2/create-agreement
Главный эндпоинт: создаёт запись согласия. Работает и с pk_, и с sk_. Записывается всё, что потом понадобится для доказательства: поля, время, источник, действовавшие редакции текста согласия и политики.
POST /api/v2/create-agreement HTTP/1.1
Host: v2.1opd.ru
API-KEY: sk_ваш_ключ
Content-Type: application/json
{
"form_id": 12,
"hash_field": "user@example.com",
"consent_given": true,
"fields": [
{ "field": "email", "value": "user@example.com" },
{ "field": "first_name", "value": "Иван" },
{ "field": "phone_number", "value": "+79991234567" }
]
}forms_scope поле обязательно. Для ключа на одну форму его можно не передавать; если передать идентификатор другой существующей формы, запрос будет отклонён с 403 — согласие в чужую форму не пишется.{ field, value }. Имена — из состава формы (см. поля субъекта и get-fields). Поле, которого в форме нет, даёт 400 Unknown field. Регистр не важен: Email и email — одно поле.true — записываем с подтверждённой отметкой. false — отказ 400: согласие без отметки не записывается. Без поля запись сохранится, но отметка останется «неизвестно» — так работают старые интеграции.form123456789 у Тильды). По нему кабинет предлагает привязку формы к блоку сайта.Двойные отправки платформа гасит сама. Повтор с теми же проектом, субъектом и содержимым в пределах 5 минут не создаёт вторую запись — приходит тот же hash, что у оригинала. За пределами окна повтор заводит новую версию согласия.
Форма должна быть готова принимать. Неутверждённый текст согласия (черновик) и форма без текста отвечают 409; промо-форма после даты окончания акции — 410 promo_finished. Это не сбои: приём отказывает, потому что записать согласие юридически не на что.
HTTP/1.1 200 OK
{
"hash": "0622845210A8C5136424513435FA90EB",
"hash_field": "user@example.com",
"fields": [
{ "field": "email", "value": "user@example.com" },
{ "field": "first_name", "value": "Иван" },
{ "field": "phone_number", "value": "+79991234567" }
]
}hash — идентификатор согласия: 32 шестнадцатеричных символа в верхнем регистре. Сохраните его: по нему запись читают (get-user) и отзывают (delete-user).
HTTP/1.1 202 Accepted
{
"status": "queued",
"idempotency_key": "d4e5f6a7b8c9d0e1...",
"check_url": "/spool/status/d4e5f6a7b8c9d0e1...",
"original_path": "/api/v2/create-agreement"
}После 202 запрос не повторяют. Так отвечает очередь, когда база временно недоступна: согласие принято и будет дописано само — повторы идут ступенями от 10 минут до 48 часов, тело хранится 72 часа. Повторная отправка того же тела за окном дедупликации создаст вторую версию согласия, а оригинал всё равно доедет.
hash в 202 не приходит, и посчитать его у себя нельзя — в него входит номер версии согласия, то есть состояние на стороне платформы. Когда запись доедет, идентификатор появится по адресу из check_url: GET /spool/status/<idempotency_key>, поле delivered_hash.
503 — единственный случай, когда отправку повторяют: недоступны и приём, и очередь, доставка не гарантируется.
POST /api/v2/get-fields
Отдаёт поля формы, к которой привязан ключ, — именно эти имена принимает fields в запросе согласия. Работает только с ключом, выпущенным на одну форму: у ключа на несколько форм составы разные, и запрос ответит 403. Тело можно отправить пустым.
POST /api/v2/get-fields HTTP/1.1
Host: v2.1opd.ru
API-KEY: sk_ключ_этой_формы
Content-Type: application/json
{}HTTP/1.1 200 OK
[
{ "field_name": "email", "field_title": "Электронная почта" },
{ "field_name": "first_name", "field_title": "Имя" },
{ "field_name": "phone_number", "field_title": "Номер телефона" }
]POST /api/v2/get-user
Возвращает данные субъекта по hash согласия. Нужны право «чтение» на ключе и ключ одной формы. Пригодится для ответа на запрос человека о его данных.
POST /api/v2/get-user HTTP/1.1
Host: v2.1opd.ru
API-KEY: sk_ключ_этой_формы
Content-Type: application/json
{ "hash": "0622845210A8C5136424513435FA90EB" }
[
{ "field": "email", "value": "user@example.com" },
{ "field": "first_name", "value": "Иван" }
]
# Согласие отозвано или истекло — 200 с пояснением вместо данных:
# { "status": "По данному hash был пользователь, но он был удалён. Данные недоступны." }
# hash не существует (или прошёл срок хранения) — 404:
# { "reason": "Entity not found" }POST /api/v2/delete-user
Отзывает все согласия субъекта в этой форме и стирает его персональные данные; сам факт «согласие было и отозвано тогда-то» остаётся в журнале — это часть доказательства. Нужно право «удаление»; публикуемым ключом pk_ удалять нельзя.
POST /api/v2/delete-user HTTP/1.1
Host: v2.1opd.ru
API-KEY: sk_ключ_этой_формы
Content-Type: application/json
{ "hash": "0622845210A8C5136424513435FA90EB" }
# Ответ: { "status": 200 }
# Субъект уже удалён ранее — 200 с тем же пояснением, что у get-user.
# hash не существует — 404: { "reason": "Entity not found" }Включается на ключе по желанию (для sk_): каждый запрос подписывается секретом, и перехваченный ключ без секрета бесполезен. После включения запросы без подписи не принимаются.
import base64, hashlib, hmac, json, time
ts = str(int(time.time())) # unix-время, секунды
body = json.dumps(payload).encode() # ровно те байты, что уйдут в запрос
secret = base64.b64decode(secret_b64) # секрет из кабинета хранится в base64
sign = hmac.new(secret, ts.encode() + b"\n" + body, hashlib.sha256).hexdigest()
headers = {
"API-KEY": "sk_...",
"API-TIMESTAMP": ts,
"API-SIGN": sign, # hex, 64 символа в нижнем регистре. Не base64.
}время + "\n" + байты тела, ровно те, что уходят в запрос, до любых переформатирований JSON.API-TIMESTAMP (unix-время в секундах) и API-SIGN. Допустимое расхождение часов — 300 секунд.https:// и порта: example.com. Шаблон *.example.com покрывает поддомены, но не сам example.com — его вносят отдельной строкой. Запрос без Origin (curl без заголовка) отклоняется.10.0.0.0/24). Вносите исходящий адрес своего сервера — у хостинга он может отличаться от адреса сайта (curl ifconfig.me с сервера покажет его). Для серверов с меняющимся адресом список не подходит — берите HMAC.Оба списка редактируются в кабинете на карточке ключа и применяются сразу.
У ключа может быть область forms_scope — перечень форм, в которые он пишет. Это основной вариант для сайта с несколькими формами: один ключ, а форму выбирает form_id в теле запроса.
# Ключ с forms_scope: [12, 13, 21]
form_id: 12 → принято
form_id: 21 → принято
form_id: 40 → 403 «Форма не разрешена ключом»Ключ без области привязан к одной форме: form_id можно не передавать. Если на таком ключе указать другую существующую форму, приём ответит 403 — молча писать согласие не в ту форму он не станет. Несуществующий form_id игнорируется: так продолжают работать старые шаблоны, где это поле забито константой.
Тело ошибки — { "reason": "..." }, текст на русском. В таблице — ошибки самого запроса; 202 и 503 ошибками не являются и разобраны выше.
| Код | Причина | Когда возникает |
|---|---|---|
| 400 | Unknown field: … | В fields есть поле, которого нет в форме. Состав полей — в карточке формы и в get-fields. |
| 400 | hash_field обязателен | Поле hash_field отсутствует или пустое. |
| 400 | consent_given=false… | Отправитель сам сообщил, что отметки согласия не было. Такая запись не создаётся. |
| 400 | Некорректный JSON | Тело не разбирается как JSON. |
| 401 | Неверный API-ключ | Ключа нет, он отозван или истёк вместе со сроком договора. |
| 401 | Подпись не совпадает | HMAC не сошёлся. Частая причина — подпись в base64 вместо hex, или тело изменилось после подписания. |
| 401 | Timestamp устарел | Разница между API-TIMESTAMP и временем сервера больше 300 секунд. |
| 403 | Origin не разрешён | Заголовок Origin (или хост из Referer) не входит в список ключа. Запрос вовсе без Origin для pk_-ключа тоже отклоняется. |
| 403 | IP не разрешён | Адрес источника не входит в IP-список ключа. |
| 403 | Форма не разрешена ключом | form_id не входит в forms_scope, либо на ключе одной формы указана другая существующая форма. |
| 404 | Entity not found | get-user / delete-user: записи с таким hash нет. |
| 409 | Текст согласия — черновик | Редакция текста согласия не утверждена в кабинете. Черновик согласий не принимает. |
| 409 | К форме не привязан текст | У формы нет текста согласия — непонятно, на что соглашается человек. |
| 410 | promo_finished | Акция завершена по дате — новые данные участников не принимаются. В ответе есть end_date. |
| 429 | — | Превышен лимит частоты запросов с одного адреса. Подождите и повторите. |
В fields передаются любые из 28 полей ниже — имя поля указывается в field. Обязательное одно: email, он же обычно идентификатор. Пустые поля не передавайте. Все значения хранятся зашифрованными; значение — строка до 5000 символов. Какие поля включены у конкретной формы — в её карточке и в get-fields.
| field | Что это | Примечание |
|---|---|---|
| Электронная почта | обязательное; обычно и есть hash_field | |
| first_name | Имя | по желанию |
| last_name | Фамилия | по желанию |
| sur_name | Отчество | по желанию |
| birth_date | Дата рождения | по желанию |
| phone_number | Номер телефона | по желанию |
| birth_place | Место рождения | по желанию |
| registration_address | Адрес регистрации | по желанию |
| delivery_address | Адрес доставки | по желанию |
| passport_details | Паспортные данные | по желанию |
| inn | ИНН | по желанию |
| snils | СНИЛС | по желанию |
| bank_details | Банковские реквизиты | по желанию |
| account_vk | Аккаунт VK | по желанию |
| account_telegram | Аккаунт Telegram | по желанию |
| account_instagram | Аккаунт Instagram | по желанию |
| account_facebook | Аккаунт Facebook | по желанию |
| account_viber | Аккаунт Viber | по желанию |
| account_ok | Аккаунт OK | по желанию |
| account_whatsapp | Аккаунт WhatsApp | по желанию |
| job_title | Должность | по желанию |
| organization_name | Название организации | по желанию |
| field_of_activity | Сфера деятельности | по желанию |
| city | Город | по желанию |
| mattermost_account | Аккаунт Mattermost | по желанию |
| slack_account | Аккаунт Slack | по желанию |
| ms_teams_account | Аккаунт MS Teams | по желанию |
| sex | Пол | значения male / female |
Самый короткий способ убедиться, что ключ работает, — отправить create-agreement из терминала или Postman и найти запись в кабинете, в реестре согласий. Две оговорки:
pk_-ключа добавьте заголовок Origin с разрешённым доменом вручную — инструменты вне браузера сами его не ставят;