TheCRM API для разработчиков
Публичный REST API для интеграций с CRM учебного центра. Доступно: геймификация (коины и подарки), ученики, лиды, оплаты (чтение) и исходящие вебхуки на события (ученик, оплата, лид).
Быстрый старт
- 1Получите ключВ CRM: меню → Разработчикам / API. Директор (или сотрудник с этим правом) создаёт ключ с нужными правами. Полный ключ показывается один раз.
- 2Передайте ключ в заголовкеAuthorization: Bearer tcrm_live_… — во всех запросах. Центр определяется из ключа.
- 3Сделайте первый вызовНапример, получите каталог подарков — и вы в деле.
curl https://thecrm.uz/api/v1/gamification/gifts \ -H "Authorization: Bearer tcrm_live_…"
Аутентификация
Каждый запрос авторизуется ключом центра в заголовке Authorization: Bearer tcrm_live_…. Организация определяется из ключа — параметр orgId не нужен и не принимается. Ключ действует только в рамках своего центра.
gamification:read | Баланс, каталог подарков, история обменов |
gamification:write | Обмен коинов на подарок |
students:read | Список учеников и карточка ученика |
leads:read | Список лидов воронки |
payments:read | Список оплат (чтение) |
Лимиты и ошибки
X-RateLimit-Limit, X-RateLimit-Remaining и X-RateLimit-Reset (секунд до сброса окна). При превышении — 429 с Retry-After. Нужен выше лимит — напишите нам.| Код | Значение |
|---|---|
| 200 | Успех. |
| 400 | Нарушено бизнес-правило (напр. not_enough_coins, request_already_pending). |
| 401 | Ключ не передан или недействителен. |
| 403 | У ключа нет нужного права (scope). |
| 404 | Ресурс не найден в вашем центре. |
| 429 | Превышен лимит запросов. Повторите после Retry-After. |
Тело ошибки
Все ошибки — JSON одного вида. Машиночитаемый код в message (напр. not_enough_coins) — на нём и стройте ветвление.
{ "statusCode": 400, "message": "not_enough_coins", "error": "Bad Request" }
Баланс коинов ученика
Баланс геймификации ученика: заработано, удержано под заявки на подарки и доступно к трате. Тот же расчёт, что видит ученик в приложении — единый источник правды.
Параметры пути
| studentIdобязательно string · uuid | ID ученика. Должен принадлежать вашему центру, иначе 404. |
Ответ 200
| studentId string | ID ученика. |
| name string | Имя ученика. |
| earned integer | Всего заработано коинов. |
| held integer | Удержано под незавершённые заявки. |
| available integer | Доступно к трате = earned − held. |
curl https://thecrm.uz/api/v1/gamification/students/{studentId}/balance \
-H "Authorization: Bearer tcrm_live_…"{ "studentId": "a1b2c3d4-…", "name": "Karimov Aziz", "earned": 1240, "held": 200, "available": 1040 }
Каталог подарков
Список активных подарков центра, которые ученик может получить за коины.
Ответ 200
| id string | ID подарка. |
| name string | Название. |
| description string | null | Описание. |
| costCoins integer | Стоимость в коинах. |
curl https://thecrm.uz/api/v1/gamification/gifts \ -H "Authorization: Bearer tcrm_live_…"
[ { "id": "…", "name": "Backpack", "description": null, "costCoins": 2000 }, { "id": "…", "name": "Headphones", "description": "…", "costCoins": 5000 } ]
История обменов
Заявки ученика на подарки со статусами. Cursor-пагинация, как в остальных списках.
Параметры пути
| studentIdобязательно string · uuid | ID ученика. |
Параметры запроса
| limit integer | Размер страницы, 1–100 (по умолчанию 50). |
| cursor string | Курсор следующей страницы (nextCursor из ответа). |
Ответ 200
| items[].id string | ID заявки. |
| items[].giftName string | Название подарка на момент заявки. |
| items[].costCoins integer | Списано коинов. |
| items[].status enum | Одно из: REQUESTED, APPROVED, REJECTED. |
| items[].requestedAt string · date-time | Когда подана заявка. |
| nextCursor string | null | Курсор следующей страницы (null — конец). |
curl https://thecrm.uz/api/v1/gamification/students/{studentId}/redemptions \
-H "Authorization: Bearer tcrm_live_…"{ "items": [ { "id": "…", "giftName": "Backpack", "costCoins": 2000, "status": "REQUESTED", "requestedAt": "2026-09-21T14:32:00Z" } ], "nextCursor": null }
Обмен коинов на подарок
Создаёт заявку на подарок (код 201). Остаток проверяется на сервере: при нехватке — 400 not_enough_coins; одна незавершённая заявка на ученика — 400 request_already_pending. Передайте заголовок Idempotency-Key, чтобы безопасно повторять запрос: повтор с тем же ключом вернёт ту же заявку, не создавая вторую.
Параметры пути
| studentIdобязательно string · uuid | ID ученика. |
Тело запроса
| giftIdобязательно string · uuid | ID подарка из каталога. |
Ответ 200
| ok boolean | true — заявка создана. |
| id string | ID созданной заявки. |
| status enum | REQUESTED · APPROVED · REJECTED. |
| giftName string | Название подарка. |
| costCoins integer | Списано коинов. |
| requestedAt string · date-time | Когда подана заявка. |
curl -X POST https://thecrm.uz/api/v1/gamification/students/{studentId}/redeem \
-H "Authorization: Bearer tcrm_live_…" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: <unique-operation-id>" \
-d '{"giftId":"…"}'{ "ok": true, "id": "…", "status": "REQUESTED", "giftName": "Backpack", "costCoins": 2000, "requestedAt": "2026-09-22T10:00:00Z" }
Список учеников
Постранично возвращает учеников центра. Максимум 100 на страницу — постранично через cursor.
Параметры запроса
| limit integer | Размер страницы, 1–100 (по умолчанию 50). |
| cursor string | Курсор следующей страницы (nextCursor из ответа). |
| status string | Фильтр по статусу: TRIAL, ACTIVE, PAUSED, GRADUATED, CHURNED. |
Ответ 200
| items[].id string | ID ученика. |
| items[].firstName string | Имя. |
| items[].lastName string | Фамилия. |
| items[].phone string | null | Телефон. |
| items[].status enum | TRIAL · ACTIVE · PAUSED · GRADUATED · CHURNED. |
| items[].enrolledAt string · date-time | Когда записан. |
| nextCursor string | null | Курсор следующей страницы (null — конец). |
curl "https://thecrm.uz/api/v1/students?limit=50" \ -H "Authorization: Bearer tcrm_live_…"
{ "items": [ { "id": "…", "firstName": "Aziz", "lastName": "Karimov", "phone": "+99890…", "status": "ACTIVE", "enrolledAt": "2026-03-01T…" } ], "nextCursor": "…" }
Ученик по ID
Один ученик центра. Не найден в вашем центре — 404.
Параметры пути
| idобязательно string · uuid | ID ученика. |
Ответ 200
| id string | ID ученика. |
| firstName string | Имя. |
| lastName string | Фамилия. |
| phone string | null | Телефон. |
| status enum | TRIAL · ACTIVE · PAUSED · GRADUATED · CHURNED. |
| enrolledAt string · date-time | Когда записан. |
curl https://thecrm.uz/api/v1/students/{id} \
-H "Authorization: Bearer tcrm_live_…"{ "id": "…", "firstName": "Aziz", "lastName": "Karimov", "phone": "+99890…", "status": "ACTIVE", "enrolledAt": "2026-03-01T…" }
Список лидов
Постранично возвращает лиды центра (заявки в воронке).
Параметры запроса
| limit integer | Размер страницы, 1–100 (по умолчанию 50). |
| cursor string | Курсор следующей страницы (nextCursor из ответа). |
| stage string | Фильтр по стадии: NEW, CONTACTED, DEFERRED, TRIAL, CONVERTED, LOST. |
Ответ 200
| items[].id string | ID лида. |
| items[].fullName string | Имя. |
| items[].phone string | null | Телефон. |
| items[].source string | Источник (INSTAGRAM, TELEGRAM, WEBSITE, …). |
| items[].stage enum | NEW · CONTACTED · DEFERRED · TRIAL · CONVERTED · LOST. |
| items[].createdAt string · date-time | Когда создан. |
| nextCursor string | null | Курсор следующей страницы. |
curl "https://thecrm.uz/api/v1/leads?limit=50" \ -H "Authorization: Bearer tcrm_live_…"
{ "items": [ { "id": "…", "fullName": "Nigora", "phone": "+99890…", "source": "INSTAGRAM", "stage": "NEW", "createdAt": "2026-09-20T…" } ], "nextCursor": null }
Список оплат
Постранично возвращает оплаты центра. Только чтение. Суммы — в сумах (целое число).
Параметры запроса
| limit integer | Размер страницы, 1–100 (по умолчанию 50). |
| cursor string | Курсор следующей страницы (nextCursor из ответа). |
| status string | Фильтр: PENDING, COMPLETED, FAILED, REFUNDED. |
Ответ 200
| items[].id string | ID платежа. |
| items[].studentId string | null | ID ученика. |
| items[].amount integer | Сумма в сумах. |
| items[].status enum | PENDING · COMPLETED · FAILED · REFUNDED. |
| items[].paidAt string · date-time | null | Когда оплачено. |
| nextCursor string | null | Курсор следующей страницы. |
curl "https://thecrm.uz/api/v1/payments?status=COMPLETED" \ -H "Authorization: Bearer tcrm_live_…"
{ "items": [ { "id": "…", "studentId": "…", "amount": 750000, "status": "COMPLETED", "paidAt": "2026-09-05T…" } ], "nextCursor": "…" }
События: получайте изменения в реальном времени
Зарегистрируйте URL в CRM (меню → Разработчикам / API → Вебхуки), выберите события — и мы будем слать на него POST с JSON. Доставка с повторами (backoff до 6 попыток).
События
student.created | Создан ученик |
payment.completed | Оплата проведена |
lead.created | Создан лид |
Заголовки и дедупликация
X-TheCRM-Event | Тип события, напр. payment.completed |
X-TheCRM-Delivery | ID доставки — стабилен между повторами. Дедуп на нём. |
X-TheCRM-Signature | Подпись вида t=<unix>,v1=<hmac> |
Поле id в теле совпадает с X-TheCRM-Delivery и не меняется при повторной доставке того же события — используйте его как ключ идемпотентности на своей стороне.
Проверка подписи
Заголовок X-TheCRM-Signature: t=<unix>,v1=<hmac>. Подпись v1 — это HMAC-SHA256 строки `${t}.${rawBody}` вашим секретом (виден в CRM). Timestamp внутри подписи защищает от повторного проигрывания перехваченного запроса — отвергайте события старше 5 минут.
express.json() — используйте raw body (напр. express.raw()), иначе подпись не сойдётся.{ "id": "8f3c1e2a-… (= X-TheCRM-Delivery)", "event": "payment.completed", "createdAt": "2026-09-22T10:00:00Z", "data": { "id": "…", "studentId": "…", "amount": 750000, "status": "COMPLETED", "paidAt": "2026-09-22T10:00:00Z" } }
import crypto from 'crypto';
// rawBody — СЫРЫЕ байты тела (Buffer/строка),
// header — значение X-TheCRM-Signature.
function verify(rawBody, header, secret) {
const parts = Object.fromEntries(
String(header || '').split(',').map(p => p.split('='))
);
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > 300) {
return false; // старше 5 мин — возможный replay
}
const mac = crypto
.createHmac('sha256', secret)
.update(t + '.' + rawBody)
.digest('hex');
const a = Buffer.from(mac);
const b = Buffer.from(parts.v1 || '');
return a.length === b.length &&
crypto.timingSafeEqual(a, b);
}Изменения и совместимость
Политика версий
- Версия зашита в путь — /v1. В пределах v1 мы делаем только обратно-совместимые изменения.
- Совместимо и НЕ ломает: новые поля в ответах, новые эндпоинты, новые необязательные параметры. Читайте ответы снисходительно — незнакомые поля просто игнорируйте.
- Ломающее изменение = новая версия (/v2). Старую версию поддерживаем минимум 6 месяцев после анонса и предупреждаем заранее.
Соглашение об именах полей
Имена отражают сущность и не унифицированы между ресурсами намеренно: у ученика — firstName и lastName, у лида — единое fullName, у баланса — name (готовая строка для показа). Даты — ISO 8601 (UTC), деньги — целое число сум.