OpenCRM
Попробовать бесплатно
Документация

REST API, версия 1

Справочник для разработчика: как получить токен, что можно читать и записывать, как приходят ошибки. Все адреса начинаются с https://opencrm.by/api/v1.

Об интеграции

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

Токен выпускает владелец компании: Настройки → Интеграции и API → API-токены. Открытое значение показывается один раз — в базе хранится только его хеш. Передавайте токен в заголовке каждого запроса.

curl https://opencrm.by/api/v1/me \
  -H "Authorization: Bearer ocrm_ВАШ_ТОКЕН"

Компания определяется по токену, поэтому в адресах её нет — интеграции не нужно знать ни идентификатор компании, ни её адрес в системе.

Токен действует от имени выбранного сотрудника, и его права работают и здесь. Если у сотрудника нет права «Сделки: просмотр», токен получит 403 на списке сделок, даже когда выдан с доступом на запись. Отдельно есть режим «только чтение» — он запрещает POST и PATCH целиком.

Ошибки

Ошибка приходит с подходящим HTTP-кодом и телом одного формата:

{
  "error": {
    "code": "invalid_token",
    "message": "Токен неизвестен, отозван или истёк."
  }
}
КодHTTPКогда приходит
unauthenticated401Заголовка Authorization нет или он не в формате Bearer
invalid_token401Токен неизвестен, отозван или у него истёк срок
token_owner_unavailable403Сотрудник, от имени которого выдан токен, удалён или переведён в другую компанию
insufficient_ability403Токен выдан только на чтение, а запрос меняет данные
forbidden403Сотруднику токена не хватает права; в сообщении указано, какого именно
module_disabled403Нужный модуль отключён в тарифе или настройках компании
customer_required422При создании сделки не передан ни counterparty_id, ни customer_name
counterparty_not_found422Контрагент с таким идентификатором в этой компании не найден
funnel_not_found422Воронка не найдена
status_not_found422Этап не найден или относится к другой воронке
no_funnels422В компании ещё не создано ни одной воронки
validation_failed422Данные не прошли проверку — подробности в сообщении

Проверка полей встроенными правилами возвращает привычный для Laravel ответ: message и разбор по полям в errors. Запрос к несуществующей записи — 404, превышение лимита — 429.

Списки и страницы

Списки отдаются постранично. Параметры — page и per_page (по умолчанию 50, максимум 200). Рядом с данными приходит блок meta.

{
  "data": [ ... ],
  "meta": { "page": 1, "per_page": 50, "total": 137, "last_page": 3 }
}

Чтобы забирать только изменившееся, используйте updated_since — дату в любом понятном формате, например 2026-09-01 или 2026-09-01T10:30:00+03:00. Сделки отсортированы по времени изменения, новые сверху.

Лимиты

120 запросов в минуту. При превышении приходит 429 и заголовок Retry-After с числом секунд до следующей попытки. Даты в ответах — в формате ISO 8601 с часовым поясом. Все суммы — числа с двумя знаками после запятой.

Проверка доступа

GET/api/v1/me

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

{
  "data": {
    "company": { "id": 12, "name": "Монтаж и сервис", "slug": "montazh" },
    "user": { "id": 3, "name": "Анна Ковалёва", "role": "owner" },
    "token": {
      "name": "Форма на сайте",
      "abilities": ["read", "write"],
      "expires_at": null
    },
    "modules": ["orders", "counterparties", "tasks", "inventory"]
  }
}

Воронки и этапы

GET/api/v1/funnels

Справочник, без которого нечего передавать в status_id. Этапы приходят в порядке следования в воронке.

{
  "data": [
    {
      "id": 1,
      "name": "Продажи",
      "is_default": true,
      "is_active": true,
      "statuses": [
        { "id": 4, "name": "Новая заявка", "type": "", "position": 1 },
        { "id": 5, "name": "В работе", "type": "", "position": 2 },
        { "id": 9, "name": "Успешно", "type": "success", "position": 8 }
      ]
    }
  ]
}

Поле type отмечает закрывающие этапы: success — успешное завершение, failure — отказ. У обычных этапов оно пустое.

Сделки

