Бюро Цифровых Технологий

База знаний

Документация «Вираж»

База знаний по продуктам и API экосистемы «Вираж»: модули, методы, вебхуки — с примерами.

Обновлено: 16.10.2026

Парковка

Парковочные билеты на купленные места и фактические проезды от парковочной системы клуба: импорт покупки в CRM, проверка доступа на точке, въезды и выезды, в том числе накопленные без связи.

Охват раздела

ОбластьВ разделеКомментарий
Справочники парковок, зон, мест и точекдаТолько чтение
Импорт купленных парковочных билетовдаОсновной сценарий интеграции
Связь с клиентом, заказом и мероприятиемдаПо идентификаторам CRM или источника
Блокировка, снятие блокировки, отменадаУправляемый статус билета
Проверка доступадаБез побочных эффектов
Въезды, выезды, отказы, офлайн-пачкидаВключая опоздавшие события
Эквайринг и возврат денегнетПриходят подтверждённые суммы источника
Открытие шлагбауманетПроверка доступа ничего не открывает
Продажа и резерв места до оплатынетВ CRM попадает купленное место
Редактирование справочников парковокнетВедётся на стороне парковки
PDF-билет, письма, вебхуки, перевыпуск QRнетРешаются вне этого раздела

Соглашения

ПравилоКак в API
ТранспортHTTPS, JSON в UTF-8, поля в snake_case
ИсточникКлуб и source_system берутся из токена, не из тела
ВремяRFC 3339 с часовым поясом, в базе UTC
ДеньгиЦелые минимальные единицы: 150000 = 1500 RUB
ИдемпотентностьIdempotency-Key на каждый изменяющий вызов, хранится от 7 суток
Версия записиIf-Match: "3"; расхождение — 412, нет заголовка — 428
Страницыdata + meta.next_cursor; limit до 100, по умолчанию 50
Лимиты300 запросов в минуту на чтение, 120 на запись, 600 на проверку доступа
Неизвестные поля422; свои данные передаются в metadata до 16 КБ

Две пары полей легко перепутать. version — версия записи в CRM для If-Match, source_version — версия снимка у источника. occurred_at — когда проезд произошёл, received_at — когда платформа его приняла; второе не заменяет первое.

ПравоГде нужно
parking:readВсе методы GET
parking:tickets:writeСоздание, импорт, правка билета, suspend, resume, cancel
parking:passages:writePOST /passages и POST /passages/batch
parking:access:checkPOST /access/check
parking:credentials:readПоле access_code в ответе, дополнительно к остальным правам

Обзор методов

МетодПутьОписание
GET/integrations/parking/lotsСправочник парковок
GET/integrations/parking/lots/{parking_lot_id}/spacesМеста парковки
PUT/integrations/parking/tickets/external/{external_id}Занести купленное место
POST/integrations/parking/ticketsСоздать парковочный билет
GET/integrations/parking/ticketsСписок билетов
GET/integrations/parking/tickets/{ticket_id}Карточка билета
GET/integrations/parking/tickets/{ticket_id}/statusКомпактный статус билета
PATCH/integrations/parking/tickets/{ticket_id}Исправить билет
POST/integrations/parking/tickets/{ticket_id}/suspendЗаблокировать билет
POST/integrations/parking/tickets/{ticket_id}/resumeСнять блокировку
POST/integrations/parking/tickets/{ticket_id}/cancelОтменить билет
POST/integrations/parking/access/checkПроверить доступ
POST/integrations/parking/passagesПередать проезд
POST/integrations/parking/passages/batchПередать пачку проездов
GET/integrations/parking/passagesИстория проездов

Справочник парковок

Эндпоинт
GET /integrations/parking/lots

Парковки, доступные токену интеграции, вместе с зонами и точками проезда. Во всех остальных методах используются ID отсюда: свои идентификаторы парковок и шлагбаумов не принимаются.

Параметры

ПараметрГдеТипОбязателенОписание
statusquerystringнетФильтр по состоянию парковки: active, closed, maintenance.
limitqueryintegerнетРазмер страницы, от 1 до 100. По умолчанию — 50.
cursorquerystringнетЗначение meta.next_cursor из предыдущего ответа.
Запрос
curl "https://api.virazh.io/v1/integrations/parking/lots?status=active" \
  -H "Authorization: Bearer sk_test_51H..."
Ответ
{
  "data": [
    {
      "id": "10000000-0000-4000-8000-000000000001",
      "external_id": "lot-north",
      "name": "Северная парковка",
      "address": "ул. Спортивная, 1",
      "timezone": "Europe/Moscow",
      "capacity": 420,
      "status": "active",
      "zones": [
        { "id": "20000000-...-0001", "name": "Зона A" }
      ],
      "gates": [
        {
          "id": "30000000-...-0001",
          "name": "Въезд с ул. Спортивной",
          "direction": "entry"
        }
      ]
    }
  ],
  "meta": { "next_cursor": null, "has_more": false }
}

Поля ответа

