DSSDevelopers
Единая цифровая платформа ДСС

Партнёрский API продажи билетов

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

версия контракта 0.7.0 дата 8 октября 2026 статус на проде и на стенде с 04.09; продуктивные ключи — после сертификации источник partner-api.openapi.yaml

Этот документ и OpenAPI-файл описывают одно и то же. При расхождении по форме полей побеждает OpenAPI-файл; этот текст объясняет, почему поля устроены именно так и что с ними делать.

Как это работает

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

  1. Живой инвентарь, а не квота. Вы работаете с теми же местами, что и витрина ДСС. Место, проданное на витрине, исчезает у вас немедленно, и наоборот. Заранее выделенных пачек мест не существует; ДСС остаётся мастер-системой статусов.
  2. Доступ выдаётся на мероприятие. Вы видите и продаёте только то, на что ДСС выдала грант. Список меняется на стороне ДСС без правок вашего кода.
  3. Деньги у вас. Вы принимаете оплату и сами фискализируете чек. Платёжный шлюз ДСС в этом потоке не участвует; payment_info в подтверждении заказа это факт вашего платежа, который ДСС хранит для сверки. Взаиморасчёт между ДСС и партнёром периодический и идёт вне API.
  4. Заказ раньше денег, билеты после подтверждения. Заказ запирает места платёжным окном до списания. После списания вы зовёте confirm и получаете билеты: подписанный qr_token и pdf_url на каждый. Писем покупателю ДСС не отправляет, доставка на вашей стороне.
  5. Возврат денег делаете вы. ДСС гасит билеты, погашенный не проходит на вход. В первой фазе возврат оформляется обращением к персоналу ДСС, и место после него в продажу не возвращается; освобождение мест появится вместе с ручкой refund во второй фазе.
GET/events GET/seats POST/bookings POST/orders списание у партнёра POST/confirm билетыqr_token POST/cancel эквайер отказал бронь: 3 мин ttl_seconds окно оплаты: 5 мин expires_at списывать деньги только после 201 на POST /orders
Счастливый путь и единственная развилка. Бронь держит места на время выбора, заказ на платёжное окно; между ними только один запрет: не списывать деньги, пока заказ не создан. Цифры таймеров это умолчания, действующие значения приходят в ответах.
Правило, которое проверяет сертификация

Списывать деньги раньше ответа 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>
ЧастьЧто подставлять
timestampUnix-время в секундах, десять цифр, то же значение, что в X-Timestamp. Окно валидности пять минут в обе стороны.
METHODGET или POST, заглавными.
uriПолный путь с query-строкой ровно как он уходит на провод, включая префикс /api/v1/partners, без хоста. Параметры не пересортировываются.
bodyТело запроса байт в байт. Для GET и для POST без тела (cancel) пустая строка, но разделитель \n перед ней остаётся; Content-Type у запроса без тела не требуется.

Подпись: HMAC-SHA256(key = secret_key, message = canonical), результат в нижнем регистре hex. Секрет это ключ HMAC, а не часть сообщения.

Пример на Pythonpython
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
POST, тело без лишних пробелов
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: d2b089654f675c25a6f8e3dacce46f5850c7a5a6c62a8a697fcd92e8fc6c5527
GET, тело пустое
1790841600\nGET\n/api/v1/partners/events?date_from=2026-10-01&limit=50\n

X-Signature: d783761a531da8c84c1e6fba1c048f5ebc6cf8c9ac66fe43a7cf060dd95616ca

