# API отвечает 401 — ключ не принимается

> 401 unauthorized означает, что в запросе нет действующего API-ключа. Причины, порядок проверки и рабочий пример curl. Чаще всего виноват заголовок или префикс Bearer.

## Коротко

`401` и `{"error":"unauthorized"}` означают ровно одно: **в запросе не пришёл действующий API-ключ**. К счёту, сумме, кассиру и тарифу это отношения не имеет — с идеально составленным телом запроса 401 придёт точно так же. Три самые частые причины: заголовок `X-API-Key` вообще не отправлен, вместо него написан `Authorization: Bearer`, либо ключ удалён в кабинете. Пять шагов ниже находят причину.

## Причина по симптому

| Что вы видите | Причина | Что делать |
|---|---|---|
| `401 unauthorized` на всех эндпоинтах | Заголовка `X-API-Key` нет | Добавьте заголовок |
| `401` с ключом, который вчера работал | Ключ удалён или отключён в кабинете | Создайте новый ключ и обновите интеграцию |
| `401` в коде, но в Postman тот же ключ работает | Код берёт ключ из другой переменной, приходит пустая строка | Проверьте, загружен ли `.env` и перезапущен ли сервис |
| `422 invalid_api_key` | Формат ключа нарушен | Ключ должен быть `qp_live_…` или `qp_test_…` |
| `401`, хотя ключ выглядит правильным | Ключ отправлен с префиксом `Bearer` | Передавайте чистый ключ, без префикса |

`401` и `403` — разные вещи. 401 значит «я не знаю, кто вы». 403 значит «я знаю, кто вы, но прав на это действие нет». Если пришёл 403, смотрите [API отвечает 403](/kb/ru/api-403).

## Порядок проверки

**1. Сверьте имя заголовка посимвольно.** Оно — `X-API-Key`. Вариант `X-Api-Key` тоже подойдёт (регистр в HTTP-заголовках не важен), а `X_API_KEY`, `ApiKey` и `api-key` — нет. Некоторые фреймворки не превращают подчёркивание в дефис, поэтому пишите заголовок именно так.

**2. Не пишите Bearer.** Это самая частая ошибка. Наш API не использует схему `Authorization: Bearer …`. Ключ передаётся в заголовке `X-API-Key`, без префикса, в чистом виде.

```
Верно:    X-API-Key: qp_live_xxxxxxxxxxxxxxxx
Неверно:  Authorization: Bearer qp_live_xxxxxxxxxxxxxxxx
Неверно:  X-API-Key: Bearer qp_live_xxxxxxxxxxxxxxxx
```

**3. Посмотрите на сам ключ.** Он начинается с `qp_live_` или `qp_test_`. При копировании в начало или конец часто попадают пробел, перевод строки или кавычка. Обрежьте их в коде:

```js
const key = (process.env.QUTPAY_API_KEY || '').trim();
if (!key.startsWith('qp_')) throw new Error('API-ключ не загружен');
```

Если такая проверка выполняется при старте сервиса, вы увидите проблему сразу, а не в проде.

**4. Убедитесь, что ключ есть в кабинете.** Список ключей — [кабинет](https://qut.kz/app) → раздел «Интеграции». Удалённый ключ не восстанавливается: нужно создать новый и обновить интеграцию.

**5. Проверьте, ключ какого режима вы используете.** `qp_test_` — песочница, `qp_live_` — боевой режим. Если интеграция работает в одном режиме, а ключ от другого, путаница начинается именно здесь. О разнице режимов: [Что такое Qut Pay](/kb/ru/what-is-qutpay).

## Рабочий пример

Действителен ли сам ключ, выясняется одним запросом:

```bash
curl -i https://api.qut.kz/api/v1/invoices \
  -H "X-API-Key: qp_test_ВАШ_КЛЮЧ"
```

Пришёл `200` — ключ рабочий, дело в том, как ваш код отправляет заголовок. Пришёл `401` — дело в самом ключе.

Создание счёта:

```bash
curl -i -X POST https://api.qut.kz/api/v1/invoices \
  -H "X-API-Key: qp_test_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"amount":1000,"description":"Тестовый счёт"}'
```

Node.js:

```js
const res = await fetch('https://api.qut.kz/api/v1/invoices', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.QUTPAY_API_KEY.trim(),
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ amount: 1000, description: 'Тестовый счёт' }),
});
```

В PHP с cURL заголовок пишется целой строкой:

```php
curl_setopt($ch, CURLOPT_HTTPHEADER, [
  'X-API-Key: ' . trim(getenv('QUTPAY_API_KEY')),
  'Content-Type: application/json',
]);
```

## Пять типичных промахов

- **Ключ обновили, а `.env` на сервере — нет.** Создав новый ключ, не забудьте перезапустить сервис.
- **Прокси или CDN срезали заголовок.** В некоторых конфигурациях неизвестные `X-`заголовки не пропускаются. Отправьте запрос напрямую и сравните.
- **Ключ лежит в коде, который исполняется в браузере.** Там ему не место, и заголовок часто теряется из-за CORS. Ключ должен быть только на сервере.
- **Смешались два ключа.** Где-то остался старый, где-то уже новый. Приведите все места к одному.
- **Ключ удалил коллега.** Если в команде несколько человек, договоритесь, кто создаёт и удаляет ключи.

## Если всё верно, а 401 остаётся

Ключ новый, заголовок правильный, пробелов нет, а 401 продолжает приходить — проверьте, не на нашей ли стороне дело:

```bash
curl -s https://api.qut.kz/api/v1/status
```

Этот эндпоинт отвечает и без ключа. Если он молчит, причина не в вашем ключе. Как разделить стороны: [Проблема у вас или у Kaspi](/kb/ru/is-it-us-or-kaspi).

Обращаясь в поддержку, приложите **первые десять символов** ключа (никогда не присылайте ключ целиком), время запроса, имя эндпоинта и полный текст ответа.

## Вопросы и ответы

**Стоит ли повторять запрос при 401?** Нет смысла. Пока запрос не изменился, ответ будет тем же. Цикл повторов только упрётся в суточную защиту.

**Если сменить ключ, пропадут ли старые счета?** Нет. Счета принадлежат организации, а не ключу. С новым ключом вы увидите всё как раньше.

**Что делать, если ключ утёк?** Немедленно удалить его в кабинете и создать новый. Удалённым ключом создать счёт уже нельзя.

**Может ли один ключ обслуживать несколько серверов?** Может. Но удобнее выдать каждой точке свой ключ — тогда видно, кто какой трафик создаёт.

**Можно ли создать боевой счёт ключом песочницы?** Нет. Ключ `qp_test_` работает только в песочнице, реальные деньги при этом не двигаются.