ПолеТипОписание
data[].capacityintegerСколько мест парковки можно продать всего — это не текущий остаток свободных.
data[].timezonestringЧасовой пояс парковки в формате IANA: по нему клуб читает сроки действия билетов.
data[].zonesarray<Zone>Зоны парковки: id, external_id, name.
data[].gatesarray<Gate>Точки проезда: id, external_id, name и direction — entry, exit или both.
meta.next_cursorstring | nullКурсор следующей страницы; null — страниц больше нет.

Места парковки

Эндпоинт
GET /integrations/parking/lots/{parking_lot_id}/spaces

Нумерованные места с типом, пригодностью и оценкой занятости. Метод только читает справочник: место он не резервирует.

Параметры

ПараметрГдеТипОбязателенОписание
parking_lot_idпутьuuidдаПарковка из справочника.
zone_idqueryuuidнетТолько места указанной зоны.
statusquerystringнетПригодность места: active, blocked, maintenance.
limitqueryintegerнетРазмер страницы, от 1 до 100. По умолчанию — 50.
Запрос
curl "https://api.virazh.io/v1/integrations/parking/lots/10000000-0000-4000-8000-000000000001/spaces?status=active" \
  -H "Authorization: Bearer sk_test_51H..."
Ответ
{
  "data": [
    {
      "id": "10000000-0000-4000-8000-000000000042",
      "external_id": "A-042",
      "parking_lot_id": "10000000-...-0001",
      "zone_id": "20000000-...-0001",
      "number": "A-042",
      "type": "standard",
      "status": "active",
      "occupancy": "free",
      "occupancy_updated_at": "2026-10-20T18:05:00Z"
    }
  ],
  "meta": { "next_cursor": null, "has_more": false }
}

Поля ответа

ПолеТипОписание
data[].typestringКатегория места: standard, accessible, vip, ev.
data[].statusstringПригодность места к использованию: active, blocked, maintenance. Не путать с занятостью.
data[].occupancystringОценка фактической занятости по проездам: free, occupied, unknown.
data[].occupancy_updated_atdatetime | nullКогда занятость подтверждалась последний раз; null — сведений нет, значение occupancy доверять нельзя.

Занести купленное место

Эндпоинт
PUT /integrations/parking/tickets/external/{external_id}

Основной метод интеграции: заносит купленный билет и обновляет его при повторной синхронизации. Запись ищется по паре «источник + external_id», поэтому повторная выгрузка заказа не создаёт второй билет. Передаётся полный снимок импортируемых полей: пропущенные необязательные поля очищаются.

Параметры

ПараметрГдеТипОбязателенОписание
external_idпутьstringдаID парковочного билета в системе-источнике, до 128 символов. Обязан совпадать с полем external_id в теле.
Idempotency-KeyзаголовокuuidдаКлюч идемпотентности запроса. Тот же ключ с другим телом вернёт 409 IDEMPOTENCY_CONFLICT.
source_versionтелоintegerдаМонотонно возрастающая версия записи у источника, от 1. Меньшая версия — 409 STALE_SOURCE_VERSION, та же версия с другим снимком — 409 SOURCE_VERSION_CONFLICT.
external_order_idтелоstringдаЗаказ у источника. Один заказ может содержать несколько парковочных билетов.
external_order_line_idтелоstringдаСтрока парковочной покупки внутри заказа — именно по ней сумма учитывается один раз.
parking_lot_idтелоuuidдаПарковка из справочника.
parking_space_idтелоuuidнетКонкретное место. Без него билет даёт право на любое свободное место в указанной зоне или на парковке.
valid_fromтелоdatetimeдаНачало действия включительно.
valid_untilтелоdatetimeдаКонец действия исключительно; строго позже valid_from.
max_entriesтелоinteger | nullдаЛимит успешных въездов: 1 — разовый билет, null — без ограничения. Поле обязательно, подразумеваемого значения нет.
paymentтелоobjectдаПодтверждённые источником сведения об оплате: status, amount_minor, currency, paid_minor, refunded_minor, paid_at. Деньги этот метод не списывает и не возвращает.
customerтелоobjectнетПокупатель: crm_user_id либо external_id, name, email, phone. Покупка без профиля допустима.
vehicleтелоobjectнетАвтомобиль: plate_number и country_code обязательны внутри объекта. Можно не передавать, если доступ только по QR-коду.
event_idтелоintegerнетМероприятие CRM, к которому относится парковка.
Запрос
curl -X PUT "https://api.virazh.io/v1/integrations/parking/tickets/external/parking-ticket-10001" \
  -H "Authorization: Bearer sk_test_51H..." \
  -H "Idempotency-Key: 40000000-0000-4000-8000-000000000001" \
  -H "Content-Type: application/json" \
  -d '{
    "external_id": "parking-ticket-10001",
    "source_version": 1,
    "external_order_id": "order-5001",
    "external_order_line_id": "line-1",
    "customer": { "external_id": "customer-101" },
    "parking_lot_id": "10000000-0000-4000-8000-000000000001",
    "parking_space_id": "10000000-0000-4000-8000-000000000042",
    "vehicle": { "plate_number": "А123АА777", "country_code": "RU" },
    "valid_from": "2026-10-20T16:00:00+03:00",
    "valid_until": "2026-10-20T23:00:00+03:00",
    "max_entries": 1,
    "payment": {
      "status": "paid",
      "amount_minor": 150000,
      "currency": "RUB",
      "paid_minor": 150000,
      "refunded_minor": 0,
      "paid_at": "2026-10-15T14:30:00+03:00"
    }
  }'
