Коротко
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.
Порядок проверки
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_. При копировании в начало или конец часто попадают пробел, перевод строки или кавычка. Обрежьте их в коде:
const key = (process.env.QUTPAY_API_KEY || '').trim();
if (!key.startsWith('qp_')) throw new Error('API-ключ не загружен');
Если такая проверка выполняется при старте сервиса, вы увидите проблему сразу, а не в проде.
4. Убедитесь, что ключ есть в кабинете. Список ключей — кабинет → раздел «Интеграции». Удалённый ключ не восстанавливается: нужно создать новый и обновить интеграцию.
5. Проверьте, ключ какого режима вы используете. qp_test_ — песочница, qp_live_ — боевой режим. Если интеграция работает в одном режиме, а ключ от другого, путаница начинается именно здесь. О разнице режимов: Что такое Qut Pay.
Рабочий пример
Действителен ли сам ключ, выясняется одним запросом:
curl -i https://api.qut.kz/api/v1/invoices \
-H "X-API-Key: qp_test_ВАШ_КЛЮЧ"
Пришёл 200 — ключ рабочий, дело в том, как ваш код отправляет заголовок. Пришёл 401 — дело в самом ключе.
Создание счёта:
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:
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 заголовок пишется целой строкой:
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'X-API-Key: ' . trim(getenv('QUTPAY_API_KEY')),
'Content-Type: application/json',
]);
Пять типичных промахов
- Ключ обновили, а
.envна сервере — нет. Создав новый ключ, не забудьте перезапустить сервис. - Прокси или CDN срезали заголовок. В некоторых конфигурациях неизвестные
X-заголовки не пропускаются. Отправьте запрос напрямую и сравните. - Ключ лежит в коде, который исполняется в браузере. Там ему не место, и заголовок часто теряется из-за CORS. Ключ должен быть только на сервере.
- Смешались два ключа. Где-то остался старый, где-то уже новый. Приведите все места к одному.
- Ключ удалил коллега. Если в команде несколько человек, договоритесь, кто создаёт и удаляет ключи.
Если всё верно, а 401 остаётся
Ключ новый, заголовок правильный, пробелов нет, а 401 продолжает приходить — проверьте, не на нашей ли стороне дело:
curl -s https://api.qut.kz/api/v1/status
Этот эндпоинт отвечает и без ключа. Если он молчит, причина не в вашем ключе. Как разделить стороны: Проблема у вас или у Kaspi.
Обращаясь в поддержку, приложите первые десять символов ключа (никогда не присылайте ключ целиком), время запроса, имя эндпоинта и полный текст ответа.
Вопросы и ответы
Стоит ли повторять запрос при 401? Нет смысла. Пока запрос не изменился, ответ будет тем же. Цикл повторов только упрётся в суточную защиту.
Если сменить ключ, пропадут ли старые счета? Нет. Счета принадлежат организации, а не ключу. С новым ключом вы увидите всё как раньше.
Что делать, если ключ утёк? Немедленно удалить его в кабинете и создать новый. Удалённым ключом создать счёт уже нельзя.
Может ли один ключ обслуживать несколько серверов? Может. Но удобнее выдать каждой точке свой ключ — тогда видно, кто какой трафик создаёт.
Можно ли создать боевой счёт ключом песочницы? Нет. Ключ qp_test_ работает только в песочнице, реальные деньги при этом не двигаются.