GET/api/v1/orders

Список сделок компании. Все параметры необязательны и комбинируются.

ПараметрЧто делает
funnel_idТолько сделки этой воронки
status_idТолько сделки на этом этапе
user_idСделки конкретного менеджера
counterparty_idСделки одного контрагента
updated_sinceИзменённые с указанного момента — для регулярной синхронизации
created_sinceСозданные с указанного момента
searchПоиск по номеру, названию, имени клиента, телефону и почте
page, per_pageСтраница и размер страницы
curl "https://opencrm.by/api/v1/orders?status_id=5&per_page=20" \
  -H "Authorization: Bearer ocrm_ВАШ_ТОКЕН"
{
  "data": [
    {
      "id": 812,
      "number": "ORD-260906-00812",
      "title": "Кондиционер, ул. Немига 5",
      "funnel": { "id": 1, "name": "Продажи" },
      "status": { "id": 5, "name": "В работе", "type": "" },
      "manager": { "id": 3, "name": "Анна Ковалёва" },
      "counterparty_id": 402,
      "customer_name": "Иванов Иван",
      "customer_phone": "+375 29 123-45-67",
      "total_amount": 1840.00,
      "source": "website",
      "created_at": "2026-09-06T10:12:04+03:00",
      "updated_at": "2026-09-06T11:40:19+03:00"
    }
  ],
  "meta": { "page": 1, "per_page": 20, "total": 1, "last_page": 1 }
}
GET/api/v1/orders/{id}

Карточка сделки. К полям списка добавляются customer_kind, customer_contact_name, customer_email, customer_unp, customer_address, comment, items_count и debt_amount.

POST/api/v1/orders

Создаёт сделку. Нужен либо counterparty_id, либо customer_name — остальное необязательно. Не указали воронку — берётся основная, не указали этап — первый в этой воронке.

ПолеОписание
counterparty_idСуществующий контрагент; его данные подставятся в карточку
customer_nameИмя клиента, если контрагента ещё нет
customer_phone, customer_emailКонтакты клиента
customer_kindindividual или company; по умолчанию физлицо
customer_unp, customer_address, customer_contact_nameРеквизиты и контактное лицо
funnel_id, status_idКуда положить сделку
user_idОтветственный менеджер
title, comment, sourceНазвание, комментарий и источник заявки
total_amount, debt_amountСумма сделки и долг
itemsПозиции: product_id или product_name, quantity, price
curl -X POST https://opencrm.by/api/v1/orders \
  -H "Authorization: Bearer ocrm_ВАШ_ТОКЕН" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Заявка с сайта",
    "customer_name": "Иванов Иван",
    "customer_phone": "+375 29 123-45-67",
    "source": "website"
  }'

Ответ — 201 и карточка созданной сделки вместе с присвоенным номером.

Позиции передаются массивом items. Товар из каталога указывается по product_id — название и единица подставятся сами; для разовой позиции достаточно product_name. Сумма сделки считается по позициям, а явный total_amount её перекрывает — так удобно провести скидку.

{
  "customer_name": "Иванов Иван",
  "items": [
    { "product_id": 41, "quantity": 2, "price": 1200.50 },
    { "product_name": "Монтаж", "quantity": 1, "price": 300 }
  ]
}
Позиции не двигают склад: остатки меняются документами прихода, перемещения и списания. Через API сделка получает состав и сумму, а не резерв товара.
Сделка создаётся тем же способом, что и из карточки, поэтому роботы срабатывают сами: заявка с сайта сразу запускает вашу автоматизацию — назначение ответственного, задачу менеджеру, что настроено на первом этапе.
PATCH/api/v1/orders/{id}

Меняет только переданные поля: status_id, user_id, title, comment, customer_phone, customer_email, total_amount, debt_amount. Этап должен принадлежать воронке этой сделки. Смена этапа требует права «Сделки: смена статуса», смена менеджера — «Сделки: смена менеджера».

curl -X PATCH https://opencrm.by/api/v1/orders/812 \
  -H "Authorization: Bearer ocrm_ВАШ_ТОКЕН" \
  -H "Content-Type: application/json" \
  -d '{"status_id": 9}'