Ответ
{
  "data": {
    "id": "20000000-0000-4000-8000-000000000001",
    "number": "PARK-000001",
    "external_id": "parking-ticket-10001",
    "source_version": 1,
    "source_system": "parking-vendor",
    "status": "active",
    "effective_status": "valid",
    "occupancy_status": "outside",
    "entries_used": 0,
    "entries_remaining": 1,
    "crm_user_id": 4821,
    "crm_order_id": 91045,
    "version": 1,
    "created_at": "2026-10-15T11:30:02Z",
    "updated_at": "2026-10-15T11:30:02Z"
  }
}

Поля ответа

ПолеТипОписание
data.iduuidID билета в CRM — им оперируют остальные методы.
data.numberstringЧитаемый номер билета, например PARK-000001.
data.effective_statusstringВычисляемое право на новый въезд: valid, not_paid, not_started, suspended, cancelled, refunded, expired, exhausted.
data.versionintegerВерсия записи CRM для If-Match. Это не source_version источника.
data.access_codestringКод для QR или штрихкода, выданный сервером. Приходит только с правом parking:credentials:read и не передаётся в URL.

Создать парковочный билет

Эндпоинт
POST /integrations/parking/tickets

Создаёт билет, когда внешняя система не ведёт синхронизацию по external_id. Одно право парковки — один билет. Повтор существующего external_id даёт 409: для обновления нужен PUT.

Параметры

ПараметрГдеТипОбязателенОписание
Idempotency-KeyзаголовокuuidдаКлюч идемпотентности; повтор с тем же ключом вернёт исходный ответ.
bodyтелоticket_inputдаТот же состав полей, что и у метода «Занести купленное место».
Запрос
curl -X POST "https://api.virazh.io/v1/integrations/parking/tickets" \
  -H "Authorization: Bearer sk_test_51H..." \
  -H "Idempotency-Key: 40000000-0000-4000-8000-000000000011" \
  -H "Content-Type: application/json" \
  -d '{
    "external_id": "parking-ticket-10002",
    "source_version": 1,
    "external_order_id": "order-5001",
    "external_order_line_id": "line-2",
    "parking_lot_id": "10000000-0000-4000-8000-000000000001",
    "zone_id": "20000000-0000-4000-8000-000000000001",
    "valid_from": "2026-10-20T16:00:00+03:00",
    "valid_until": "2026-10-20T23:00:00+03:00",
    "max_entries": null,
    "payment": {
      "status": "paid",
      "amount_minor": 150000,
      "currency": "RUB",
      "paid_minor": 150000,
      "refunded_minor": 0,
      "paid_at": "2026-10-15T14:30:00+03:00"
    }
  }'
Ответ
{
  "data": {
    "id": "20000000-0000-4000-8000-000000000002",
    "number": "PARK-000002",
    "status": "active",
    "effective_status": "valid",
    "occupancy_status": "outside",
    "entries_used": 0,
    "entries_remaining": null,
    "version": 1
  }
}

Поля ответа

ПолеТипОписание
dataTicketСозданный билет, 201. Повтор того же ключа — 200 с исходным телом.

Список билетов

Эндпоинт
GET /integrations/parking/tickets

Поиск билетов и выборка изменений для сверки. Сортировка по updated_at и id, граница updated_since включительна — дедуплицируйте по id и version.

Параметры

ПараметрГдеТипОбязателенОписание
external_idquerystringнетБилет по ID источника.
external_order_idquerystringнетВсе парковочные билеты одного заказа источника.
crm_user_idqueryintegerнетБилеты клиента CRM.
parking_lot_idqueryuuidнетБилеты одной парковки.
event_idqueryintegerнетБилеты, относящиеся к мероприятию CRM.
statusquerystringнетУправляемый статус: active, suspended, cancelled.
effective_statusquerystringнетВычисляемое состояние, например expired или exhausted.
plate_numberquerystringнетГосномер; передаётся только вместе с country_code.
updated_sincequerydatetimeнетИзменённые с указанного момента, граница включительна.
limitqueryintegerнетРазмер страницы, от 1 до 100. По умолчанию — 50.
Запрос
curl "https://api.virazh.io/v1/integrations/parking/tickets?external_order_id=order-5001&limit=50" \
  -H "Authorization: Bearer sk_test_51H..."
Ответ
{
  "data": [
    {
      "id": "20000000-...-0001",
      "number": "PARK-000001",
      "external_id": "parking-ticket-10001",
      "status": "active",
      "effective_status": "exhausted",
      "occupancy_status": "inside",
      "entries_used": 1,
      "entries_remaining": 0,
      "updated_at": "2026-10-20T14:42:11Z",
      "version": 2
    }
  ],
  "meta": { "next_cursor": null, "has_more": false }
}

Поля ответа

