Партнёрский API продажи билетов
Руководство по интеграции для разработчиков партнёра: как подписывать запросы, в каком порядке звать ручки, что означает каждый статус и код ошибки, и как пройти сертификацию на стенде.
Этот документ и OpenAPI-файл описывают одно и то же. При расхождении по форме полей побеждает OpenAPI-файл; этот текст объясняет, почему поля устроены именно так и что с ними делать.
Как это работает
Партнёр продаёт билеты на мероприятия ДСС у себя. Мероприятия, зал и статусы мест остаются на стороне ДСС; партнёр получает доступ к живому инвентарю и проводит оплату самостоятельно. Пять утверждений, на которых стоит всё остальное:
- Живой инвентарь, а не квота. Вы работаете с теми же местами, что и витрина ДСС. Место, проданное на витрине, исчезает у вас немедленно, и наоборот. Заранее выделенных пачек мест не существует; ДСС остаётся мастер-системой статусов.
- Доступ выдаётся на мероприятие. Вы видите и продаёте только то, на что ДСС выдала грант. Список меняется на стороне ДСС без правок вашего кода.
- Деньги у вас. Вы принимаете оплату и сами фискализируете чек. Платёжный шлюз ДСС в этом потоке не участвует;
payment_infoв подтверждении заказа это факт вашего платежа, который ДСС хранит для сверки. Взаиморасчёт между ДСС и партнёром периодический и идёт вне API. - Заказ раньше денег, билеты после подтверждения. Заказ запирает места платёжным окном до списания. После списания вы зовёте
confirmи получаете билеты: подписанныйqr_tokenиpdf_urlна каждый. Писем покупателю ДСС не отправляет, доставка на вашей стороне. - Возврат денег делаете вы. ДСС гасит билеты, погашенный не проходит на вход. В первой фазе возврат оформляется обращением к персоналу ДСС, и место после него в продажу не возвращается; освобождение мест появится вместе с ручкой
refundво второй фазе.
Списывать деньги раньше ответа 201 на POST /orders запрещено. Заказ и есть гарантия места: до него бронь может истечь, и получится «деньги списаны, места нет». Сверка проверяет, что payment_info.paid_at не раньше created_at заказа; нарушение не отклоняется, но считается. И не списывайте позже, чем за 30 секунд до expires_at: льготы за границей окна нет ни у чтения, ни у подтверждения.
Заказ, не получивший ни confirm, ни cancel, истекает сам и возвращает места в продажу. Подвисших билетов без денег не бывает по построению.
Если у вас на руках ТЗ СКУП ДСС
Контракт следует разделу 4.2.14 технического задания заказчика и отличается от него в шести местах, каждое ради покупателя или денег: места отдаются посекторно, а не всем залом одним ответом; успешный ответ обёрнут в {"data": …}; ключи идемпотентности partner_booking_id и partner_order_id обязательны; формула подписи зафиксирована проверочным вектором, а не словами; заказ создаётся до списания, а оплата подтверждается отдельным вызовом; гарантированный канал отмены это опрос, вебхуки подключаются позже как ускоритель.
Быстрый старт
- Стенд
https://api-sandbox.dss.ndsvs.kz, только HTTPS; ключpk_sandbox_…и секрет к нему ДСС выдаёт техническому контакту вместе с датой сертификационной сессии (часть сценариев требует оператора ДСС на связи, дата согласуется отдельно)- Продуктив
https://api-dss.ndsvs.kz/api/v1/partners, ключиpk_live_…после сертификации- Транспорт
- HTTPS, TLS 1.2 и выше; JSON в UTF-8; имена полей
snake_case - Заголовки
X-Api-Key,X-Timestamp,X-Signatureна каждом запросе;Content-Type: application/jsonна POST
Подпись запроса
Каждый запрос подписывается HMAC-SHA256. Строка для подписи собирается из четырёх частей, разделённых одним символом перевода строки (байт 0x0A):
<timestamp>\n<METHOD>\n<uri>\n<body>| Часть | Что подставлять |
|---|---|
timestamp | Unix-время в секундах, десять цифр, то же значение, что в X-Timestamp. Окно валидности пять минут в обе стороны. |
METHOD | GET или POST, заглавными. |
uri | Полный путь с query-строкой ровно как он уходит на провод, включая префикс /api/v1/partners, без хоста. Параметры не пересортировываются. |
body | Тело запроса байт в байт. Для GET и для POST без тела (cancel) пустая строка, но разделитель \n перед ней остаётся; Content-Type у запроса без тела не требуется. |
Подпись: HMAC-SHA256(key = secret_key, message = canonical), результат в нижнем регистре hex. Секрет это ключ HMAC, а не часть сообщения.
import hmac, hashlib, json, time
def headers(api_key: str, secret: str, method: str, uri: str, body: bytes = b"") -> dict:
ts = str(int(time.time())) # секунды, не миллисекунды
canonical = f"{ts}\n{method.upper()}\n{uri}\n".encode() + body
sig = hmac.new(secret.encode(), canonical, hashlib.sha256).hexdigest()
return {"X-Api-Key": api_key, "X-Timestamp": ts, "X-Signature": sig}
# тело сериализуется ОДИН раз, и отправляются ровно эти байты
body = json.dumps(payload, ensure_ascii=False, separators=(",", ":")).encode()X-Timestamp в миллисекундах. Тринадцать цифр окажутся вне пятиминутного окна, и ответ будет INVALID_SIGNATURE, хотя подпись посчитана верно.
Тело сериализовано дважды. Подписали одну строку, а HTTP-клиент пересобрал JSON с другими пробелами или порядком ключей. Подписывайте те же байты, что отправляете.
Литеральный \n. В примерах ниже перевод строки показан как \n ради читаемости. Скопированные два символа «обратный слэш и n» дадут другую подпись.
Отсутствующий X-Api-Key отвечает 401 INVALID_API_KEY; любая другая проблема аутентификации, включая timestamp вне окна, отвечает 401 INVALID_SIGNATURE, а details.reason различает signature_mismatch и timestamp_out_of_window, чтобы чинить часы, а не формулу. Отдельного nonce нет намеренно: каждый изменяющий вызов идемпотентен по вашим ключам (Идемпотентность), поэтому повтор запроса внутри окна воспроизводит тот же результат и нового эффекта не создаёт.
Проверочный вектор
Сойдитесь с ним до первого запроса на стенд: расхождение здесь стоит минуты, расхождение на стенде стоит дня переписки.
- secret
sandbox_secret_do_not_use_in_prod- X-Timestamp
1790841600
1790841600\nPOST\n/api/v1/partners/bookings\n{"event_id":32,"partner_booking_id":"TKT-BKG-2026-0009871","seats":["seat-AA-A-03-005","seat-AA-A-03-006"],"customer":{"email":"buyer@ticketon.kz"}}
X-Signature: d2b089654f675c25a6f8e3dacce46f5850c7a5a6c62a8a697fcd92e8fc6c55271790841600\nGET\n/api/v1/partners/events?date_from=2026-10-01&limit=50\n
X-Signature: d783761a531da8c84c1e6fba1c048f5ebc6cf8c9ac66fe43a7cf060dd95616caPOST без тела (cancel)
1790841600\nPOST\n/api/v1/partners/orders/7f0e4c6e-2c3b-4f7a-9a1d-3f4b2c1d0e9a/cancel\n
X-Signature: 9733cdf84a9a9e3f7f430807e8ecd990b072cc7390c3aed08e5f351e032868a6Чеклист перед первым запросом на стенд
- Обе подписи проверочного вектора сошлись на вашей реализации.
X-Timestampдесятизначный, берётся из синхронизированных часов.- Тело сериализуется один раз; подписанные байты и отправленные байты это один буфер.
- Клиент читает
X-Request-Idиз ответа и пишет его в журнал рядом с телом отказа. - Таймаут клиента около 10 секунд, повтор с нарастающей задержкой на 5xx и на сетевых ошибках.
Общие правила
- Успех
{"data": …}; списки дополнительно несут{"meta": {"total", "limit", "offset"}}- Ошибка
{"error": {"code", "message", "details"}}; ветвиться поcode,messageписать в журнал, а не показывать покупателю- Деньги
- целые тенге без копеек;
currencyвсегдаKZT - Время
- ISO 8601 со смещением, например
2026-10-05T19:00:00+05:00; площадки в UTC+5, но смещение в ответах любое, чаще+00:00: разбирайте ISO 8601 целиком, не подстрокой - Идентификаторы
seat-AA-A-03-007,sector-AA-A,row-AA-A-03это непрозрачные строки платформы: не разбирать и не собирать самостоятельно;order_idиticket_idэто UUID-строки, номера видаDSS-2026-001042остаются для переписки с поддержкой- Трассировка
- заголовок
X-Request-Id(строка до 64 символов из латиницы, цифр,_и-; UUID подходит) принимается на любом запросе и возвращается тем же заголовком в каждом ответе, включая ошибки; значение вне формата подменяется серверным; по нему ДСС находит запрос у себя - Пагинация
limitдо 100 (по умолчанию 50),offset; при опросе изменений сupdated_sinceответ полный и пагинация не применяется- Неизвестные поля
- в теле запроса отвергаются (
422 INVALID_PARAMETERS, поле вdetails); в ответах могут появляться без предупреждения, клиент обязан их пропускать
Ограничение частоты
По умолчанию 100 запросов в минуту на партнёра; боевое значение договорное и согласуется под онсейл заранее, как и срок брони с платёжным окном. Каждый ответ несёт X-RateLimit-Limit, X-RateLimit-Remaining и X-RateLimit-Reset (Unix-секунды). Окно фиксированное, одна минута; X-RateLimit-Reset это начало следующего окна. При превышении 429 RATE_LIMIT_EXCEEDED с заголовком Retry-After; договорной потолок одновременных запросов отвечает тем же кодом.
Рекомендуемая каденция чтений: сводку секторов не чаще раза в 10 секунд на мероприятие; места сектора запрашивать, когда пользователь открывает сектор, а не фоном по всем секторам. Доступность в чтениях справочная, истина только за успешной бронью, поэтому частый опрос точности не добавляет.
Перед приложением стоит общая защита инфраструктуры. Её 429 может прийти без тела нашего формата и без заголовков X-RateLimit-*. То же касается 5xx: тело может быть вне формата {"error": …}. Ветвитесь по HTTP-статусу, повторяйте с нарастающей задержкой; все изменяющие вызовы идемпотентны, повтор безопасен.
Справочник ручек
Девять операций. Восемь доступны в первой фазе, refund появляется во второй. Все пути относительно префикса /api/v1/partners. У любой операции возможны 401 (аутентификация) и 429 (лимит), в таблицах ниже они не повторяются; 403 (ACCESS_DENIED, PARTNER_DISABLED) перечислен там, где он возможен.
| Операция | Зачем |
|---|---|
GET /events | что можно продавать; с updated_since это фид изменений |
GET /events/{event_id} | карточка одного мероприятия |
GET /events/{event_id}/seats | сводка по секторам, затем места сектора |
POST /bookings | удержать места на время выбора |
POST /orders | заказ: места заперты платёжным окном |
POST /orders/{order_id}/confirm | оплата прошла, билеты в ответе |
POST /orders/{order_id}/cancel | эквайер отказал, места свободны сразу |
POST /bookings/{booking_id}/release | покупатель передумал, места свободны сразу |
GET /orders?partner_order_id= | восстановление после потери ответа |
POST /orders/{order_id}/refund | отметка возврата, вторая фаза |
/eventsСписок мероприятий и фид изменений
Возвращает мероприятия, на которые у вас есть грант. Без updated_since в выдаче sales_scheduled, sales_open и sales_paused. С updated_since ручка становится фидом изменений: приходят все мероприятия с грантом, изменившиеся после курсора, в том числе cancelled, sales_closed и отозванные (access: "revoked"). Подробно об опросе в разделе Изменения мероприятий.
Параметры запроса
| Параметр | Значение |
|---|---|
updated_since | meta.cursor из предыдущего ответа, как есть: курсор серверный, непрозрачный и URL-безопасный (латиница, цифры, _, -). Не своё время. Первый запрос без параметра. |
date_from, date_to | период по дате начала, YYYY-MM-DD, конец включительно |
venue_id | объект, например almaty-arena |
event_type | тип из словаря |
limit, offset | пагинация; с updated_since игнорируются |
Поля, на которые стоит смотреть: title гарантированно на трёх языках; description и hall_name гарантируют только ru, остальные языки могут отсутствовать, показывайте с откатом на русский. max_seats_per_booking это действующий потолок мест в одной брони именно для этого мероприятия (по умолчанию 8). price_from_tenge это минимальная текущая цена среди свободных и удерживаемых мест, окрашенных платной категорией (у категории бывают периоды «с даты»); null, если таких мест нет, в том числе когда в продаже только бесплатные места.
Ошибки
| Код | Когда |
|---|---|
401INVALID_API_KEY, INVALID_SIGNATURE | аутентификация |
422INVALID_PARAMETERS | неверная дата, неизвестный тип, limit вне диапазона |
429RATE_LIMIT_EXCEEDED | превышена частота |
/events/{event_id}Карточка мероприятия
Текущее состояние одного мероприятия с грантом: обновление вашей страницы между опросами фида. Тело такое же, как элемент списка. Отменённое мероприятие с грантом читается.
Ошибки
| Код | Когда |
|---|---|
403ACCESS_DENIED | мероприятие видно на витрине ДСС, гранта нет |
403PARTNER_DISABLED | доступ партнёра приостановлен ДСС целиком; отличайте от проблемы ключа |
404EVENT_NOT_FOUND | такого event_id нет, либо мероприятие непубличное и гранта на него нет |
/events/{event_id}/seatsДоступность мест
Места отдаются посекторно: зал первого объекта это 960 мест в шести секторах. Ручка работает в двух режимах. Без параметра sector приходит сводка по секторам с минимальной ценой и числом свободных мест; с параметром sector приходят места этого сектора. Схему зала стройте одним запросом сводки плюс по запросу на сектор, который открывает пользователь.
Параметры запроса
| Параметр | Значение |
|---|---|
sector | идентификатор сектора из сводки, например sector-AA-A |
category | фильтр по коду категории билетов мероприятия: C1, C2… (значение из поля category_code мест). Закрытого словаря нет; неизвестный код не даёт ошибку, а возвращает пустой список мест |
status живой: held значит, что место прямо сейчас удерживает кто-то, в том числе покупатель на витрине ДСС. Продавать можно только места с is_sellable: true, это поле учитывает и статус, и назначенную категорию. Место, которому категория не назначена, приходит status: blocked, is_sellable: false, price_tenge: null и не входит в available_count и available_seats. Цена места price_tenge текущая: у категории бывают периоды «с даты», и цена меняется в момент начала периода. Геометрию для отрисовки (SVG-план зала, в котором id элементов совпадают с space_id) можно запросить у технического контакта ДСС.
Ошибки
| Код | Когда |
|---|---|
403ACCESS_DENIED, PARTNER_DISABLED | нет гранта, доступ приостановлен |
404EVENT_NOT_FOUND | мероприятия нет |
422INVALID_PARAMETERS | неизвестный sector (в details.sector) |
Режим сектора
Начиная с 0.5.0 у каждого сектора в сводке есть sale_mode: seat, места выбираются на карте и бронируются полем seats; sector, места выбирает платформа, бронь делается полем sector (см. «Покупка сектором» у POST /bookings). Поле standing отмечает стоячую зону схемы, такой сектор всегда в режиме sector. В categories лежат категории сектора с остатком available_count и текущей ценой price_tenge: из них берётся category_code для брони. У сектора в режиме sector запрос с параметром sector отдаёт пустой список seats. Число available_count самого сектора считает только свободные места, а остаток категории в categories включает и места, у которых срок удержания уже истёк и которые вот-вот будут освобождены, поэтому сумма по категориям может ненадолго оказаться больше числа сектора; для брони по сектору ориентируйтесь на остаток категории.
Схема зала
Начиная с 0.6.0 приложение может нарисовать ту же схему зала, что и сайт продажи. В карточке мероприятия GET /events/{id} есть поле scheme: overview_svg_url (обзор зала) и sectors[] (sector_id и svg_url сидячих секторов); в списке GET /events оно всегда null, а у мероприятия без схемы null и в карточке. В сводке секторов у каждого сектора тоже есть svg_url, у стоячей зоны (отдельного SVG у неё нет) и без схемы это null. Файлы публичные и статические: без подписи и без cookies. В обзорном SVG id элемента равен sector_id, в SVG сектора id элемента равен space_id места. Порядок: раскрасьте сектора обзора по сводке секторов, по нажатию откройте SVG сектора и раскрасьте места по ответу ?sector=. Сектор с sale_mode: "sector" вместо схемы мест открывает выбор количества и категории.
Цвет категории
Начиная с 0.7.0 рядом с category_code и category_name есть category_color: цвет категории строкой #RRGGBB, как хранится (регистр букв не нормализуется). Поле у места в ответе ?sector=, у места брони и у категории в сводке секторов. Это тот же цвет, что в легенде сайта продажи, его для каждой категории выбирает организатор мероприятия. У неокрашенного места (category_code равен null) цвет тоже null; у категории в сводке секторов он задан всегда.
/bookingsВременное бронирование
Удерживает места на срок брони. Захват атомарный: либо удержаны все перечисленные места, либо ни одно. Срок настраивается на партнёра, по умолчанию 3 минуты; действующее значение приходит в expires_at и ttl_seconds, не зашивайте его константой. Бронь не является продажей и ничего не резервирует за покупателем: по истечении срока места освобождаются сами.
Цена фиксируется в момент брони. price_tenge каждого места и total_tenge из ответа наследуются заказом как есть; до брони цены могут меняться, в том числе по началу нового периода цен категории; после брони цена места не меняется, ни заказом, ни подтверждением. Итог покупателю показывайте из ответа брони, а не из закэшированных чтений. Льготных цен в первой фазе нет: каждое место бронируется по полной цене своей категории.
Тело запроса
| Поле | Значение |
|---|---|
event_id | мероприятие с грантом |
partner_booking_id | обязателен: ключ идемпотентности, уникальный в пределах партнёра, до 64 символов |
seats | ровно одно из seats и sector. От 1 до max_seats_per_booking мероприятия; абсолютный потолок схемы 20 действует в первой фазе, групповые продажи сверх него обсуждаются отдельно |
sector | ровно одно из seats и sector, с 0.5.0. Покупка сектором: {sector_id, category_code, quantity}, quantity от 1 до 20, места выбирает платформа |
customer | необязательный, только email-псевдоним; имена и телефоны ДСС не принимает |
Покупка сектором
Сектор с sale_mode: "sector" (фан-зона, стоячая зона, сектор без выбора места) бронируется без называния мест: вы передаёте вместо seats поле sector с кодом сектора из сводки, category_code из его categories и числом билетов quantity. Платформа сама выбирает места, захват атомарный (все quantity или ни одного), выданные места приходят в seats брони теми же полями, что у обычной брони, цена фиксируется в момент брони. Дальше POST /orders и confirm идут как обычно.
Повтор с тем же partner_booking_id и тем же sector возвращает ту же бронь с кодом 200, платформа не выбирает места заново. Тот же ключ с другим sector, либо с seats вместо sector и наоборот, это 409 IDEMPOTENCY_CONFLICT. Когда в секторе меньше мест, чем quantity, отвечает 409 SECTOR_SOLD_OUT с details.available: предложите покупателю это число или обновите сводку. Бронь по местам в секторе режима sector и бронь по сектору в секторе режима seat отвечают 409 SECTOR_MODE_MISMATCH: режим берите из сводки, а не из памяти.
Ошибки
| Код | Когда | Что делать |
|---|---|---|
409SEATS_NOT_AVAILABLE | хотя бы одно место занято, ни одно не удержано; список в details.seats | обновить сектор, предложить другие места |
409SECTOR_SOLD_OUT | бронь по сектору: в секторе меньше мест категории, чем quantity; details.available | предложить меньшее число или обновить сводку |
409SECTOR_MODE_MISMATCH | способ брони не совпал с sale_mode сектора | перечитать сводку секторов и выбрать seats или sector по режиму |
409SALES_PAUSED | продажи по мероприятию приостановлены | показать паузу, читать можно |
409SALES_NOT_OPEN | продажи не открыты, закрыты или мероприятие отменено | снять с продажи до изменения статуса |
409BOOKING_LIMIT_EXCEEDED | мест больше действующего предела; details: {limit, requested} | ограничить корзину по max_seats_per_booking |
409IDEMPOTENCY_CONFLICT | ваш partner_booking_id уже использован с другим телом | новый идентификатор для новой брони |
422SEATS_UNKNOWN | место не существует, принадлежит другому мероприятию или не продаётся (ему не назначена категория); список в details.seats | ошибка на вашей стороне, проверить источник идентификаторов |
422INVALID_PARAMETERS | тело не прошло проверку; оба поля seats и sector сразу или ни одного; для сектора details.reason sector_unknown (сектора нет у мероприятия) или sector_category_unknown (у категории нет мест в секторе); неизвестный category_code даёт details.field: sector.category_code | разбор по полям в details |
| 403404 | ACCESS_DENIED, PARTNER_DISABLED, EVENT_NOT_FOUND | как у чтений |
/bookings/{booking_id}/releaseОсвобождение брони
Возвращает места брони в продажу немедленно, не дожидаясь ttl_seconds. Зовите, когда покупатель ушёл с экрана выбора: иначе места стоят занятыми весь срок брони.
Тела запроса нет: в подписи <body> пустая строка. Идемпотентна: повтор отвечает 200. После освобождения бронь читается со status: "expired", как и по истечении срока. Повтор POST /bookings с тем же partner_booking_id вернёт её в этом состоянии и мест заново не удержит: для новой попытки нужен новый ключ идемпотентности.
Работает после отзыва гранта и при выключенном партнёре: операция только уменьшает удержанное.
Ошибки
| Код | Когда | Что делать |
|---|---|---|
409BOOKING_ALREADY_CONFIRMED | по брони уже создан заказ; его номер в details.order_id | освобождать заказ через POST /orders/{order_id}/cancel |
409BOOKING_NOT_FOUND | брони нет или она не ваша | проверить booking_id из ответа на бронь |
/ordersЗаказ до списания денег
Превращает бронь в заказ со статусом pending. Места остаются за заказом до expires_at: платёжное окно партнёра, по умолчанию 5 минут. Дальше вы списываете деньги и зовёте confirm; если эквайер отказал, зовёте cancel. Если не случилось ни того ни другого, заказ истекает сам. Билеты в этом ответе не приходят, они выпускаются на confirm.
Тело запроса
| Поле | Значение |
|---|---|
booking_id | из ответа брони |
partner_order_id | обязателен, до 64 символов, уникален в пределах партнёра; должен существовать до списания: по нему заказ восстанавливается |
customer.email | обязателен и это псевдоним, уникальный на заказ, под вашим доменом, например order-TKT-2026-0001234@partner.ticketon.kz: по нему заказ находит поддержка ДСС, с покупателем ДСС связывается через вас. Настоящие адреса, имена и телефоны ДСС не принимает и не хранит |
total_tenge в ответе это ровно та сумма, которую вы списываете за билеты. Ваш сервисный сбор с покупателя сверх неё ДСС не видит, не сверяет и не ограничивает.
Ошибки
| Код | Когда | Что делать |
|---|---|---|
409BOOKING_EXPIRED | срок брони истёк | новая бронь, потом новый заказ |
409SALES_PAUSED, SALES_NOT_OPEN | продажи приостановлены или закрыты между бронью и заказом | снять с продажи до изменения статуса |
409BOOKING_NOT_FOUND | такой брони нет или она чужая | проверить booking_id |
409BOOKING_ALREADY_CONFIRMED | по этой брони уже создан заказ с другим partner_order_id | найти его через GET /orders |
409IDEMPOTENCY_CONFLICT | ваш partner_order_id уже использован с другой бронью | новый идентификатор |
403ACCESS_DENIED, PARTNER_DISABLED | грант на мероприятие отозван или доступ приостановлен после брони | остановить продажу по мероприятию |
429PENDING_LIMIT_EXCEEDED | слишком много незакрытых pending-заказов | повторить после Retry-After; закрывать заказы confirm или cancel, не бросать |
422INVALID_PARAMETERS | например пустой email, лишнее поле или место без цены (details.reason: price_not_set) | разбор по полям в details |
/orders/{order_id}/confirmОплата подтверждена, выпуск билетов
Зовётся сразу после успешного списания, не из отложенной очереди. payment_info описывает ваш платёж и сохраняется у ДСС для сверки. В ответе билеты: qr_token для турникета и pdf_url для скачивания. Повтор по уже подтверждённому заказу возвращает 200 с теми же билетами; первый принятый payment_info при повторе не перезаписывается и возвращается в ответе, так что расхождение видно.
Выпуск билетов может отстать от принятия оплаты на срок до одной минуты: заказ уже paid, а tickets пуст или короче числа мест. Это не отказ. Перечитывайте GET /orders?partner_order_id= до полного списка и не возвращайте деньги по пустому tickets.
Порядок отказов: сначала отмена мероприятия (EVENT_CANCELLED), затем платёжное окно (ORDER_EXPIRED по expires_at, даже если места ещё не освобождены внутренней уборкой), затем статус заказа (ORDER_CANCELLED).
Тело запроса
| Поле | Значение |
|---|---|
payment_info.amount_tenge | обязан совпадать с total_tenge заказа; при расхождении 422 с обеими суммами в details, заказ остаётся pending, окно идёт, повтор с верной суммой допустим до expires_at |
payment_info.paid_at | момент списания у вас, ISO 8601 |
payment_info.external_payment_id | идентификатор платежа в вашей системе или у эквайера |
payment_info.currency, method | KZT; способ оплаты в свободной форме, необязателен |
Ошибки
| Код | Когда | Что делать |
|---|---|---|
409EVENT_CANCELLED | мероприятие отменено между заказом и оплатой; проверяется первым | вернуть деньги |
409ORDER_EXPIRED | платёжное окно истекло по expires_at до confirm, даже если места ещё не освобождены уборкой | немедленно вернуть покупателю списанное; каждый такой случай считается дефектом интеграции и попадает в сверку |
409ORDER_CANCELLED | заказ уже отменён через cancel (поздний успех эквайера) | вернуть деньги, оформить новый заказ при желании покупателя |
422INVALID_PARAMETERS | сумма не совпала или тело неполное | исправить и повторить в окне |
404ORDER_NOT_FOUND | заказа нет или он чужой | проверить order_id |
/orders/{order_id}/cancelСписание не удалось
Зовётся при отказе эквайера. Отменяет pending-заказ и возвращает места в продажу сразу, не дожидаясь конца платёжного окна: покупатель, который тут же пробует другую карту, не должен ждать собственные места. Тела запроса нет: <body> в подписи пустая строка, Content-Type не требуется. Повтор по уже отменённому заказу возвращает 200; по заказу с прошедшим окном ничего не пишет и возвращает его со статусом expired. Тело ответа как у POST /orders.
Ошибки
| Код | Когда |
|---|---|
409ORDER_NOT_CANCELLABLE | заказ уже подтверждён; текущий статус в details. Для оплаченного заказа существует только возврат |
404ORDER_NOT_FOUND | заказа нет или он чужой |
/orders?partner_order_id=…Чтение заказа по вашему идентификатору
Ручка восстановления и разбора. Сценарий, ради которого она существует: ответ на POST /orders потерян (таймаут, падение процесса), order_id неизвестен, тело для повтора не сохранилось. Этот запрос отдаёт заказ по единственному, что у вас точно есть: вашему partner_order_id. Дальше confirm или cancel по полученному order_id. Тело такое же, как у создания заказа; после подтверждения в нём есть билеты, те же ссылки на PDF, paid_at и сохранённый слепок payment_info.
Статус expired вычисляется в момент чтения по expires_at: заказ с прошедшим окном читается как expired сразу, независимо от внутренней уборки.
Ошибки
| Код | Когда |
|---|---|
404ORDER_NOT_FOUND | заказа с таким partner_order_id у вас нет |
422INVALID_PARAMETERS | параметр отсутствует или длиннее 64 символов |
/orders/{order_id}/refundвторая фазаОтметка возврата
Недоступна в первой фазе. До неё возврат оформляется обращением в ДСС (контакт и срок обработки фиксируются договором): персонал ДСС гасит билет вручную, деньги покупателю в любом случае возвращаете вы. Во второй фазе ручка гасит перечисленные билеты и освобождает их места; операция идемпотентна по билету. Причина возврата в свободной форме попадает в журнал ДСС.
Ошибки
| Код | Когда |
|---|---|
409TICKET_NOT_REFUNDABLE | билет уже использован на входе, погашен по другой причине или относится к другому заказу; текущий статус в details |
404ORDER_NOT_FOUND | заказа нет или он чужой |
422INVALID_PARAMETERS | пустой список билетов или причина вне длины 1–500 символов |
Состояния брони и заказа
200, а не ошибкой.| Объект | Статус | Значение |
|---|---|---|
| бронь | active | места удержаны до expires_at |
expired | мест за бронью нет: срок вышел либо вы освободили её через release; повтор POST /bookings с тем же ключом вернёт её в этом состоянии и мест заново не удержит | |
converted | по брони создан заказ, его номер в order_id | |
| заказ | pending | места заперты до expires_at, ждём confirm или cancel |
paid | оплата подтверждена (paid_at), билеты выпущены или догоняют оплату в течение минуты, expires_at становится null | |
cancelled | отменён вами через cancel либо каскадом отмены мероприятия, если был pending. ОПЛАЧЕННЫЙ заказ при отмене мероприятия остаётся paid, а его билеты становятся void: возвраты начинайте по статусу МЕРОПРИЯТИЯ из фида updated_since, а не по статусу заказа | |
expired | окно прошло без confirm; вычисляется по expires_at при чтении, и по той же границе confirm отвечает ORDER_EXPIRED | |
refunded | вторая фаза: все билеты возвращены | |
partially_refunded | вторая фаза: часть билетов возвращена |
Идемпотентность и повторы
Каждый изменяющий вызов безопасен к повтору, и ключи это ваши же идентификаторы. Оба идентификатора обязаны существовать до вызова и не меняться при повторах.
| Вызов | Ключ | Повтор возвращает |
|---|---|---|
POST /bookings | partner_booking_id | 200 и ту же бронь в её текущем состоянии; второй набор мест не удерживается |
POST /orders | partner_order_id | 200 и тот же заказ в его текущем статусе |
confirm, cancel | order_id | 200 с тем же результатом |
refund | ticket_id | успех по уже погашенному билету с той же причиной |
Повтор ключа с другим телом это не повтор, а конфликт. «Другое тело» для брони это другой event_id или другое множество seats (порядок не важен), для заказа другой booking_id; customer не сравнивается. Конфликт отвечает 409 IDEMPOTENCY_CONFLICT, в details лежат сохранённое и присланное значения поля.
Восстановление после потери ответа
Если ответ на POST /orders потерян и order_id неизвестен, есть два равноценных пути: повторить POST /orders с тем же partner_order_id (вернётся тот же заказ) или прочитать его через GET /orders?partner_order_id=…. Дальше confirm или cancel по полученному order_id. Терять заказы навсегда в этой схеме негде.
Повторы на ошибках сети и 5xx
Повторяйте с нарастающей задержкой; рекомендуемый таймаут клиента 10 секунд. Поскольку каждый изменяющий вызов идемпотентен, повтор после таймаута не создаёт второй брони и второго заказа. Пишите X-Request-Id из ответа рядом с телом отказа: по нему ДСС находит запрос в своих журналах.
Изменения мероприятий
Гарантированный канал с первого дня: опрос. Опрашивайте GET /events?updated_since=… не реже раза в 5 минут. Именно так доезжают отмена, перенос, закрытие продаж и отзыв доступа, и именно на этом канале лежит ваша обязанность остановить продажу и начать возвраты по отменённому мероприятию.
- Первый запрос делается без
updated_since. Из ответа берётсяmeta.cursor. - Первый проход — одной страницей. Просите
limit=100(максимум контракта) и сверьте число записей вdataсmeta.total; не совпало — повторите запрос. Листать черезoffsetприходится, только еслиmeta.totalбольше 100, и тогда помните: каждая страница — отдельный запрос, а список между ними может измениться. Отзыв доступа убирает строку, перенос мероприятия меняет её место в порядке (список упорядочен поstarts_at), и мероприятие, которое само НЕ менялось, может оказаться на границе страниц и не попасть ни на одну; раз оно не менялось, опрос сupdated_sinceего тоже не вернёт. Поэтому: курсор берите с ПЕРВОЙ страницы — всё, что изменится во время прохода, придёт следующим опросом; выдачу обрабатывайте как идемпотентное обновление (одно мероприятие может прийти дважды); раз в сутки делайте полный проход и сверяйте набор мероприятий со своим. - Каждый следующий запрос передаёт этот курсор в
updated_sinceкак есть и берёт новый из ответа. Курсор серверный, непрозрачный и URL-безопасный; собственное время сюда не подставляется, часы двух систем расходятся. - С
updated_sinceответ полный:limitиoffsetигнорируются. Опрос ведётся без фильтровdate_from,venue_id,event_type, иначе перенос мероприятия за пределы фильтра пропал бы молча. - Изменившееся мероприятие остаётся в выдаче со своим новым состоянием. Исчезновение из списка событием не является.
| Что пришло | Что это значит | Что делать |
|---|---|---|
status: cancelled | мероприятие отменено; билеты ДСС гасит, на вход они не проходят | остановить продажу, начать возвраты покупателям |
новая starts_at | перенос | уведомить покупателей, обновить страницу |
status: sales_closed | продажи закрыты (в том числе мероприятие уже идёт или прошло) | снять с продажи |
status: sales_paused | пауза продаж, бронь отвергается | показать паузу, не бронировать |
access: revoked | ДСС отозвала ваш доступ к мероприятию; новые брони ответят 403; повторная выдача придёт как access: active | остановить продажу; проданные билеты остаются действительными, а confirm и cancel по уже созданным pending-заказам работают до конца окна |
status: sales_scheduled | грант есть, продажи ещё не открыты, дата открытия в sales_start_at, если организатор её задал; sales_start_at может быть null: тогда дата не назначена, следите за сменой статуса на sales_open по опросу updated_since | собрать страницу заранее, бронь до открытия отвечает SALES_NOT_OPEN |
Вебхуки, вторая фаза
Вебхуки booking_expired, event_updated и event_cancelled подключаются во второй фазе как ускоритель и обязательный опрос не отменяют. ДСС вызывает ваш URL и подписывает запрос той же схемой HMAC с теми же тремя заголовками; uri в канонической строке это полный путь вашего URL без хоста. Доставка не менее одного раза: любой ответ кроме 2xx или таймаут означает повтор с нарастающей задержкой, поэтому обработчик обязан быть идемпотентным по booking_id или event_id. URL для них понадобится позже.
| Вебхук | Тело |
|---|---|
booking_expired | booking_id, expired_at; дополнительно event_id, seats |
event_updated | event_id, change_type из rescheduled, updated, sales_paused, sales_resumed; new_date только при rescheduled |
event_cancelled | event_id, cancellation_reason, cancelled_at; билеты ДСС гасит сама, вы начинаете возвраты |
Билеты, QR и PDF
Токен подписан ключом мероприятия, и сканер на входе разбирает именно его. Если закодировать в QR ссылку на страницу билета, контролёр получит отказ QR_INVALID при полностью исправном билете. Это уже случалось на витрине ДСС, поэтому предупреждаем заранее.
- qr_token
- JWT в ASCII, около 520–570 символов. Кодировать в байтовом режиме QR с уровнем коррекции M (версия символа 18–20); не усекать и не перекодировать. Между чтениями токен стабилен. Значение
nullозначает, что билет погашен и QR рисовать не нужно. - pdf_url
- подписанная ссылка без состояния, действует до окончания мероприятия плюс 30 дней.
GET /ordersвозвращает те же ссылки. Отдавайте её покупателю как ссылку, а не скачивайте PDF пачками со своего сервера: маршрут защищён отдельным лимитом на адрес. - number
- номер билета вида
DSS-TKT-000061, для поддержки и сверки; номер заказа видаDSS-2026-001042 - status
activeдействителен ·usedгость прошёл ·voidпогашен (возврат, отмена, инициатива ДСС) ·expiredмероприятие прошло
Письма покупателю по партнёрскому каналу ДСС не отправляет: доставка билета, QR и PDF на вашей стороне. Покупатель партнёра находится поддержкой ДСС по номеру заказа и по customer.email, поэтому адрес обязателен.
Ошибки
Единый формат: {"error": {"code", "message", "details"}}. Ветвитесь по code, message пишите в журнал: это текст для оператора, а не для покупателя. Неизвестный код обрабатывайте по HTTP-статусу, а не падайте.
| HTTP | Код | Когда |
|---|---|---|
| 401 | INVALID_API_KEY | ключ неизвестен или заголовок отсутствует |
| 401 | INVALID_SIGNATURE | подпись не сошлась, X-Timestamp вне окна или не число; details.reason: signature_mismatch или timestamp_out_of_window |
| 403 | ACCESS_DENIED | на публично видимое мероприятие нет гранта; непубличное без гранта отвечает 404 |
| 403 | PARTNER_DISABLED | доступ партнёра приостановлен ДСС целиком; существующие заказы живут до конца окна |
| 404 | EVENT_NOT_FOUND | мероприятия нет |
| 404 | ORDER_NOT_FOUND | заказа нет или он чужой |
| 409 | SEATS_NOT_AVAILABLE | хотя бы одно место занято, ни одно не удержано; details.seats |
| 409 | SECTOR_SOLD_OUT | бронь по сектору: мест меньше, чем quantity; details.available |
| 409 | SECTOR_MODE_MISMATCH | бронь по местам в секторе режима sector или по сектору в секторе режима seat |
| 409 | SALES_PAUSED | бронь или заказ на мероприятие с приостановленными продажами |
| 409 | SALES_NOT_OPEN | бронь или заказ на мероприятие, продажи которого не открыты, закрыты или отменены |
| 409 | BOOKING_LIMIT_EXCEEDED | мест больше действующего предела; details: {limit, requested} |
| 409 | IDEMPOTENCY_CONFLICT | ваш ключ с другим телом; details: {field, stored, received} |
| 409 | BOOKING_EXPIRED | бронь истекла |
| 409 | BOOKING_NOT_FOUND | брони не существует |
| 409 | BOOKING_ALREADY_CONFIRMED | по броне уже создан другой заказ |
| 409 | ORDER_EXPIRED | платёжное окно истекло до confirm; немедленно вернуть списанное |
| 409 | ORDER_CANCELLED | confirm по заказу, уже отменённому через cancel |
| 409 | EVENT_CANCELLED | confirm, когда мероприятие отменено между заказом и оплатой |
| 409 | ORDER_NOT_CANCELLABLE | заказ уже подтверждён, отмене не подлежит |
| 409 | TICKET_NOT_REFUNDABLE | билет использован на входе или уже погашен |
| 422 | INVALID_PARAMETERS | тело или параметры не прошли проверку; разбор по полям в details; у брони по сектору details.reason sector_unknown или sector_category_unknown |
| 422 | SEATS_UNKNOWN | место не существует, принадлежит другому мероприятию или не продаётся (ему не назначена категория); details.seats |
| 429 | RATE_LIMIT_EXCEEDED | превышена частота или конкурентность; заголовки X-RateLimit-* |
| 429 | PENDING_LIMIT_EXCEEDED | слишком много незакрытых pending-заказов; повторить после Retry-After |
Словари
| Поле | Значения |
|---|---|
Event.status | sales_scheduled, sales_open, sales_paused, sales_closed, cancelled. Словарь закрыт: внутренние состояния платформы сворачиваются в эти пять, новые появятся только с новой версией контракта |
Event.access | active, revoked |
Event.event_type | concert, sport, theatre, exhibition, conference, festival, kids, other; может быть null |
Event.age_rating | 0+, 6+, 12+, 16+, 18+, 21+; может быть null |
Seat.status | free, held, sold, blocked |
category_code | код категории билетов мероприятия: C1, C2… Словаря нет, у каждого мероприятия свои категории; код стабилен и не переиспользуется. Название для покупателя в category_name |
Booking.status | active, expired, converted |
Order.status | pending, paid, cancelled, expired; во второй фазе refunded, partially_refunded |
Ticket.status | active, used, void, expired |
payment_info.currency | KZT |
| локализованный текст | объект {"ru", "kk", "en"}; у title все три языка, у description, hall_name и category_name гарантирован только ru |
Песочница и сертификация
Стенд открыт: https://api-sandbox.dss.ndsvs.kz, только HTTPS. Ключ pk_sandbox_… и секрет к нему ДСС выдаёт техническому контакту вместе с датой сертификационной сессии: часть сценариев ниже требует оператора ДСС на связи, поэтому дата согласуется отдельно. Вместе с ключами приходят этот документ с OpenAPI-файлом, коллекция Postman, сгенерированная из него, и тестовые мероприятия на стенде, на которых проходятся все сценарии, включая возврат и повторный проход. Нагрузочные прогоны по стенду только по предварительному согласованию: стенд разделяет машину с другими системами. На стенде работают те же фоновые процессы, что на продуктиве (уборка холдов, переходы мероприятий); проход по QR проверяется на настоящей точке прохода стенда контролёром ДСС во время сессии.
Секрет sandbox_secret_do_not_use_in_prod из проверочного вектора сверяется только офлайн, на бумаге, до начала интеграции. У секрета партнёра на стенде своё, случайное значение, поэтому запрос, подписанный секретом вектора, стенд отвечает 401 INVALID_SIGNATURE. Это не отказ стенда и не наша ошибка: вектором сверяют формулу подписи, ключами стенда подписывают запросы на стенд, и это два разных секрета для двух разных целей.
API-документация (Scalar) на стенде закрыта паролем, как на продуктиве: схему и это руководство берите только с developers.dss.ndsvs.kz.
Ночной пересев
Каждую ночь с 23:00 до 23:10 UTC (04:00-04:10 по Алматы) стенд недоступен и пересеивается: брони, заказы и билеты предыдущего дня исчезают, многодневные проверки сверки планируйте с учётом этого. Ключ и секрет партнёра при пересеве не меняются: интеграцию переделывать каждое утро не нужно, обновляются только данные. На стенде может быть несколько партнёров одновременно; ключ и секрет у каждого свои, а мероприятия и гранты одинаковы. После пересева на стенде: мероприятие 1 в sales_open с грантом (счастливый путь), мероприятие 2 в sales_open без гранта (сценарий 10 из списка ниже), мероприятие 3 в published с грантом, то есть видно как sales_scheduled (сценарий 25). В каждом зале 960 мест в шести секторах.
Две частые ошибки первого прогона
GET /events/{event_id}/seats отвечает по-разному в двух режимах. Без sector приходит сводка по секторам (ключ sectors, без seats); с sector=<sector_id> приходят места этого сектора (ключ seats, без sectors). Значения sector_id для второго запроса берите из ответа первого.
GET /api/v1/partners/events/32/seats
GET /api/v1/partners/events/32/seats?sector=sector-AA-AЛимит частоты и потолок незакрытых pending-заказов общие на партнёра, а не на мероприятие и не на экземпляр клиента. Окно лимита частоты восстанавливается само каждую минуту; потолок pending сам не освобождается, слот закрывает только confirm, cancel или истечение платёжного окна. На каждый 429 выдерживайте ровно Retry-After, а не собственный таймаут повтора.
Что нужно от партнёра
- Подтверждение проверочного вектора, до всего остального.
- Технический контакт: кто у вас ведёт интеграцию.
- Две даты: когда ваша разработка готова и когда вы готовы сертифицироваться.
- Диапазон исходящих адресов, если хотите ограничение по адресу.
- URL для вебхуков понадобится позже, ко второй фазе.
Сценарии сертификации
Продуктивные ключи выдаются после того, как эти сценарии пройдены на стенде вашим клиентом. Список можно использовать как собственный чеклист до сертификации.
- Проверочный вектор HMAC сходится.
- Счастливый путь:
events→seats→bookings→orders→confirm, билеты в ответе. Предусловие: мероприятие с открытыми продажами и грантом; на стенде это мероприятие №1 (см. «Ночной пересев»). - Повтор брони с тем же
partner_booking_idвозвращает ту же бронь, места не удвоены. - Повтор заказа с тем же
partner_order_idвозвращает тот же заказ. - Повтор
confirmотвечает200с теми же билетами. - Бронь истекла:
BOOKING_EXPIRED, места свободны. - Заказ истёк до
confirm:ORDER_EXPIRED, и в ту же секундуGET /ordersотвечаетexpired: обе стороны окна дают один ответ. - Отказ эквайера:
cancel, места свободны немедленно. amount_tengeне равен сумме заказа:422с обеими суммами.- Мероприятие без гранта:
403, в списке отсутствует. На стенде это мероприятие №2. - Превышение лимита частоты:
429с заголовками. Этот сценарий намеренно исчерпывает общий лимит партнёра: после него дождитесь конца минутного окна (заголовокX-RateLimit-Reset), иначе следующие сценарии получат429не по своей вине. - Отмена мероприятия видна опросом с
updated_since. - QR из
qr_tokenпроходит на настоящей точке прохода стенда, повторный скан того же билета отвергается кодомALREADY_USED. Проходится вместе с оператором ДСС на сертификационной сессии: скан делает контролёр ДСС своей учёткой, отдельной демо-точки нет. - Потеря ответа: повтор
POST /ordersиGET /orders?partner_order_id=возвращают тот же заказ, второй не создаётся. - Отзыв гранта виден опросом (
access: "revoked"), бронь по нему отвечает403,confirmпо живомуpendingпроходит. - Бронь сверх предела:
BOOKING_LIMIT_EXCEEDED; бронь наsales_paused:SALES_PAUSED. - Повтор ключа с другим телом:
IDEMPOTENCY_CONFLICT; повтор брони после истечения возвращает её соstatus: expiredи мест не удерживает. confirmпосле каскада отмены мероприятия:EVENT_CANCELLED.cancelиconfirmнаперегонки по одному заказу: ровно один из них побеждает, второй отвечает409.PARTNER_DISABLEDпосреди потока: новая бронь отвергается,confirmпо живомуpendingпроходит.422по сумме, затем повторconfirmс верной суммой в окне проходит.- Запрос без
X-Api-Keyи запрос сX-Timestampвне окна: два разных кода. - Ротация секрета: после
rotateподписи и старым, и новым секретом принимаются; окно закрывает отдельная командаdrop-prev, само оно не закрывается, ДСС сообщает партнёру, когда старый секрет отключён. PENDING_LIMIT_EXCEEDEDпри превышении потолка незакрытых заказов, повтор послеRetry-After. Потолок общий на партнёра и сам не восстанавливается: после сценария отмените созданные заказы черезcancelили дождитесь истечения платёжного окна, иначе следующие сценарии получат429не по своей вине.- Неизвестный
sector:422сdetails.sector; бронь наsales_scheduled:SALES_NOT_OPEN. На стенде это мероприятие №3. - Гашение билета персоналом ДСС видно в
GET /ordersи отвергается на точке прохода кодомTICKET_VOID. - Перенос мероприятия (
starts_at) виден опросом. paidс неполнымticketsна намеренно уроненном выпуске: повторное чтение возвращает полный список, денег партнёр не возвращал.- Повтор
confirmс другимpayment_info:200с сохранённым первым слепком. - Освобождение брони:
POST /bookings/{booking_id}/releaseвозвращает места в продажу сразу, повтор отвечает200, по брони с заказом приходит409 BOOKING_ALREADY_CONFIRMED. - Бронь по сектору: на секторе с
sale_mode: "sector"бронь сsectorвыдаёт запрошенное число мест этого сектора и категории, повтор с тем же ключом возвращает ту же бронь, тот же ключ с другимsectorили сseatsотвечаетIDEMPOTENCY_CONFLICT. Нужен сектор режимаsectorна стенде; без него сценарий пропускается. - Сектор распродан: бронь по сектору на одно место больше остатка отвечает
SECTOR_SOLD_OUTсdetails.available. Нужен сектор режимаsectorс остатком до 19 мест.
Версии
Добавление полей и кодов ошибок происходит без предупреждения: клиент обязан переживать неизвестные поля и ветвиться по HTTP-статусу на неизвестных кодах. Удаление и переименование только новой версией контракта с уведомлением и окном параллельной работы.
| Версия | Дата | Что изменилось |
|---|---|---|
| 0.7.0 | 08.10.2026 | Только добавления. Цвет категории: поле category_color (#RRGGBB) у места в GET /events/{id}/seats?sector=…, у места брони и у категории сектора в сводке секторов. null ровно тогда, когда category_code равен null; у категории сектора задан всегда. Тот же цвет, что в легенде сайта продажи ДСС; цвет категории выбирает организатор. |
| 0.6.0 | 08.10.2026 | Только добавления. SVG-схема зала от дизайнера, та же, что у сайта продажи: поле scheme (overview_svg_url, sectors[] с sector_id и svg_url) в карточке GET /events/{id}, в списке null; поле svg_url у сектора в сводке секторов, null у стоячей зоны и без схемы. Файлы публичные, без подписи. id элементов обзорного SVG равен sector_id, id элементов SVG сектора равен space_id места. |
| 0.5.0 | 06.10.2026 | Только добавления. Покупка сектором: у сектора в сводке появились sale_mode (seat или sector), standing и categories (категории с остатком и текущей ценой); у сектора режима sector список мест пуст. POST /bookings принимает ровно одно из seats и sector ({sector_id, category_code, quantity}), платформа сама выбирает места. Новые коды: 409 SECTOR_SOLD_OUT (с details.available) и 409 SECTOR_MODE_MISMATCH; 422 INVALID_PARAMETERS с details.reason sector_unknown и sector_category_unknown. Сценарии сертификации 32 и 33. |
| 0.4.0 | 30.09.2026 | Ломающее: category_code больше не закрытый словарь (standard, vip, fan, invitation, service), у каждого мероприятия свои коды C1, C2…; фильтр category у мест принимает их, неизвестный код возвращает пустой список, а не 422. Добавлено category_name у места и забронированного места. price_tenge и price_from_tenge это текущая цена (у категории бывают периоды «с даты»); цена брони фиксируется при брони. price_from_tenge мероприятия и сектора null, если в продаже только бесплатные места. Место без категории приходит status: blocked, is_sellable: false, price_tenge: null и не считается в доступных. |
| 0.3.1 | 17.09.2026 | Ручка освобождения брони; уточнён смысл expired; описан предел одновременных запросов. Уточнено 22.09, без изменения поведения: первый проход фида (раздел «Изменения мероприятий»), минимальная длина partner_order_id |
| 0.3.0 | 03.09.2026 | Первая редакция, выданная партнёрам. Относительно внутренней 0.2.0: order_id и ticket_id стали UUID-строками; данные покупателя только псевдоним email, поля name, phone и consent убраны; правило «не списывать позже, чем за 30 секунд до expires_at»; стенд открыт, ключи выдаются техническому контакту |
| 0.2.0 | 02.09.2026 | Внутренняя редакция после двух ревью, партнёрам не выдавалась |
| 0.1.0 | 01.09.2026 | Внутренняя редакция, партнёрам не выдавалась |