Контрагенты

GET/api/v1/counterparties

Параметры: search (имя, компания, контактное лицо, почта, УНП), phone, type, updated_since, а также page и per_page.

Поиск по телефону сравнивает только цифры, поэтому формат записи не важен: +375 (29) 765-43-21 и 297654321 найдут одного и того же контрагента. Это ровно то, что нужно телефонии, чтобы показать карточку на входящем звонке.

curl "https://opencrm.by/api/v1/counterparties?phone=297654321" \
  -H "Authorization: Bearer ocrm_ВАШ_ТОКЕН"
GET/api/v1/counterparties/{id}

Карточка контрагента по идентификатору.

POST/api/v1/counterparties

Обязательное поле — type: individual или company. Физлицу нужен full_name, юрлицу — company_name. Дополнительно принимаются contact_name, contact_position, phone, email, unp, address_region, address_locality, address и comment.

curl -X POST https://opencrm.by/api/v1/counterparties \
  -H "Authorization: Bearer ocrm_ВАШ_ТОКЕН" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "company",
    "company_name": "ООО Ромашка",
    "unp": "191234567",
    "phone": "+375 17 200-30-40"
  }'

Задачи

GET/api/v1/tasks

Фильтры: status (planned, in_progress, pending_approval, done), priority, assigned_user_id, due_before, due_after, updated_since, search, плюс page и per_page.

GET/api/v1/tasks/{id}

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

POST/api/v1/tasks

Обязательны title и due_at — срок в формате даты со временем. Дополнительно: description, status, priority, assigned_user_ids и watcher_user_ids. Первый из ответственных становится основным, а сотрудник, уже назначенный ответственным, в наблюдатели не дублируется.

curl -X POST https://opencrm.by/api/v1/tasks \
  -H "Authorization: Bearer ocrm_ВАШ_ТОКЕН" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Позвонить клиенту",
    "due_at": "2026-09-20T10:00:00+03:00",
    "assigned_user_ids": [7]
  }'
PATCH/api/v1/tasks/{id}

Меняет только переданные поля. Перевод в done проставляет время завершения, в in_progress — время начала. Списки участников трогаются только если переданы: без watcher_user_ids наблюдатели остаются как были.

Заявки «Неразобранного»

Буфер входящих обращений: заявка ждёт решения менеджера и только потом становится сделкой. Внешней системе это удобнее, чем создавать сделку сразу — воронка не засоряется спамом и ошибочными обращениями.

GET/api/v1/leads

Фильтры: status (new, accepted, rejected), source и created_since.

POST/api/v1/leads

Нужен хотя бы один способ связи — phone или email, иначе придёт contact_required. Дополнительно принимаются name, comment, page_url и произвольный объект payload — в нём переживут любые свои поля.

curl -X POST https://opencrm.by/api/v1/leads \
  -H "Authorization: Bearer ocrm_ВАШ_ТОКЕН" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Иванов Иван",
    "phone": "+375 29 123-45-67",
    "comment": "Заявка с лендинга",
    "payload": { "услуга": "Монтаж кондиционера" }
  }'

Повторная отправка того же телефона в пределах десяти минут считается дублем и возвращает ту же заявку — двойной клик по кнопке на сайте не создаёт две.

POST/api/v1/leads/{id}/accept

Принять заявку: создаётся контрагент (если такого ещё нет) и сделка. Необязательные funnel_id, status_id и assigned_user_id говорят, куда её положить. Уже разобранная заявка отвечает already_handled.

Справочники

Товары, склады, остатки и объекты доступны только на чтение: их меняют документами внутри системы, чтобы не разошлись партии и себестоимость.

GET/api/v1/products

Фильтры: search (название и артикул), category_id, is_active.

GET/api/v1/warehouses

Полный список складов компании — без страниц, их обычно немного.

GET/api/v1/balances

Остатки по складам: количество, средняя цена и стоимость. Фильтры — product_id, warehouse_id и only_in_stock.