ПолеТипОписание
dataarray<Ticket>Билеты с полным составом полей карточки.
meta.has_morebooleanЕсть ли следующая страница.

Карточка билета

Эндпоинт
GET /integrations/parking/tickets/{ticket_id}

Билет целиком: покупка, оплата, автомобиль, место, счётчики въездов и время последнего въезда и выезда.

Параметры

ПараметрГдеТипОбязателенОписание
ticket_idпутьuuidдаID билета в CRM.
Запрос
curl "https://api.virazh.io/v1/integrations/parking/tickets/20000000-0000-4000-8000-000000000001" \
  -H "Authorization: Bearer sk_test_51H..."
Ответ
{
  "data": {
    "id": "20000000-0000-4000-8000-000000000001",
    "number": "PARK-000001",
    "status": "active",
    "effective_status": "exhausted",
    "occupancy_status": "inside",
    "entries_used": 1,
    "entries_remaining": 0,
    "last_entry_at": "2026-10-20T14:42:10Z",
    "last_exit_at": null,
    "payment": {
      "status": "paid",
      "amount_minor": 150000,
      "currency": "RUB",
      "paid_minor": 150000,
      "refunded_minor": 0
    },
    "vehicle": { "plate_number": "А123АА777", "country_code": "RU" },
    "status_reason": null,
    "version": 2
  }
}

Поля ответа

ПолеТипОписание
data.occupancy_statusstringПоложение автомобиля по подтверждённым проездам: outside, inside, unknown.
data.entries_usedintegerКоличество уникальных подтверждённых въездов.
data.last_entry_atdatetime | nullПоследний подтверждённый въезд.
data.status_reasonstring | nullПричина последнего изменения управляемого статуса — текст из suspend, resume или cancel.

Компактный статус билета

Эндпоинт
GET /integrations/parking/tickets/{ticket_id}/status

Короткий ответ для интерфейса оператора и регулярной сверки: состояние, оплата, положение автомобиля и счётчики. Поле as_of — время расчёта, version — значение для If-Match.

Параметры

ПараметрГдеТипОбязателенОписание
ticket_idпутьuuidдаID билета в CRM.
Запрос
curl "https://api.virazh.io/v1/integrations/parking/tickets/20000000-0000-4000-8000-000000000001/status" \
  -H "Authorization: Bearer sk_test_51H..."
Ответ
{
  "data": {
    "ticket_id": "20000000-0000-4000-8000-000000000001",
    "status": "active",
    "effective_status": "exhausted",
    "payment_status": "paid",
    "occupancy_status": "inside",
    "entries_used": 1,
    "entries_remaining": 0,
    "last_entry_at": "2026-10-20T14:42:10Z",
    "last_exit_at": null,
    "as_of": "2026-10-20T14:42:11Z",
    "version": 2
  }
}

Поля ответа

ПолеТипОписание
data.effective_statusstringВычисляемое право на новый въезд на момент as_of.
data.as_ofdatetimeМомент, на который посчитано состояние.
data.versionintegerТекущая версия записи CRM для последующих изменяющих вызовов.

Исправить билет

Эндпоинт
PATCH /integrations/parking/tickets/{ticket_id}

Правка автомобиля, срока действия или комментария без повторного создания покупки. Место, парковка, оплата, source_version и счётчики здесь не меняются; нужен хотя бы один параметр.

Параметры

ПараметрГдеТипОбязателенОписание
ticket_idпутьuuidдаID билета в CRM.
If-MatchзаголовокstringдаТекущее значение version в кавычках, например "3". Без заголовка — 428 VERSION_REQUIRED, при расхождении — 412 VERSION_CONFLICT.
Idempotency-KeyзаголовокuuidдаКлюч идемпотентности запроса.
vehicleтелоobjectнетНовый автомобиль. Смена запрещена, пока occupancy_status равен inside или unknown — сначала нужна сверка истории проездов.
valid_fromтелоdatetimeнетНовое начало действия; период заново проверяется на пересечение брони.
valid_untilтелоdatetimeнетНовый конец действия.
noteтелоstring | nullнетКомментарий до 1000 символов; null очищает его.
Запрос
curl -X PATCH "https://api.virazh.io/v1/integrations/parking/tickets/20000000-0000-4000-8000-000000000001" \
  -H "Authorization: Bearer sk_test_51H..." \
  -H "Idempotency-Key: 40000000-0000-4000-8000-000000000012" \
  -H 'If-Match: "2"' \
  -H "Content-Type: application/json" \
  -d '{
    "vehicle": { "plate_number": "В456ВВ777", "country_code": "RU" },
    "note": "Покупатель приедет на другой машине"
  }'
Ответ
{
  "data": {
    "id": "20000000-0000-4000-8000-000000000001",
    "vehicle": { "plate_number": "В456ВВ777", "country_code": "RU" },
    "note": "Покупатель приедет на другой машине",
    "status": "active",
    "effective_status": "valid",
    "version": 3
  }
}

Поля ответа

ПолеТипОписание
dataTicketБилет после правки с увеличенным version.

Заблокировать билет

