thecrm.developers
● API v1 · стабильный

TheCRM API для разработчиков

Публичный REST API для интеграций с CRM учебного центра. Доступно: геймификация (коины и подарки), ученики, лиды, оплаты (чтение) и исходящие вебхуки на события (ученик, оплата, лид).

Изоляция на уровне БД
Ключ видит только свой центр — Postgres RLS, не «мы аккуратно написали».
Ключи и права
Хэшируются, показываются один раз, отзыв в один клик, права-scopes.
Лимиты и версии
Rate-limit на ключ, версия /v1 — меняем без поломок у вас.

Быстрый старт

  1. 1
    Получите ключ
    В CRM: меню → Разработчикам / API. Директор (или сотрудник с этим правом) создаёт ключ с нужными правами. Полный ключ показывается один раз.
  2. 2
    Передайте ключ в заголовке
    Authorization: Bearer tcrm_live_… — во всех запросах. Центр определяется из ключа.
  3. 3
    Сделайте первый вызов
    Например, получите каталог подарков — и вы в деле.
Запрос
curl https://thecrm.uz/api/v1/gamification/gifts \
  -H "Authorization: Bearer tcrm_live_…"

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

Каждый запрос авторизуется ключом центра в заголовке Authorization: Bearer tcrm_live_…. Организация определяется из ключа — параметр orgId не нужен и не принимается. Ключ действует только в рамках своего центра.

Base URLhttps://thecrm.uz/api/v1пути ниже указаны относительно него
Права (scopes). Ключу выдаются только нужные права (принцип наименьших привилегий):
gamification:readБаланс, каталог подарков, история обменов
gamification:writeОбмен коинов на подарок
students:readСписок учеников и карточка ученика
leads:readСписок лидов воронки
payments:readСписок оплат (чтение)
Песочница. Создайте тестовый ключ tcrm_test_… — он отвечает готовыми примерами данных, а обмен коинов ничего не меняет в центре. Идеально, чтобы собрать интеграцию до боевого ключа. Боевой ключ — tcrm_live_….

Лимиты и ошибки

Rate limit. По умолчанию 120 запросов/мин на ключ. На каждый ответ отдаём 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) — на нём и стройте ветвление.

400 Bad Request
{
  "statusCode": 400,
  "message": "not_enough_coins",
  "error": "Bad Request"
}
GETтребует gamification:read

Баланс коинов ученика

Баланс геймификации ученика: заработано, удержано под заявки на подарки и доступно к трате. Тот же расчёт, что видит ученик в приложении — единый источник правды.

GET/v1/gamification/students/{studentId}/balance

Параметры пути

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_…"
200 OK· application/json
{
  "studentId": "a1b2c3d4-…",
  "name": "Karimov Aziz",
  "earned": 1240,
  "held": 200,
  "available": 1040
}
GETтребует gamification:read

Каталог подарков

Список активных подарков центра, которые ученик может получить за коины.

GET/v1/gamification/gifts

Ответ 200

id
string
ID подарка.
name
string
Название.
description
string | null
Описание.
costCoins
integer
Стоимость в коинах.
Запрос
curl https://thecrm.uz/api/v1/gamification/gifts \
  -H "Authorization: Bearer tcrm_live_…"
200 OK· application/json
[
  { "id": "…", "name": "Backpack", "description": null, "costCoins": 2000 },
  { "id": "…", "name": "Headphones", "description": "…", "costCoins": 5000 }
]
GETтребует gamification:read

История обменов

Заявки ученика на подарки со статусами. Cursor-пагинация, как в остальных списках.

GET/v1/gamification/students/{studentId}/redemptions

Параметры пути

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_…"
200 OK· application/json
{
  "items": [
    {
      "id": "…",
      "giftName": "Backpack",
      "costCoins": 2000,
      "status": "REQUESTED",
      "requestedAt": "2026-09-21T14:32:00Z"
    }
  ],
  "nextCursor": null
}
POSTтребует gamification:write

Обмен коинов на подарок

Создаёт заявку на подарок (код 201). Остаток проверяется на сервере: при нехватке — 400 not_enough_coins; одна незавершённая заявка на ученика — 400 request_already_pending. Передайте заголовок Idempotency-Key, чтобы безопасно повторять запрос: повтор с тем же ключом вернёт ту же заявку, не создавая вторую.

POST/v1/gamification/students/{studentId}/redeem

Параметры пути

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":"…"}'
201 Created· application/json
{
  "ok": true,
  "id": "…",
  "status": "REQUESTED",
  "giftName": "Backpack",
  "costCoins": 2000,
  "requestedAt": "2026-09-22T10:00:00Z"
}
GETтребует students:read

Список учеников

Постранично возвращает учеников центра. Максимум 100 на страницу — постранично через cursor.

GET/v1/students

Параметры запроса

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_…"
200 OK· application/json
{
  "items": [
    { "id": "…", "firstName": "Aziz", "lastName": "Karimov",
      "phone": "+99890…", "status": "ACTIVE", "enrolledAt": "2026-03-01T…" }
  ],
  "nextCursor": "…"
}
GETтребует students:read

Ученик по ID

Один ученик центра. Не найден в вашем центре — 404.

GET/v1/students/{id}

Параметры пути

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_…"
200 OK· application/json
{
  "id": "…", "firstName": "Aziz", "lastName": "Karimov",
  "phone": "+99890…", "status": "ACTIVE", "enrolledAt": "2026-03-01T…"
}
GETтребует leads:read

Список лидов

Постранично возвращает лиды центра (заявки в воронке).

GET/v1/leads

Параметры запроса

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_…"
200 OK· application/json
{
  "items": [
    { "id": "…", "fullName": "Nigora", "phone": "+99890…",
      "source": "INSTAGRAM", "stage": "NEW", "createdAt": "2026-09-20T…" }
  ],
  "nextCursor": null
}
GETтребует payments:read

Список оплат

Постранично возвращает оплаты центра. Только чтение. Суммы — в сумах (целое число).

GET/v1/payments

Параметры запроса

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_…"
200 OK· application/json
{
  "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-DeliveryID доставки — стабилен между повторами. Дедуп на нём.
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 минут.

Подписывается СЫРОЕ тело. Считайте HMAC от неизменённых байтов запроса. В Express не берите объект после express.json() — используйте raw body (напр. express.raw()), иначе подпись не сойдётся.
Отвечайте 2xx. Иначе доставка считается неуспешной и повторяется с нарастающей задержкой (1, 5, 15, 60, 180, 360 мин); после 6 попыток помечается как недоставленная.
Тело запроса
{
  "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"
  }
}
Проверка (Node.js)
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), деньги — целое число сум.

Журнал изменений
2026-09-22v1
Вебхуки (student.created, payment.completed, lead.created) с подписью t=,v1=. Песочница (tcrm_test_). Idempotency-Key и код 201 на обмене коинов. Cursor-пагинация истории обменов. Заголовки X-RateLimit-*.
2026-09v1
Запуск: ключи центра, геймификация, чтение учеников/лидов/оплат, OpenAPI.