{
  "data": [
    {
      "product": { "id": 41, "name": "Труба медная", "sku": "TR-15", "unit": "м" },
      "warehouse": { "id": 2, "name": "Основной склад" },
      "quantity": 148.5,
      "average_price": 12.40,
      "total_cost": 1841.40
    }
  ],
  "meta": { "page": 1, "per_page": 50, "total": 1, "last_page": 1 }
}
GET/api/v1/objects
GET/api/v1/objects/{id}

Объекты с адресом и ответственным. Фильтры списка: search, is_active, updated_since.

Веб-хуки

Чтобы не опрашивать API, подпишитесь на события: Настройки → Интеграции и API → Веб-хуки. Укажите адрес получателя (только https) и нужные события — система пришлёт секрет для проверки подписи, он показывается один раз.

Какие события есть

СобытиеКогда приходит
order.createdСделка создана — из карточки, через API или роботом
order.status_changedСделка перешла на другой этап; в теле есть и предыдущий этап
task.completedЗадачу перевели в «Выполнено» — один раз на закрытие
lead.createdПришла заявка с формы на сайте или через API
call.finishedЗвонок завершён: направление, статус, длительность, ссылка на запись
pingПроверка связи по кнопке в настройках

Что приходит

Мы отправляем POST с телом JSON и тремя заголовками.

POST /hooks/opencrm HTTP/1.1
Content-Type: application/json
X-OpenCRM-Event: order.status_changed
X-OpenCRM-Delivery: 4471
X-OpenCRM-Signature: sha256=9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08

{
  "event": "order.status_changed",
  "occurred_at": "2026-09-06T12:04:11+03:00",
  "data": {
    "order": { "id": 812, "number": "ORD-260906-00812", "status": { "id": 9, "name": "Успешно", "type": "success" } },
    "previous_status": { "id": 5, "name": "В работе", "type": "" }
  }
}

Сделка внутри data.order описана так же, как в ответах API. В событии order.created она приходит в подробном виде — с контактами, комментарием и суммами.

Проверка подписи

X-OpenCRM-Signature — это HMAC-SHA256 от тела запроса, посчитанный вашим секретом. Сравнивайте строки безопасным способом и считайте подпись по сырому телу, до разбора JSON.

$body = file_get_contents('php://input');
$expected = 'sha256=' . hash_hmac('sha256', $body, $secret);

if (! hash_equals($expected, $_SERVER['HTTP_X_OPENCRM_SIGNATURE'] ?? '')) {
    http_response_code(403);
    exit;
}

$event = json_decode($body, true);

Повторные попытки

Ответьте любым кодом 2xx — этого достаточно, тело ответа мы не читаем. Если получатель ответил ошибкой или не ответил за 10 секунд, отправка повторится с растущей паузой: через 1, 4, 9, 16 и 25 минут, всего до шести попыток. Заголовок X-OpenCRM-Delivery у повторов тот же — по нему удобно отбрасывать дубли.

Отвечайте быстро: примите событие, поставьте его в свою очередь и верните 200. Долгая обработка в момент запроса приводит к таймауту и лишним повторам.

Сценарий: заявка с формы на сайте

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

<?php
// обработчик формы на вашем сайте
$response = file_get_contents('https://opencrm.by/api/v1/orders', false, stream_context_create([
    'http' => [
        'method'  => 'POST',
        'header'  => "Authorization: Bearer ocrm_ВАШ_ТОКЕН\r\n"
                   . "Content-Type: application/json\r\n",
        'content' => json_encode([
            'title'          => 'Заявка с сайта: ' . $_POST['service'],
            'customer_name'  => $_POST['name'],
            'customer_phone' => $_POST['phone'],
            'comment'        => $_POST['message'],
            'source'         => 'website',
        ], JSON_UNESCAPED_UNICODE),
        'ignore_errors' => true,
    ],
]));

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

Регулярная выгрузка изменений

Внешней системе, которая забирает данные по расписанию, достаточно запоминать время последнего обмена и запрашивать /api/v1/orders?updated_since=…. Если изменения нужны сразу, а не по расписанию, — подпишитесь на веб-хуки.

Нужен доступ или разбор задачи?

Напишите нам: подскажем, как собрать обмен под ваш сайт, телефонию или учётную систему.

Все интеграции Написать нам