Эндпоинт
POST /integrations/parking/tickets/{ticket_id}/suspend

Временно запрещает новые въезды: active → suspended, повтор идемпотентен. Автомобиль внутри, история выездов и бронь места сохраняются.

Параметры

ПараметрГдеТипОбязателенОписание
ticket_idпутьuuidдаID билета в CRM.
If-MatchзаголовокstringдаТекущее значение version в кавычках.
Idempotency-KeyзаголовокuuidдаКлюч идемпотентности запроса.
reasonтелоstringдаПричина блокировки, от 1 до 1000 символов. Попадает в status_reason и в журнал.
Запрос
curl -X POST "https://api.virazh.io/v1/integrations/parking/tickets/20000000-0000-4000-8000-000000000001/suspend" \
  -H "Authorization: Bearer sk_test_51H..." \
  -H "Idempotency-Key: 40000000-0000-4000-8000-000000000013" \
  -H 'If-Match: "3"' \
  -H "Content-Type: application/json" \
  -d '{ "reason": "Спор по оплате заказа" }'
Ответ
{
  "data": {
    "id": "20000000-0000-4000-8000-000000000001",
    "status": "suspended",
    "effective_status": "suspended",
    "suspended_at": "2026-10-18T09:12:40Z",
    "status_reason": "Спор по оплате заказа",
    "version": 4
  }
}

Поля ответа

ПолеТипОписание
data.statusstringСтановится suspended.
data.suspended_atdatetimeВремя блокировки.

Снять блокировку

Эндпоинт
POST /integrations/parking/tickets/{ticket_id}/resume

Возвращает билет в работу: suspended → active, повтор идемпотентен. Отмену, неоплату, истёкший срок и исчерпанный лимит метод не снимает — они пересчитываются в effective_status.

Параметры

ПараметрГдеТипОбязателенОписание
ticket_idпутьuuidдаID билета в CRM.
If-MatchзаголовокstringдаТекущее значение version в кавычках.
Idempotency-KeyзаголовокuuidдаКлюч идемпотентности запроса.
reasonтелоstringдаПричина снятия блокировки, от 1 до 1000 символов.
Запрос
curl -X POST "https://api.virazh.io/v1/integrations/parking/tickets/20000000-0000-4000-8000-000000000001/resume" \
  -H "Authorization: Bearer sk_test_51H..." \
  -H "Idempotency-Key: 40000000-0000-4000-8000-000000000014" \
  -H 'If-Match: "4"' \
  -H "Content-Type: application/json" \
  -d '{ "reason": "Оплата подтверждена" }'
Ответ
{
  "data": {
    "id": "20000000-0000-4000-8000-000000000001",
    "status": "active",
    "effective_status": "valid",
    "suspended_at": null,
    "status_reason": "Оплата подтверждена",
    "version": 5
  }
}

Поля ответа

ПолеТипОписание
data.statusstringСтановится active.
data.effective_statusstringПересчитывается заново: билет с истёкшим сроком вернётся как expired, а не valid.

Отменить билет

Эндпоинт
POST /integrations/parking/tickets/{ticket_id}/cancel

Снимает право на новые въезды, сохраняя покупку и историю: active или suspended → cancelled, восстановление не предусмотрено. Возврат денег это не выполняет: суммы возврата приходят новым снимком через PUT.

Параметры

ПараметрГдеТипОбязателенОписание
ticket_idпутьuuidдаID билета в CRM.
If-MatchзаголовокstringдаТекущее значение version в кавычках.
Idempotency-KeyзаголовокuuidдаКлюч идемпотентности запроса.
reasonтелоstringдаПричина отмены, от 1 до 1000 символов.
Запрос
curl -X POST "https://api.virazh.io/v1/integrations/parking/tickets/20000000-0000-4000-8000-000000000001/cancel" \
  -H "Authorization: Bearer sk_test_51H..." \
  -H "Idempotency-Key: 40000000-0000-4000-8000-000000000004" \
  -H 'If-Match: "2"' \
  -H "Content-Type: application/json" \
  -d '{ "reason": "Покупатель отменил парковку" }'
Ответ
{
  "data": {
    "id": "20000000-0000-4000-8000-000000000001",
    "status": "cancelled",
    "effective_status": "cancelled",
    "cancelled_at": "2026-10-19T10:04:11Z",
    "status_reason": "Покупатель отменил парковку",
    "version": 3
  }
}

Поля ответа

ПолеТипОписание
data.statusstringСтановится cancelled, обратного перехода нет.
data.cancelled_atdatetimeВремя отмены.

Проверить доступ

Эндпоинт
POST /integrations/parking/access/check

Проверяет право на въезд или выезд прямо сейчас, не записывая проезд и не открывая шлагбаум. Отказ приходит как 200 с allowed=false и причиной в reason_code, а не как ошибка; время берётся на сервере.

Параметры

