Справка

Форматы данных

Как в API записаны деньги, даты, время, телефоны и номера, и как получать длинные списки.

Деньги

В ответах деньги — объект: сумма строкой с ровно двумя знаками после точки и валюта.

JSONCode
{ "amount": "255000.00", "currency": "KZT" }
  • Валюта сейчас всегда KZT — тенге.
  • Сумма — строка, а не число: в JavaScript, PHP и многих базах дробные числа теряют точность (0.1 + 0.2 = 0.30000000000000004). Храните деньги у себя в десятичном типе (DECIMAL(12,2), Decimal в Python, BigDecimal в Java) или в тиынах целым числом — но не в float.
  • Отрицательная сумма бывает у возврата ("-50000.00") и у остатка при переплате.

В запросах суммы — строкой в тенге, без валюты:

ГдеКак
Скидка (discount.amount)Целое или до двух знаков после точки: "10000", "10000.50"
Счёт Kaspi (amount)Только целые тенге: "50000" или "50000.00". С тиынами — fractional_amount

Разделитель дробной части — точка. Без пробелов и знака валюты: "50 000 ₸" не примется (bad_amount).

Номера записей

Все номера (id, booking_id, shift_id…) — строки: "15230", а не 15230. Храните их у себя как текст: устройство номеров может поменяться, а строка останется строкой. Не делайте выводов по величине номера.

Время и даты

ЧтоФорматПримерПоля
Момент времениRFC 3339 в UTC (буква Z в конце)2027-05-14T09:31:07.482913Z*_at: created_at, hold_until, paid_at
Дата без времениГГГГ-ММ-ДД2027-07-01*_on, date_*, *_due, birth_date
  • Время в ответах — всегда UTC. Лагерь живёт по времени Казахстана: UTC+5. Чтобы показать менеджеру местное время, прибавьте 5 часов: hold_until 2027-05-15T09:31:07Z — это 15 мая, 14:31 по Астане.
  • Дробная часть секунд может быть, а может не быть — разбирайте время стандартной функцией разбора RFC 3339, а не регулярным выражением.
  • Даты без времени (date_start, full_payment_due, paid_on) — по календарю лагеря (UTC+5). Платёж, пришедший 14 мая в 23:30 по Астане (18:30 UTC), имеет paid_on: 2027-05-14.
  • В параметрах запроса время передавайте тоже в RFC 3339: updated_since=2027-05-14T00:00:00Z или с поясом: 2027-05-14T05:00:00+05:00. Не тот формат — 400 bad_time. Знак + в адресе нужно закодировать как %2B, поэтому проще передавать время в UTC с Z.

updated_since — с запасом

Время изменения записи — это начало транзакции, которая её изменила. Запрашивая «изменённые с момента прошлой загрузки», берите момент на минуту раньше: иначе можно пропустить запись, изменённую на стыке. Повторно полученные записи просто перезапишите у себя.

Телефоны

  • В запросах — любой казахстанской записью: 87010000001, 8 701 000 00 01, +7 (701) 000-00-01, +77010000001.
  • В ответах — всегда +7XXXXXXXXXX: +77010000001.
  • Сравнивайте телефоны у себя в том же виде +7XXXXXXXXXX, иначе один номер покажется двумя.

Пустые значения

  • В ответах поле, у которого нет значения, приходит как null (а не пропадает), если в справочнике не сказано иное.
  • В правках (PATCH) различаются три случая — например, для external_id:
В телеЧто будет
поля нетне менять
"external_id": "deal-1"заменить
"external_id": nullстереть

Для payer_phone и partner_manager «стереть» — пустая строка "".

Строгость к запросам

  • Незнакомое поле в теле запроса — отказ 422 invalid_input с именем поля в message. Опечатка ("birthdate" вместо "birth_date") не превратится в молча потерянные данные.
  • Тело — JSON в UTF-8, заголовок Content-Type: application/json.
  • Логические параметры адреса — true или false: ?on_sale=true.

Списки и порции

Списки отдаются порциями (по-английски — pagination):

JSONCode
{ "items": [ { "id": "15230", "…": "…" }, { "id": "15231", "…": "…" } ], "next_cursor": "c15231" }
  1. Запросите первую порцию: GET /bookings?limit=200.
  2. Обработайте items.
  3. Если next_cursor не пустой — запросите следующую: GET /bookings?limit=200&cursor=c15231.
  4. next_cursor пустой (null) — список закончился.
  • limit — от 1 до 200, по умолчанию 50. У сверки (GET /reconcile/bookings) — до 5000, по умолчанию 1000.
  • Курсор — непрозрачная строка: передавайте как есть, не собирайте сами. Неверный курсор — 400 bad_cursor: начните список заново.
  • Ответ списка — всегда объект {"items": [...], …}, а не голый массив.
  • Списки идут по возрастанию номера записи, поэтому новые записи не сдвигают уже полученные порции (исключение — журнал доставок вебхука: от новых к старым).
  • Лента событий устроена иначе — номером after, а не курсором (см. События).
async function* all(path, params = {}) { let cursor = ""; for (;;) { const url = new URL(`https://partners.zvezdnyy-burabai.edu.kz/ext/v1${path}`); for (const [k, v] of Object.entries({ ...params, limit: 200 })) url.searchParams.set(k, v); if (cursor) url.searchParams.set("cursor", cursor); const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.ZB_KEY}` } }); if (res.status === 429) { await new Promise((r) => setTimeout(r, Number(res.headers.get("Retry-After") ?? 1) * 1000)); continue; // та же порция ещё раз } const page = await res.json(); yield* page.items; if (!page.next_cursor) return; cursor = page.next_cursor; } } for await (const booking of all("/bookings", { status: "active" })) console.log(booking.id);
Last modified on