#API лидов
Статус: Доступно (чтение) / Планируется (запись). Работают
GET /leadsиGET /leads/{id}(областьleads:read). Создание, изменение, приём, оценка, заметки и задачи — Планируется: вызовы вернут404. Поля лида ниже — фактические, как их отдаёт чтение (схемаLeadв OpenAPI).
Лид — это обращение человека, которое может стать клиентом: он связан с контактом, диалогом, источником, оценкой и сделкой. Через API внешняя CRM создаёт лиды, читает их, меняет статус и ответственного, принимает лид в работу, оставляет заметки и ставит задачи.
Доступ: leads:read — чтение; leads:write — запись; leads:score — оценка (платно, см. Оценка лидов). ПДн: да — контакт содержит имя, почту, телефон; свободный текст лида (title, intent, ai_summary, score.reason, next_action, lost_reason) тоже может нести ПДн, поэтому лид и без контакта считается раскрытием. Внутренние заметки персонала (notes) наружу не отдаются. Каждая выдача записывается в журнал выдач без значений (см. Передача ПДн). В ячейке RU для области leads:* список IP интеграции обязателен, а при подключении указываются страна размещения принимающей системы (только РФ; иначе 403 pii_transfer_not_allowed) и основание передачи (Передача ПДн). Платно: нет, кроме оценки лида нашей моделью. Режим: какие поля вы вправе менять, зависит от режима CRM арендатора — CRM: режимы.
#Базовый адрес
| Ячейка | Адрес |
|---|---|
| RU | https://formula-cream.pro/api/public/v1 |
| EN | https://wfacademy.org/api/public/v1 |
| ID | https://wfacademy.id/api/public/v1 |
Ключ действует только на своей ячейке: ключ RU на адресе EN вернёт 401 wrong_cell с полем correct_base_url. Заголовки, формат ошибок, пагинация, пределы — в Соглашениях. Аутентификация — Ключи и области. Во всех примерах ключ заведомо ненастоящий (EXAMPLE).
#Точки
| Метод и путь | Область | Что делает |
|---|---|---|
POST /leads | leads:write | Создать лид (идемпотентно по external_id) |
GET /leads | leads:read | Список лидов с фильтрами |
GET /leads/{id} | leads:read | Лид по идентификатору платформы |
GET /leads/by-external/{external_id} | leads:read | Лид по идентификатору во внешней CRM |
PATCH /leads/{id} | leads:write | Изменить поля; требует If-Match и Idempotency-Key |
POST /leads/{id}/accept | leads:write | Принять лид во внешнюю CRM |
POST /leads/{id}/score | leads:score | Оценить лид нашей моделью (платно) |
PUT /leads/{id}/score | leads:write | Записать оценку внешней CRM (без списания) |
POST /leads/{id}/actions/{action_id}/ack | leads:write | Подтвердить или отклонить запрос действия агента |
POST /leads/{id}/notes | leads:write | Добавить заметку |
POST /leads/{id}/tasks | leads:write | Поставить задачу |
Статус по точкам: Доступно — GET /leads и GET /leads/{id}; все остальные точки таблицы — Планируется.
Оценка, действия агентов и карта статусов (PUT /crm/status-map, leads:write) описаны отдельно: оценка, действия агентов, карта статусов.
#Объекты
#Лид
| Поле | Тип | Описание |
|---|---|---|
id | string (uuid) | Идентификатор лида на платформе |
external_id | string | null | Идентификатор лида во внешней CRM; null, пока внешняя CRM не подключена |
external_url | string | null | Ссылка на карточку во внешней CRM |
version | integer | null | Версия для If-Match; пока null (в базе нет колонки версии) — используйте etag |
etag | string | Метка версии; совпадает с заголовком ETag карточки (слабая, вида W/"…") |
status | string | Один из шести канонических статусов: new, qualified, proposal, negotiation, won, lost (см. карту статусов). Архив — не статус, а флаг archived |
archived | boolean | Лид в архиве (отметка на лиде, не статус) |
archived_reason | string | null | Причина архива, например ai_not_client |
owner | "internal" | "external" | Кто владеет статусом, ответственным и исходом (см. режимы) |
title | string | null | Название лида |
source | string | null | Источник |
assigned_to | string (uuid) | null | Ответственный — только сотрудник арендатора |
value | object | null | Ожидаемая сумма: {"amount": "15000.00", "currency": "RUB"} |
product_id | string (uuid) | null | Продукт, к которому относится лид |
course_slug | string | null | Курс |
contact | object | null | Контакт (см. ниже) |
conversation_id | string (uuid) | null | Диалог, из которого возник лид |
attribution | object | null | Источник и путь посетителя (см. ниже) |
score | object | null | Оценка: score (0–100), grade (A ≥ 75, B 50–74, C 25–49, D < 25), reason, flags, source (ai, external, manual), model, scored_at; null у лида без оценки (балл 0 и нет следов ИИ). Пока reason, source и scored_at могут быть null (Оценка лидов) |
flags | object | null | Флаги buy_intent, support, supplier (также в score.flags) |
intent | string | null | Намерение, выделенное агентом |
ai_summary | string | null | Сводка диалога |
next_action, next_action_at | string | null | Следующее действие и срок |
lost_reason | string | null | Причина потери |
qualified_at, won_at, lost_at | string (ISO 8601) | null | Отметки исходов |
last_activity_at | string (ISO 8601) | null | Последняя активность |
created_at, updated_at | string (ISO 8601) | Создан и изменён, UTC |
На платформе у диалога не бывает двух активных лидов: один диалог — один активный лид.
#Контакт (ПДн)
| Поле | Тип | Описание |
|---|---|---|
id | string (uuid) | Идентификатор контакта |
first_name, last_name | string | null | Имя и фамилия |
email | string | null | Почта |
phone | string | null | Телефон |
tags | array of string | Теги |
geo_country, geo_city | string | null | География по IP посетителя |
#Атрибуция
| Поле | Тип | Описание |
|---|---|---|
utm_source, utm_medium, utm_campaign | string | null | Метки кампании |
first_page | string | null | Первая страница посещения |
referrer | string | null | Откуда пришёл посетитель |
Атрибуция записывается один раз; изменить её через API нельзя.
#POST /leads
Создаёт лид и, если нужно, контакт. Повторный запрос с тем же external_id не создаёт второй лид, а возвращает существующий.
Заголовки: Authorization: Bearer <ключ>, Idempotency-Key (рекомендуется; повтор с тем же external_id и без заголовка не создаёт дубль), Content-Type: application/json.
| Параметр | Тип | Обязательно | Ограничения | Пример |
|---|---|---|---|---|
external_id | string | да | Уникален в пределах арендатора и интеграции | "crm-48213" |
external_url | string | нет | Абсолютный https-адрес | "https://crm.example.com/leads/48213" |
contact | object | да | Нужна хотя бы почта или телефон | {"first_name":"Анна","email":"anna@example.com"} |
title | string | нет | Произвольная строка | "Заявка на курс" |
source | string | нет | Произвольная строка | "landing" |
value | object | нет | amount строкой, currency — код ISO 4217 | {"amount":"15000.00","currency":"RUB"} |
product_id | string (uuid) | нет | Продукт арендатора | "8a6f3d10-2b94-4c7e-a1d5-0e9b7c4f2a11" |
attribution | object | нет | Поля атрибуции | {"utm_source":"yandex"} |
status | string | нет | Один из шести канонических статусов; по умолчанию new | "new" |
{
"external_id": "crm-48213",
"external_url": "https://crm.example.com/leads/48213",
"contact": { "first_name": "Анна", "email": "anna@example.com", "phone": "+79990001122" },
"title": "Заявка на курс",
"source": "landing",
"value": { "amount": "15000.00", "currency": "RUB" },
"attribution": { "utm_source": "yandex", "utm_campaign": "spring" }
}
Ответ: 201 Created для нового лида, 200 OK при повторе с тем же external_id. Тело — объект лида.
{
"id": "5d1b2c7e-3f0a-4a8e-9a3b-1c2d3e4f5a6b",
"external_id": "crm-48213",
"external_url": "https://crm.example.com/leads/48213",
"version": 1,
"status": "new",
"archived": false,
"owner": "internal",
"title": "Заявка на курс",
"source": "landing",
"assigned_to": null,
"value": { "amount": "15000.00", "currency": "RUB" },
"contact": { "id": "0c4a9d52-7e71-4c63-8f0b-2a9c6e1d3b44", "first_name": "Анна", "last_name": null,
"email": "anna@example.com", "phone": "+79990001122", "tags": [] },
"score": null,
"flags": null,
"created_at": "2026-10-11T09:30:00Z",
"updated_at": "2026-10-11T09:30:00Z"
}
Ошибки: коды из каталога.
| Код | HTTP | Когда |
|---|---|---|
validation_failed | 422 | Поля не прошли проверку; детали в errors[] |
scope_missing | 403 | Нет leads:write |
ip_not_allowed | 403 | Запрос с адреса вне списка IP ключа (в ячейке RU список обязателен) |
idempotency_conflict | 409 | Тот же Idempotency-Key с другим телом |
rate_limited | 429 | Превышен предел записи |
Пример 422 (формат):
{
"type": "https://formula-cream.pro/developers/errors#validation_failed",
"title": "Запрос не прошёл проверку",
"status": 422,
"code": "validation_failed",
"detail": "Один или несколько параметров некорректны.",
"request_id": "req_EXAMPLE0101",
"errors": [
{ "field": "/contact", "code": "required", "message": "Нужна хотя бы почта или телефон." }
]
}
#Примеры
curl -X POST https://formula-cream.pro/api/public/v1/leads \
-H "Authorization: Bearer $API_KEY" \
-H "Idempotency-Key: 7c9e6679-7425-40de-944b-e07fc1f90ae7" \
-H "Content-Type: application/json" \
-d '{"external_id":"crm-48213","contact":{"first_name":"Анна","email":"anna@example.com"}}'
# API_KEY=sap_ru_test_EXAMPLE0KEYID_… (пример, не настоящий ключ)
const res = await fetch("https://formula-cream.pro/api/public/v1/leads", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.API_KEY}`,
"Idempotency-Key": crypto.randomUUID(),
"Content-Type": "application/json",
},
body: JSON.stringify({
external_id: "crm-48213",
contact: { first_name: "Анна", email: "anna@example.com" },
}),
});
const lead = await res.json();
import os, uuid, httpx
r = httpx.post(
"https://formula-cream.pro/api/public/v1/leads",
headers={
"Authorization": f"Bearer {os.environ['API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={"external_id": "crm-48213",
"contact": {"first_name": "Анна", "email": "anna@example.com"}},
)
lead = r.json()
$ch = curl_init("https://formula-cream.pro/api/public/v1/leads");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("API_KEY"),
"Idempotency-Key: " . bin2hex(random_bytes(16)),
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"external_id" => "crm-48213",
"contact" => ["first_name" => "Анна", "email" => "anna@example.com"],
], JSON_UNESCAPED_UNICODE),
]);
$lead = json_decode(curl_exec($ch), true);
#GET /leads
Статус: Доступно. Область
leads:read.
Возвращает страницу лидов арендатора. Пагинация курсорная (Соглашения): порядок возрастающий по (updated_at, id), курсор живёт 24 часа, подписан и привязан к ячейке, арендатору, интеграции, маршруту и набору фильтров. Ответ — {data, next_cursor}; пустая страница — честный 200 с data: []. Содержит персональные данные (контакт и свободный текст лидов): каждая выдача пишется в журнал выдач, при сбое записи журнала — 503.
| Параметр | Тип | Обязательно | Ограничения | Пример |
|---|---|---|---|---|
limit | integer | нет | 1–100; по умолчанию 25 | 50 |
cursor | string | нет | Значение next_cursor из прошлого ответа; действителен 24 часа | "eyJ1Ijoi…" |
include_deleted | boolean | нет | пока не поддерживается: true даёт 422 not_supported (удалённые лиды не хранятся) | false |
status | string | нет | Один из шести канонических статусов | qualified |
updated_since | string | нет | лиды, изменённые не раньше момента (включительно); ISO 8601, дата или дата-время с поясом | 2026-10-01T00:00:00Z |
updated_after | string | нет | то же, но строго позже момента (обход «с последнего увиденного»); вместе с updated_since нельзя | 2026-10-01T00:00:00Z |
source | string | нет | точное значение источника лида | website |
archived | boolean | нет | true — только архивные; по умолчанию (false) — только неархивные | false |
Перечень фильтров может быть расширен (например, по ответственному); действующий — в OpenAPI.
{
"data": [ { "id": "5d1b2c7e-3f0a-4a8e-9a3b-1c2d3e4f5a6b", "status": "new", "version": 1 } ],
"next_cursor": "eyJ1IjoiMjAyNi0xMC0xMVQwOTozMDowMFoiLCJpIjoiNWQxYiJ9"
}
curl "https://formula-cream.pro/api/public/v1/leads?status=qualified&limit=50" \
-H "Authorization: Bearer $API_KEY"
const res = await fetch(
"https://formula-cream.pro/api/public/v1/leads?status=qualified&limit=50",
{ headers: { Authorization: `Bearer ${process.env.API_KEY}` } },
);
const { data, next_cursor } = await res.json();
r = httpx.get(
"https://formula-cream.pro/api/public/v1/leads",
params={"status": "qualified", "limit": 50},
headers={"Authorization": f"Bearer {os.environ['API_KEY']}"},
)
page = r.json()
$ch = curl_init("https://formula-cream.pro/api/public/v1/leads?status=qualified&limit=50");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer " . getenv("API_KEY")],
]);
$page = json_decode(curl_exec($ch), true);
Чтобы получать изменения, а не весь список, запрашивайте updated_after с последним увиденным updated_at (или идите по next_cursor) и подписывайтесь на lead.updated. «Надгробий» удалённых лидов пока нет. Недействительный или просроченный курсор — 422 validation_failed с errors[].field = /cursor: начните обход заново.
#GET /leads/{id} и GET /leads/by-external/{external_id}
Статус:
GET /leads/{id}— Доступно (областьleads:read);GET /leads/by-external/{external_id}— Планируется (внешней CRM пока нет).
Возвращают объект лида; заголовок ETag равен полю etag. Содержит ПДн (контакт и свободный текст лида) и пишется в журнал выдач до ответа; сбой записи — 503. Чужой, несуществующий или не-uuid идентификатор — 404 not_found (платформа не подтверждает существование ресурса другого арендатора).
| Параметр | Тип | Обязательно | Ограничения | Пример |
|---|---|---|---|---|
id (путь) | string (uuid) | да | Идентификатор лида на платформе | 5d1b2c7e-3f0a-4a8e-9a3b-1c2d3e4f5a6b |
external_id (путь) | string | да | Идентификатор во внешней CRM; только для by-external | crm-48213 |
curl https://formula-cream.pro/api/public/v1/leads/by-external/crm-48213 \
-H "Authorization: Bearer $API_KEY"
#PATCH /leads/{id}
Меняет поля лида. Запрос обязан нести версию, которую вы прочитали, — так два писателя не затрут друг друга.
Заголовки: If-Match: <version> — обязателен; Idempotency-Key — рекомендуется.
| Параметр | Тип | Обязательно | Ограничения | Пример |
|---|---|---|---|---|
status | string | нет | Канонический статус | "proposal" |
assigned_to | string (uuid) | нет | Только сотрудник вашего арендатора | "0c4a9d52-7e71-4c63-8f0b-2a9c6e1d3b44" |
value | object | нет | {amount, currency}; amount — строка | {"amount":"15000.00","currency":"RUB"} |
notes | string | нет | Произвольная строка | "Просила перезвонить" |
lost_reason | string | нет | Для статуса lost | "дорого" |
external_id, external_url | string | нет | Идентификатор и ссылка во внешней CRM | "crm-48213" |
| Ситуация | Ответ |
|---|---|
Нет If-Match | 428 precondition_required |
| Версия устарела | 412 precondition_failed; прочитайте лид заново и повторите |
Поле принадлежит другой стороне (режим external до или после принятия, both по таблице владения) | 409 invalid_state; в errors[].field — путь к полю |
| Недопустимое значение (неизвестный статус, ответственный не из вашего арендатора) | 422 validation_failed |
Нет leads:write | 403 scope_missing |
curl -X PATCH https://formula-cream.pro/api/public/v1/leads/5d1b2c7e-3f0a-4a8e-9a3b-1c2d3e4f5a6b \
-H "Authorization: Bearer $API_KEY" \
-H "If-Match: 1" -H "Content-Type: application/json" \
-d '{"status":"proposal"}'
await fetch(`https://formula-cream.pro/api/public/v1/leads/${id}`, {
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.API_KEY}`,
"If-Match": String(lead.version),
"Content-Type": "application/json",
},
body: JSON.stringify({ status: "proposal" }),
});
r = httpx.patch(
f"https://formula-cream.pro/api/public/v1/leads/{lead_id}",
headers={"Authorization": f"Bearer {os.environ['API_KEY']}",
"If-Match": str(lead["version"])},
json={"status": "proposal"},
)
$ch = curl_init("https://formula-cream.pro/api/public/v1/leads/$leadId");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => "PATCH",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("API_KEY"),
"If-Match: " . $lead["version"],
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode(["status" => "proposal"]),
]);
#POST /leads/{id}/accept
Принять лид во внешнюю CRM (режим external). После вызова owner лида становится external, наши агенты прекращают собственные касания. Тело запроса необязательно; рекомендуется передать external_id и external_url, если вы ещё не сохранили их через PATCH. Ответ 200 OK — объект лида. Если лид уже принят или режим арендатора не external, вернётся 409 invalid_state.
| Параметр | Тип | Обязательно | Ограничения | Пример |
|---|---|---|---|---|
external_id | string | нет | Идентификатор лида во внешней CRM | "crm-48213" |
external_url | string | нет | Абсолютный https-адрес | "https://crm.example.com/leads/48213" |
curl -X POST https://formula-cream.pro/api/public/v1/leads/5d1b2c7e-3f0a-4a8e-9a3b-1c2d3e4f5a6b/accept \
-H "Authorization: Bearer $API_KEY" \
-H "Idempotency-Key: 3b241101-e2bb-4255-8caf-4136c566a962" \
-H "Content-Type: application/json" \
-d '{"external_id":"crm-48213"}'
#POST /leads/{id}/notes
| Параметр | Тип | Обязательно | Ограничения | Пример |
|---|---|---|---|---|
text | string | да | Непустая строка | "Просила перезвонить после 18:00" |
Ответ 201 Created — созданная заметка. Рекомендуется Idempotency-Key.
curl -X POST https://formula-cream.pro/api/public/v1/leads/$LEAD_ID/notes \
-H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
-d '{"text":"Просила перезвонить после 18:00"}'
#POST /leads/{id}/tasks
| Параметр | Тип | Обязательно | Ограничения | Пример |
|---|---|---|---|---|
title | string | да | "Позвонить клиенту" | |
due_at | string | нет | ISO 8601, UTC | "2026-10-12T15:00:00Z" |
assignee_id | string (uuid) | нет | Сотрудник арендатора | "0c4a9d52-7e71-4c63-8f0b-2a9c6e1d3b44" |
Ответ 201 Created — созданная задача. Рекомендуется Idempotency-Key. Задача связывается с лидом. Сегодня задачи на платформе связаны с контактом и диалогом, а не с лидом напрямую; связь с лидом — одна из предпосылок в коде (предпосылка выпуска).
curl -X POST https://formula-cream.pro/api/public/v1/leads/$LEAD_ID/tasks \
-H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
-d '{"title":"Позвонить клиенту","due_at":"2026-10-12T15:00:00Z"}'
#Заметки
- Идемпотентность:
Idempotency-Keyрекомендован для всехPOSTиPATCH; повтор с тем же телом возвращает тот же результат, с другим —409 idempotency_conflict.PUT(/leads/{id}/score,/crm/status-map) идемпотентны по природе: заголовок принимается и игнорируется. Подробнее — Соглашения. - Пределы: чтение, запись и платные операции считаются отдельно; стартовые значения 600 / 120 / 30 запросов в минуту, действующие видны в
GET /me(limits). - События: изменения лида приходят вебхуками (
lead.created,lead.updated,lead.status_changed,lead.scored) в конверте v2; нагрузка тонкая, содержимое с ПДн — подпиской сinclude_pii: true— Вебхуки. - Выдачи ПДн (ответы с контактом) попадают в журнал выдач; арендатор видит его в кабинете и по
GET /disclosures(Передача ПДн). - Предпосылки выпуска: сегодня событие
lead.createdплатформа отправляет не из всех мест создания лида, остальных событий лида нет; единая точка записи лида, версии лидов (version), связь задач с лидом (lead_id) и проверка статусов появятся после доработок кода.
#Открытые вопросы
- Состав и имена полей публичного объекта лида не утверждены; приведён набор по внутренней модели (техническое; уточнится в OpenAPI).
- Состав фильтров
GET /leadsи параметрыaccept,notes,tasksпредложены по внутренней модели и требуют утверждения (техническое). - Соответствие ошибок записи в чужое поле коду
invalid_stateподтверждается при выпуске (техническое).