ПараметрГдеТипОбязателенОписание
parking_lot_idтелоuuidдаПарковка, на которой стоит точка проезда.
gate_idтелоuuidдаТочка проезда; её направление должно допускать указанный direction.
directionтелоstringдаentry или exit.
ticket_idтелоuuidнетБилет CRM.
external_ticket_idтелоstringнетБилет по ID источника.
access_codeтелоstringнетКод с QR или штрихкода. Передаётся только в теле запроса — не в query-строке и не в пути.
vehicleтелоobjectнетРаспознанный автомобиль. Нужен хотя бы один идентификатор из четырёх; если их несколько, все должны указывать на один билет, иначе 409 IDENTITY_CONFLICT.
Запрос
curl -X POST "https://api.virazh.io/v1/integrations/parking/access/check" \
  -H "Authorization: Bearer sk_test_51H..." \
  -H "Content-Type: application/json" \
  -d '{
    "parking_lot_id": "10000000-0000-4000-8000-000000000001",
    "gate_id": "30000000-0000-4000-8000-000000000002",
    "direction": "exit",
    "ticket_id": "20000000-0000-4000-8000-000000000001"
  }'
Ответ
{
  "data": {
    "allowed": true,
    "reason_code": null,
    "ticket_id": "20000000-0000-4000-8000-000000000001",
    "effective_status": "exhausted",
    "occupancy_status": "inside",
    "entries_remaining": 0,
    "checked_at": "2026-10-20T18:17:59Z"
  }
}

Поля ответа

ПолеТипОписание
data.allowedbooleanРазрешён ли проезд в указанном направлении прямо сейчас.
data.reason_codestring | nullПричина отказа при allowed=false, например TICKET_EXPIRED или ALREADY_INSIDE.
data.entries_remaininginteger | nullСколько въездов осталось; null при безлимитном билете.
data.checked_atdatetimeСерверное время проверки.

Передать проезд

Эндпоинт
POST /integrations/parking/passages

Заносит фактический въезд, выезд или отказ. Событие уникально по паре «источник + external_id»: повторная доставка вернёт 200 с duplicate=true и не расходует второй въезд, а тот же ID с другим телом — 409.

Параметры

ПараметрГдеТипОбязателенОписание
Idempotency-KeyзаголовокuuidдаКлюч идемпотентности запроса.
external_idтелоstringдаID события у источника, одинаковый при повторной доставке одного и того же проезда.
parking_lot_idтелоuuidдаПарковка, где произошло событие.
gate_idтелоuuidдаТочка проезда из справочника этой парковки.
directionтелоstringдаentry или exit.
resultтелоstringдаpassed — подтверждённое пересечение, denied — отказ. Удачное чтение QR само по себе проездом не считается.
reason_codeтелоstringнетПричина отказа; обязательна при result=denied.
occurred_atтелоdatetimeдаФактическое время события. Больше чем на 5 минут в будущее — 422 EVENT_TIME_IN_FUTURE; прошедшие события принимаются с пересчётом истории.
verification_methodтелоstringдаСпособ идентификации: qr, barcode, plate, manual, unknown.
ticket_idтелоuuidнетБилет CRM.
external_ticket_idтелоstringнетБилет по ID источника — можно передать раньше, чем поступит сам билет.
vehicleтелоobjectнетРаспознанный автомобиль. Нужен хотя бы один из трёх: ticket_id, external_ticket_id или vehicle.
device_idтелоstringнетID контроллера или устройства.
device_sequenceтелоintegerнетПорядковый номер события контроллера — по нему разбирается порядок событий с одинаковым временем.
Запрос
curl -X POST "https://api.virazh.io/v1/integrations/parking/passages" \
  -H "Authorization: Bearer sk_test_51H..." \
  -H "Idempotency-Key: 40000000-0000-4000-8000-000000000002" \
  -H "Content-Type: application/json" \
  -d '{
    "external_id": "gate-event-7001",
    "parking_lot_id": "10000000-0000-4000-8000-000000000001",
    "gate_id": "30000000-0000-4000-8000-000000000001",
    "external_ticket_id": "parking-ticket-10001",
    "vehicle": { "plate_number": "А123АА777", "country_code": "RU" },
    "direction": "entry",
    "result": "passed",
    "occurred_at": "2026-10-20T17:42:10+03:00",
    "device_id": "gate-controller-1",
    "device_sequence": 7001,
    "verification_method": "plate"
  }'
Ответ
{
  "data": {
    "id": "50000000-0000-4000-8000-000000000001",
    "external_id": "gate-event-7001",
    "ticket_id": "20000000-0000-4000-8000-000000000001",
    "link_status": "linked",
    "reconciliation_status": "applied",
    "direction": "entry",
    "result": "passed",
    "occurred_at": "2026-10-20T14:42:10Z",
    "received_at": "2026-10-20T14:42:11Z",
    "duplicate": false
  }
}

Поля ответа

ПолеТипОписание
data.link_statusstringРезультат связывания с билетом: linked, unmatched — билет пока не поступил, ambiguous — подходит несколько.
data.reconciliation_statusstringСостояние пересчёта истории: applied, pending, needs_review при аномалии в последовательности.
data.duplicatebooleantrue, если событие с таким external_id уже было получено ранее.
data.received_atdatetimeКогда событие приняла платформа; occurred_at оно не заменяет.

Передать пачку проездов