POST без тела (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отметка возврата, вторая фаза
GET/events

Список мероприятий и фид изменений

Возвращает мероприятия, на которые у вас есть грант. Без updated_since в выдаче sales_scheduled, sales_open и sales_paused. С updated_since ручка становится фидом изменений: приходят все мероприятия с грантом, изменившиеся после курсора, в том числе cancelled, sales_closed и отозванные (access: "revoked"). Подробно об опросе в разделе Изменения мероприятий.

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

ПараметрЗначение
updated_sincemeta.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превышена частота
GET/events/{event_id}

Карточка мероприятия

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

Ошибки

КодКогда
403ACCESS_DENIEDмероприятие видно на витрине ДСС, гранта нет
403PARTNER_DISABLEDдоступ партнёра приостановлен ДСС целиком; отличайте от проблемы ключа
404EVENT_NOT_FOUNDтакого event_id нет, либо мероприятие непубличное и гранта на него нет
GET/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; у категории в сводке секторов он задан всегда.

POST/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
403404ACCESS_DENIED, PARTNER_DISABLED, EVENT_NOT_FOUNDкак у чтений
POST/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 из ответа на бронь
POST/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
POST/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, methodKZT; способ оплаты в свободной форме, необязателен

Ошибки

КодКогдаЧто делать
409EVENT_CANCELLEDмероприятие отменено между заказом и оплатой; проверяется первымвернуть деньги
409ORDER_EXPIREDплатёжное окно истекло по expires_at до confirm, даже если места ещё не освобождены уборкойнемедленно вернуть покупателю списанное; каждый такой случай считается дефектом интеграции и попадает в сверку
409ORDER_CANCELLEDзаказ уже отменён через cancel (поздний успех эквайера)вернуть деньги, оформить новый заказ при желании покупателя
422INVALID_PARAMETERSсумма не совпала или тело неполноеисправить и повторить в окне
404ORDER_NOT_FOUNDзаказа нет или он чужойпроверить order_id
POST/orders/{order_id}/cancel

Списание не удалось

Зовётся при отказе эквайера. Отменяет pending-заказ и возвращает места в продажу сразу, не дожидаясь конца платёжного окна: покупатель, который тут же пробует другую карту, не должен ждать собственные места. Тела запроса нет: <body> в подписи пустая строка, Content-Type не требуется. Повтор по уже отменённому заказу возвращает 200; по заказу с прошедшим окном ничего не пишет и возвращает его со статусом expired. Тело ответа как у POST /orders.

Ошибки

КодКогда
409ORDER_NOT_CANCELLABLEзаказ уже подтверждён; текущий статус в details. Для оплаченного заказа существует только возврат
404ORDER_NOT_FOUNDзаказа нет или он чужой
GET/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 символов
POST/orders/{order_id}/refundвторая фаза

Отметка возврата

Недоступна в первой фазе. До неё возврат оформляется обращением в ДСС (контакт и срок обработки фиксируются договором): персонал ДСС гасит билет вручную, деньги покупателю в любом случае возвращаете вы. Во второй фазе ручка гасит перечисленные билеты и освобождает их места; операция идемпотентна по билету. Причина возврата в свободной форме попадает в журнал ДСС.

Ошибки

КодКогда
409TICKET_NOT_REFUNDABLEбилет уже использован на входе, погашен по другой причине или относится к другому заказу; текущий статус в details
404ORDER_NOT_FOUNDзаказа нет или он чужой
422INVALID_PARAMETERSпустой список билетов или причина вне длины 1–500 символов

Состояния брони и заказа

Бронь Заказ active converted expired pending paid cancelled expired refunded POST /orders ttl_seconds прошёл confirm cancel expires_at прошёл refund, фаза 2
Оба автомата без возвратных рёбер: истёкшую бронь не оживить, отменённый или истёкший заказ не подтвердить. Повтор любого перехода по уже достигнутому состоянию отвечает 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 /bookingspartner_booking_id200 и ту же бронь в её текущем состоянии; второй набор мест не удерживается
POST /orderspartner_order_id200 и тот же заказ в его текущем статусе
confirm, cancelorder_id200 с тем же результатом
refundticket_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 минут. Именно так доезжают отмена, перенос, закрытие продаж и отзыв доступа, и именно на этом канале лежит ваша обязанность остановить продажу и начать возвраты по отменённому мероприятию.

  1. Первый запрос делается без updated_since. Из ответа берётся meta.cursor.
  2. Первый проход — одной страницей. Просите limit=100 (максимум контракта) и сверьте число записей в data с meta.total; не совпало — повторите запрос. Листать через offset приходится, только если meta.total больше 100, и тогда помните: каждая страница — отдельный запрос, а список между ними может измениться. Отзыв доступа убирает строку, перенос мероприятия меняет её место в порядке (список упорядочен по starts_at), и мероприятие, которое само НЕ менялось, может оказаться на границе страниц и не попасть ни на одну; раз оно не менялось, опрос с updated_since его тоже не вернёт. Поэтому: курсор берите с ПЕРВОЙ страницы — всё, что изменится во время прохода, придёт следующим опросом; выдачу обрабатывайте как идемпотентное обновление (одно мероприятие может прийти дважды); раз в сутки делайте полный проход и сверяйте набор мероприятий со своим.
  3. Каждый следующий запрос передаёт этот курсор в updated_since как есть и берёт новый из ответа. Курсор серверный, непрозрачный и URL-безопасный; собственное время сюда не подставляется, часы двух систем расходятся.
  4. С updated_since ответ полный: limit и offset игнорируются. Опрос ведётся без фильтров date_from, venue_id, event_type, иначе перенос мероприятия за пределы фильтра пропал бы молча.
  5. Изменившееся мероприятие остаётся в выдаче со своим новым состоянием. Исчезновение из списка событием не является.
Что пришлоЧто это значитЧто делать
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_expiredbooking_id, expired_at; дополнительно event_id, seats
event_updatedevent_id, change_type из rescheduled, updated, sales_paused, sales_resumed; new_date только при rescheduled
event_cancelledevent_id, cancellation_reason, cancelled_at; билеты ДСС гасит сама, вы начинаете возвраты

Билеты, QR и PDF

QR кодируется из qr_token, а не из ссылки

Токен подписан ключом мероприятия, и сканер на входе разбирает именно его. Если закодировать в 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КодКогда
401INVALID_API_KEYключ неизвестен или заголовок отсутствует
401INVALID_SIGNATUREподпись не сошлась, X-Timestamp вне окна или не число; details.reason: signature_mismatch или timestamp_out_of_window
403ACCESS_DENIEDна публично видимое мероприятие нет гранта; непубличное без гранта отвечает 404
403PARTNER_DISABLEDдоступ партнёра приостановлен ДСС целиком; существующие заказы живут до конца окна
404EVENT_NOT_FOUNDмероприятия нет
404ORDER_NOT_FOUNDзаказа нет или он чужой
409SEATS_NOT_AVAILABLEхотя бы одно место занято, ни одно не удержано; details.seats
409SECTOR_SOLD_OUTбронь по сектору: мест меньше, чем quantity; details.available
409SECTOR_MODE_MISMATCHбронь по местам в секторе режима sector или по сектору в секторе режима seat
409SALES_PAUSEDбронь или заказ на мероприятие с приостановленными продажами
409SALES_NOT_OPENбронь или заказ на мероприятие, продажи которого не открыты, закрыты или отменены
409BOOKING_LIMIT_EXCEEDEDмест больше действующего предела; details: {limit, requested}
409IDEMPOTENCY_CONFLICTваш ключ с другим телом; details: {field, stored, received}
409BOOKING_EXPIREDбронь истекла
409BOOKING_NOT_FOUNDброни не существует
409BOOKING_ALREADY_CONFIRMEDпо броне уже создан другой заказ
409ORDER_EXPIREDплатёжное окно истекло до confirm; немедленно вернуть списанное
409ORDER_CANCELLEDconfirm по заказу, уже отменённому через cancel
409EVENT_CANCELLEDconfirm, когда мероприятие отменено между заказом и оплатой
409ORDER_NOT_CANCELLABLEзаказ уже подтверждён, отмене не подлежит
409TICKET_NOT_REFUNDABLEбилет использован на входе или уже погашен
422INVALID_PARAMETERSтело или параметры не прошли проверку; разбор по полям в details; у брони по сектору details.reason sector_unknown или sector_category_unknown
422SEATS_UNKNOWNместо не существует, принадлежит другому мероприятию или не продаётся (ему не назначена категория); details.seats
429RATE_LIMIT_EXCEEDEDпревышена частота или конкурентность; заголовки X-RateLimit-*
429PENDING_LIMIT_EXCEEDEDслишком много незакрытых pending-заказов; повторить после Retry-After

Словари

ПолеЗначения
Event.statussales_scheduled, sales_open, sales_paused, sales_closed, cancelled. Словарь закрыт: внутренние состояния платформы сворачиваются в эти пять, новые появятся только с новой версией контракта
Event.accessactive, revoked
Event.event_typeconcert, sport, theatre, exhibition, conference, festival, kids, other; может быть null
Event.age_rating0+, 6+, 12+, 16+, 18+, 21+; может быть null
Seat.statusfree, held, sold, blocked
category_codeкод категории билетов мероприятия: C1, C2… Словаря нет, у каждого мероприятия свои категории; код стабилен и не переиспользуется. Название для покупателя в category_name
Booking.statusactive, expired, converted
Order.statuspending, paid, cancelled, expired; во второй фазе refunded, partially_refunded
Ticket.statusactive, used, void, expired
payment_info.currencyKZT
локализованный текстобъект {"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 для второго запроса берите из ответа первого.

Два запроса подрядhttp
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, а не собственный таймаут повтора.

Что нужно от партнёра

  1. Подтверждение проверочного вектора, до всего остального.
  2. Технический контакт: кто у вас ведёт интеграцию.
  3. Две даты: когда ваша разработка готова и когда вы готовы сертифицироваться.
  4. Диапазон исходящих адресов, если хотите ограничение по адресу.
  5. URL для вебхуков понадобится позже, ко второй фазе.

Сценарии сертификации

Продуктивные ключи выдаются после того, как эти сценарии пройдены на стенде вашим клиентом. Список можно использовать как собственный чеклист до сертификации.

  1. Проверочный вектор HMAC сходится.
  2. Счастливый путь: events → seats → bookings → orders → confirm, билеты в ответе. Предусловие: мероприятие с открытыми продажами и грантом; на стенде это мероприятие №1 (см. «Ночной пересев»).
  3. Повтор брони с тем же partner_booking_id возвращает ту же бронь, места не удвоены.
  4. Повтор заказа с тем же partner_order_id возвращает тот же заказ.
  5. Повтор confirm отвечает 200 с теми же билетами.
  6. Бронь истекла: BOOKING_EXPIRED, места свободны.
  7. Заказ истёк до confirm: ORDER_EXPIRED, и в ту же секунду GET /orders отвечает expired: обе стороны окна дают один ответ.
  8. Отказ эквайера: cancel, места свободны немедленно.
  9. amount_tenge не равен сумме заказа: 422 с обеими суммами.
  10. Мероприятие без гранта: 403, в списке отсутствует. На стенде это мероприятие №2.
  11. Превышение лимита частоты: 429 с заголовками. Этот сценарий намеренно исчерпывает общий лимит партнёра: после него дождитесь конца минутного окна (заголовок X-RateLimit-Reset), иначе следующие сценарии получат 429 не по своей вине.
  12. Отмена мероприятия видна опросом с updated_since.
  13. QR из qr_token проходит на настоящей точке прохода стенда, повторный скан того же билета отвергается кодом ALREADY_USED. Проходится вместе с оператором ДСС на сертификационной сессии: скан делает контролёр ДСС своей учёткой, отдельной демо-точки нет.
  14. Потеря ответа: повтор POST /orders и GET /orders?partner_order_id= возвращают тот же заказ, второй не создаётся.
  15. Отзыв гранта виден опросом (access: "revoked"), бронь по нему отвечает 403, confirm по живому pending проходит.
  16. Бронь сверх предела: BOOKING_LIMIT_EXCEEDED; бронь на sales_paused: SALES_PAUSED.
  17. Повтор ключа с другим телом: IDEMPOTENCY_CONFLICT; повтор брони после истечения возвращает её со status: expired и мест не удерживает.
  18. confirm после каскада отмены мероприятия: EVENT_CANCELLED.
  19. cancel и confirm наперегонки по одному заказу: ровно один из них побеждает, второй отвечает 409.
  20. PARTNER_DISABLED посреди потока: новая бронь отвергается, confirm по живому pending проходит.
  21. 422 по сумме, затем повтор confirm с верной суммой в окне проходит.
  22. Запрос без X-Api-Key и запрос с X-Timestamp вне окна: два разных кода.
  23. Ротация секрета: после rotate подписи и старым, и новым секретом принимаются; окно закрывает отдельная команда drop-prev, само оно не закрывается, ДСС сообщает партнёру, когда старый секрет отключён.
  24. PENDING_LIMIT_EXCEEDED при превышении потолка незакрытых заказов, повтор после Retry-After. Потолок общий на партнёра и сам не восстанавливается: после сценария отмените созданные заказы через cancel или дождитесь истечения платёжного окна, иначе следующие сценарии получат 429 не по своей вине.
  25. Неизвестный sector: 422 с details.sector; бронь на sales_scheduled: SALES_NOT_OPEN. На стенде это мероприятие №3.
  26. Гашение билета персоналом ДСС видно в GET /orders и отвергается на точке прохода кодом TICKET_VOID.
  27. Перенос мероприятия (starts_at) виден опросом.
  28. paid с неполным tickets на намеренно уроненном выпуске: повторное чтение возвращает полный список, денег партнёр не возвращал.
  29. Повтор confirm с другим payment_info: 200 с сохранённым первым слепком.
  30. Освобождение брони: POST /bookings/{booking_id}/release возвращает места в продажу сразу, повтор отвечает 200, по брони с заказом приходит 409 BOOKING_ALREADY_CONFIRMED.
  31. Бронь по сектору: на секторе с sale_mode: "sector" бронь с sector выдаёт запрошенное число мест этого сектора и категории, повтор с тем же ключом возвращает ту же бронь, тот же ключ с другим sector или с seats отвечает IDEMPOTENCY_CONFLICT. Нужен сектор режима sector на стенде; без него сценарий пропускается.
  32. Сектор распродан: бронь по сектору на одно место больше остатка отвечает SECTOR_SOLD_OUT с details.available. Нужен сектор режима sector с остатком до 19 мест.

Версии

Добавление полей и кодов ошибок происходит без предупреждения: клиент обязан переживать неизвестные поля и ветвиться по HTTP-статусу на неизвестных кодах. Удаление и переименование только новой версией контракта с уведомлением и окном параллельной работы.

ВерсияДатаЧто изменилось
0.7.008.10.2026Только добавления. Цвет категории: поле category_color (#RRGGBB) у места в GET /events/{id}/seats?sector=…, у места брони и у категории сектора в сводке секторов. null ровно тогда, когда category_code равен null; у категории сектора задан всегда. Тот же цвет, что в легенде сайта продажи ДСС; цвет категории выбирает организатор.
0.6.008.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.006.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.030.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.117.09.2026Ручка освобождения брони; уточнён смысл expired; описан предел одновременных запросов. Уточнено 22.09, без изменения поведения: первый проход фида (раздел «Изменения мероприятий»), минимальная длина partner_order_id
0.3.003.09.2026Первая редакция, выданная партнёрам. Относительно внутренней 0.2.0: order_id и ticket_id стали UUID-строками; данные покупателя только псевдоним email, поля name, phone и consent убраны; правило «не списывать позже, чем за 30 секунд до expires_at»; стенд открыт, ключи выдаются техническому контакту
0.2.002.09.2026Внутренняя редакция после двух ревью, партнёрам не выдавалась
0.1.001.09.2026Внутренняя редакция, партнёрам не выдавалась