# Первый запрос за 5 минут

Цель этой страницы — убедиться, что ключ работает и вы видите смены лагеря. Понадобятся ключ
(см. [Ключ доступа и права](/partners/access)) и терминал с `curl` — или любой язык программирования.

## Адрес API

```
https://partners.zvezdnyy-burabai.edu.kz/ext/v1
```

Все адреса в руководстве пишутся от этого начала: `GET /shifts` значит
`GET https://partners.zvezdnyy-burabai.edu.kz/ext/v1/shifts`.

- **Только `https://`.** Запрос по `http://` отклоняется, а ключ, который в нём был, **сразу
  отзывается**. Проверьте адрес в настройках CRM дважды.
- Версия API — в адресе (`/ext/v1`). Что это значит для вас — в
  [Совместимости](/partners/compatibility).
- Описание API в формате OpenAPI 3.1 можно [скачать в справочнике](/api) — по нему генерируется
  клиент для вашего языка.

## Заголовки

| Заголовок         | Когда                                         | Значение                                                       |
| ----------------- | --------------------------------------------- | -------------------------------------------------------------- |
| `Authorization`   | Всегда                                        | `Bearer zb_live_…` — ваш ключ                                  |
| `Content-Type`    | Когда отправляете тело (`POST`, `PATCH`)      | `application/json`                                             |
| `Idempotency-Key` | Почти у всех `POST`                           | Случайная строка, своя на каждое действие. См. [Повтор запроса](/partners/idempotency) |
| `If-Match`        | По желанию: правка и отмена брони, правка семьи | `version` записи, которую вы видели. См. [Повтор запроса](/partners/idempotency#if-match-не-затереть-чужую-правку) |

Тело запроса и ответа — JSON в UTF-8. Размер тела запроса — не больше 1 МБ.

В каждом ответе есть заголовок `X-Request-Id` — номер запроса. Сохраняйте его в своём журнале: по
нему лагерь найдёт запрос, если что-то пошло не так.

## Шаг 1. Кто я

Самый простой запрос — [`GET /me`](/api/подключение): «кто я, какой у меня отдел и какие права».
Он не меняет никаких данных.

Сначала положите ключ в переменную окружения, чтобы не вставлять его в каждую команду:

```sh
export ZB_KEY="zb_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"   # ваш настоящий ключ
```

<CodeTabs>

```sh title="cURL"
curl https://partners.zvezdnyy-burabai.edu.kz/ext/v1/me \
  -H "Authorization: Bearer $ZB_KEY"
```

```js title="JavaScript"
const res = await fetch("https://partners.zvezdnyy-burabai.edu.kz/ext/v1/me", {
  headers: { Authorization: `Bearer ${process.env.ZB_KEY}` },
});
const me = await res.json();
if (!res.ok) throw new Error(`${res.status} ${me.code}: ${me.message}`);
console.log(me.department.name, me.scopes);
```

```python title="Python"
import os
import requests

res = requests.get(
    "https://partners.zvezdnyy-burabai.edu.kz/ext/v1/me",
    headers={"Authorization": f"Bearer {os.environ['ZB_KEY']}"},
    timeout=30,
)
me = res.json()
if not res.ok:
    raise RuntimeError(f"{res.status_code} {me['code']}: {me['message']}")
print(me["department"]["name"], me["scopes"])
```

```php title="PHP"
$ch = curl_init('https://partners.zvezdnyy-burabai.edu.kz/ext/v1/me');
curl_setopt_array($ch, [
    CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('ZB_KEY')],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
]);
$me = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
    throw new RuntimeException("$status {$me['code']}: {$me['message']}");
}
echo $me['department']['name'], PHP_EOL;
```

</CodeTabs>

Ответ `200` (данные вымышленные):

```json
{
  "client": { "id": "3", "name": "CRM отдела «Пример»" },
  "department": { "id": "7", "name": "Отдел продаж «Пример»" },
  "scopes": ["read", "bookings", "invoices", "webhooks"],
  "limits": {
    "read_per_sec": 10,
    "write_per_sec": 2,
    "concurrent": 10,
    "bookings_per_minute": 30,
    "invoices_per_booking_hour": 5
  }
}
```

- `department` — ваш отдел. Если здесь чужой отдел — вам выдали не тот ключ, сообщите лагерю.
- `scopes` — права ключа.
- `limits` — ваши лимиты (см. [Лимиты](/partners/limits)).

В ответе могут быть и другие поля. Это нормально: незнакомые поля пропускайте (см.
[Совместимость](/partners/compatibility)).

### Если ответ не 200

| Ответ                    | Что не так                                                          |
| ------------------------ | ------------------------------------------------------------------- |
| `401 unauthorized`       | Нет заголовка `Authorization`, опечатка в ключе, ключ отозван или истёк. Проверьте, что перед ключом стоит `Bearer ` с пробелом |
| `403 https_required`     | Запрос ушёл по `http://`. **Ключ уже отозван** — нужен новый         |
| `400 key_in_url`         | Ключ оказался в адресе. **Ключ уже отозван** — нужен новый           |
| `403 ip_not_allowed`     | Ключ привязан к другим адресам серверов — сообщите лагерю ваш IP     |
| `429 rate_limited`       | Слишком много запросов или много неверных ключей с вашего адреса. Подождите столько секунд, сколько в заголовке `Retry-After` |

## Шаг 2. Какие смены продаются

[`GET /shifts?on_sale=true`](/api/смены) — смены, на которые можно оформить бронь прямо сейчас:

```sh
curl "https://partners.zvezdnyy-burabai.edu.kz/ext/v1/shifts?on_sale=true" \
  -H "Authorization: Bearer $ZB_KEY"
```

Ответ `200` — список **порцией** (подробно о порциях — в [Форматах данных](/partners/formats#списки-и-порции)):

```json
{
  "items": [
    {
      "id": "12",
      "season": { "id": "2", "name": "Лето 2027" },
      "number": 3,
      "date_start": "2027-07-01",
      "date_end": "2027-07-14",
      "status": "open",
      "on_sale": true,
      "sales_close_at": "2027-06-30T19:00:00Z",
      "price": { "amount": "250000.00", "currency": "KZT" },
      "transfer_price": { "amount": "15000.00", "currency": "KZT" },
      "transfer_prices": [
        {
          "transfer_point_id": "4",
          "name": "Астана, ж/д вокзал",
          "price": { "amount": "15000.00", "currency": "KZT" }
        },
        {
          "transfer_point_id": "5",
          "name": "Караганда, автовокзал",
          "price": { "amount": "20000.00", "currency": "KZT" }
        }
      ],
      "min_payment": { "amount": "50000.00", "currency": "KZT" },
      "full_payment_due": "2027-06-24",
      "max_discount": { "amount": "20000.00", "currency": "KZT" },
      "seats": { "quota": 20, "taken": 7, "free": 13 },
      "age_groups": [
        { "squad": 1, "age_min": 7, "age_max": 9 },
        { "squad": 2, "age_min": 10, "age_max": 12 }
      ]
    }
  ],
  "next_cursor": null
}
```

Что здесь важно для продажи:

| Поле               | Что значит                                                                                       |
| ------------------ | ------------------------------------------------------------------------------------------------ |
| `id`               | Номер смены — его передают при оформлении брони (`shift_id`)                                     |
| `price`            | Цена путёвки без трансфера                                                                       |
| `transfer_prices`  | Цена трансфера от каждой точки посадки. Пусто — одна цена `transfer_price` для всех точек. Точки, которой здесь нет, на эту смену не возят |
| `min_payment`      | Сколько нужно внести в первые сутки, чтобы место закрепилось. `0.00` — предоплата не нужна        |
| `full_payment_due` | До какого дня оплатить всё. В этот день лагерь сам выставит счёт на остаток                       |
| `max_discount`     | Наибольшая скидка, которую ваш отдел может дать сам. `null` — без ограничения                    |
| `seats.free`       | Сколько ещё мест можно оформить **вашему отделу**                                                |
| `sales_close_at`   | Когда закроются продажи (время UTC: `19:00Z` — это полночь по времени Казахстана)                |

## Готово

Если оба запроса ответили `200` — подключение работает. Дальше —
[Бронь и оплата по шагам](/partners/booking-and-payment).

:::caution{title="Все запросы — в боевую систему"}

Отдельного тестового адреса нет. Запросы на чтение (`GET`) безопасны — они ничего не меняют.
Оформление брони и выставление счёта — настоящие: занимают место в квоте и отправляют родителю
настоящий счёт Kaspi.

:::