Эндпоинт
POST /integrations/parking/passages/batch

До 500 событий за раз, включая накопленные без связи. Каждый элемент идёт в своей транзакции по порядку: ошибка одного не откатывает остальные, исправленные элементы досылаются новой пачкой с новым ключом.

Параметры

ПараметрГдеТипОбязателенОписание
Idempotency-KeyзаголовокuuidдаКлюч идемпотентности пачки.
itemsтелоarray<passage_input>даОт 1 до 500 событий того же состава, что и в одиночном методе. Размер тела — до 2 МБ, иначе 413 PAYLOAD_TOO_LARGE.
Запрос
curl -X POST "https://api.virazh.io/v1/integrations/parking/passages/batch" \
  -H "Authorization: Bearer sk_test_51H..." \
  -H "Idempotency-Key: 40000000-0000-4000-8000-000000000021" \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      {
        "external_id": "gate-event-7002",
        "parking_lot_id": "10000000-0000-4000-8000-000000000001",
        "gate_id": "30000000-0000-4000-8000-000000000002",
        "external_ticket_id": "parking-ticket-10001",
        "direction": "exit",
        "result": "passed",
        "occurred_at": "2026-10-20T21:18:00+03:00",
        "verification_method": "plate"
      }
    ]
  }'
Ответ
{
  "data": {
    "received": 2,
    "created": 1,
    "duplicates": 1,
    "errors": 0,
    "results": [
      {
        "index": 0,
        "external_id": "gate-event-7002",
        "http_status": 201,
        "data": { "id": "50000000-...-0002", "link_status": "linked" },
        "error": null
      },
      {
        "index": 1,
        "external_id": "gate-event-7001",
        "http_status": 200,
        "data": { "id": "50000000-...-0001", "duplicate": true },
        "error": null
      }
    ]
  }
}

Поля ответа

ПолеТипОписание
data.receivedintegerСколько элементов пришло: received = created + duplicates + errors.
data.createdintegerСколько событий сохранено впервые.
data.duplicatesintegerСколько событий уже было получено ранее.
data.resultsarray<Result>Ровно один элемент на каждый элемент запроса, с сохранением его index: http_status, data и error.

История проездов

Эндпоинт
GET /integrations/parking/passages

Въезды, выезды и отказы, включая не связанные с билетом. Сортировка по received_at и id: опоздавшие события догружаются через received_since, а не occurred_from.

Параметры

ПараметрГдеТипОбязателенОписание
ticket_idqueryuuidнетПроезды одного билета.
parking_lot_idqueryuuidнетПроезды одной парковки.
gate_idqueryuuidнетПроезды одной точки.
directionquerystringнетentry или exit.
resultquerystringнетpassed или denied.
link_statusquerystringнетlinked, unmatched или ambiguous — так находят события, требующие разбора.
occurred_fromquerydatetimeнетНижняя граница времени события, включительно.
occurred_toquerydatetimeнетВерхняя граница времени события, исключительно.
received_sincequerydatetimeнетСобытия, принятые платформой с указанного момента — включая те, что произошли раньше и пришли с опозданием.
limitqueryintegerнетРазмер страницы, от 1 до 100. По умолчанию — 50.
Запрос
curl "https://api.virazh.io/v1/integrations/parking/passages?link_status=unmatched&limit=50" \
  -H "Authorization: Bearer sk_test_51H..."
Ответ
{
  "data": [
    {
      "id": "50000000-...-0003",
      "external_id": "gate-event-7003",
      "ticket_id": null,
      "link_status": "unmatched",
      "reconciliation_status": "pending",
      "direction": "entry",
      "result": "passed",
      "vehicle": { "plate_number": "С789СС777", "country_code": "RU" },
      "occurred_at": "2026-10-20T15:02:40Z",
      "received_at": "2026-10-20T15:03:01Z",
      "duplicate": false
    }
  ],
  "meta": { "next_cursor": null, "has_more": false }
}

Поля ответа

ПолеТипОписание
dataarray<Passage>События с направлением, результатом, точкой, автомобилем и билетом.
meta.next_cursorstring | nullКурсор следующей страницы.

Состояние билета

status — управляемое состояние, его меняют только suspend, resume и cancel. effective_status — право на новый въезд: считается сервером, первое выполнившееся условие и даёт результат.

СостояниеПриоритетУсловие
cancelled1status = cancelled
refunded2payment.status = refunded
suspended3status = suspended
not_paid4payment.status не paid и не partially_refunded
not_started5now < valid_from
expired6now ≥ valid_until
exhausted7max_entries задан и entries_used ≥ max_entries
valid8Ни одно условие выше не выполняется

Въезд и выезд

СитуацияРешение
ВъездРазрешён при effective_status = valid и occupancy_status = outside
Въезд, автомобиль внутриОтказ ALREADY_INSIDE
Выезд, автомобиль внутриРазрешён всегда: срок, лимит, оплата и отмена не мешают
Выезд, outside или unknownАвтоматический отказ, нужен оператор
max_entries = 1Один въезд и последующий выезд
max_entries > 1 или nullВъезды повторяются после выездов, внутри один автомобиль
Точка проездаДолжна относиться к той же парковке и допускать направление
Нумерованное местоПересечение периодов двух оплаченных билетов запрещено
Зона без номеровПроверяется вместимость на весь период
Блокировка билетаБронь места не освобождает
Отмена и полный возвратОсвобождают будущую бронь, но не занятое место

Сверка проездов

СобытиеЧто делает платформа
passed + entryРасходует въезд, переводит в inside
passed + exitПереводит в outside
deniedНе расходует въезд и не меняет занятость
Повторная доставка200, duplicate = true, счётчики не меняются
Тот же external_id, другое тело409: история не переписывается
Проезд раньше билетаlink_status = unmatched, связывается после импорта билета
Опоздавшее событиеПринимается, идёт пересчёт: pending и STATE_RECONCILING в проверке
Невозможная последовательностьoccupancy_status = unknown, needs_review; проезд не достраивается
Только госномерСвязывается при единственном кандидате, иначе unmatched или ambiguous
Разные ticket_id и external_ticket_id409 IDENTITY_CONFLICT
occurred_at в будущемБольше 5 минут — 422 EVENT_TIME_IN_FUTURE

Коды причин

Приходят в reason_code: в ответе /access/check при allowed: false и в проезде с result: denied, где поле обязательно.

КодНаправлениеЧто означает
TICKET_NOT_FOUNDвъезд и выездБилет не найден
TICKET_NOT_PAIDвъездБилет не оплачен
TICKET_NOT_STARTEDвъездПериод действия ещё не начался
TICKET_EXPIREDвъездПериод действия закончился
TICKET_CANCELLEDвъездБилет отменён
TICKET_SUSPENDEDвъездБилет заблокирован
TICKET_REFUNDEDвъездОплата возвращена полностью
ENTRY_LIMIT_REACHEDвъездЛимит въездов исчерпан
ALREADY_INSIDEвъездАвтомобиль по этому билету уже внутри
NOT_INSIDEвыездНет подтверждения, что автомобиль внутри
UNKNOWN_OCCUPANCYвъезд и выездНеоднозначная история проездов
STATE_RECONCILINGвъезд и выездИстория пересчитывается
PARKING_CLOSEDвъездПарковка закрыта для въезда
SPACE_UNAVAILABLEвъездМесто недоступно
WRONG_PARKINGвъезд и выездБилет на другую парковку
WRONG_GATE_DIRECTIONвъезд и выездТочка не допускает это направление
PLATE_MISMATCHвъездАвтомобиль не совпадает с билетом
AMBIGUOUS_TICKETвъезд и выездПодходит несколько билетов

Ошибки раздела

Тело ошибки — { "error": { "code", "message", "details", "request_id" } }. Логируйте request_id: с ним разбор обращения занимает минуты.

СтатусКодКогда возникает
409EXTERNAL_ID_CONFLICTБилет с таким external_id уже есть, нужен PUT
409STALE_SOURCE_VERSIONСнимок старее сохранённого
409SOURCE_VERSION_CONFLICTТа же source_version, другой состав полей
409SPACE_CONFLICTМесто занято другой покупкой в этом периоде
409CAPACITY_EXCEEDEDИсчерпана вместимость зоны или парковки
409IDEMPOTENCY_CONFLICTТот же Idempotency-Key с другим телом
409IDENTITY_CONFLICTИдентификаторы указывают на разные билеты
409EVENT_CONFLICTСобытие с таким external_id принято с другим телом
409INVALID_STATEПереход статуса недопустим
412VERSION_CONFLICTIf-Match не совпал, перечитайте билет
413PAYLOAD_TOO_LARGEПачка проездов больше 2 МБ
422EVENT_TIME_IN_FUTUREoccurred_at дальше 5 минут в будущем
422INVALID_BATCHПачка пустая, больше 500 элементов или неверной структуры
428VERSION_REQUIREDНе передан If-Match

Что появляется в CRM

Раздел CRMЧто появляется
КлиентСвязь по crm_user_id или customer.external_id; профиль создаётся при достаточных контактах
ЗаказПозиция типа «парковка»; повторный импорт обновляет её, сумма учитывается один раз
БилетОтдельная сущность со ссылками на заказ, клиента и мероприятие
ПроходыТип «парковка» с фильтром по направлению; denied показан отказом
АналитикаВыручка парковки отдельной категорией; проезды не идут в посещаемость арены

Людей не объединяют автоматически по госномеру или совпадению имени, согласия на рассылки парковка не меняет.

Полезно знать

  • Основной метод интеграции — PUT /tickets/external/{external_id}: при повторной выгрузке заказа он обновит запись, а POST /tickets вернёт 409.
  • Три купленных места — это три билета с разными external_id и external_order_line_id, а не один билет с количеством.
  • Проезд передаётся по факту пересечения: успешное чтение QR-кода проездом не считается.
  • Для сверки берите received_since у проездов и updated_since у билетов — иначе опоздавшие события пройдут мимо выборки.
  • effective_status: exhausted при occupancy_status: inside — не ошибка, а обычное состояние разового билета с машиной на парковке.