Аутентификация
Один ключ, заголовок, который его несёт, и три вещи, которые платформа помнит о ключе: как он называется, чем оплачивается и до чего ему разрешено дотягиваться.
Быстро
Ключ едет в одном заголовке, и этого достаточно для всей поверхности:
export KUMO_API_KEY="kumo_sk_..."
curl https://api.kumorouter.com/v1/chat/completions \
-H "Authorization: Bearer $KUMO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "<model>",
"max_tokens": 16,
"messages": [{ "role": "user", "content": "reply with OK" }]
}'Клиенты и SDK Anthropic шлют ключ иначе — голым, в заголовке x-api-key. Обе операции в нативной форме Anthropic принимают его так же, как bearer-заголовок, поэтому такому клиенту достаточно поменять адрес и ключ.
Первый вызов целиком → Проверить ключ →
Заголовок
Ключ едет как bearer-токен в заголовке Authorization и больше нигде — ни в строке запроса, ни в куке, ни в теле. Строка запроса оседает в логах доступа и в истории браузера; заголовок — нет.
Каждая операция, дотягивающаяся до модели, объявляет этот один носитель, и один и тот же ключ открывает их все: на всю поверхность приходится одно удостоверение, а не по одному на протокол. Ключ выпускается организации, и любой вызов, который он аутентифицирует, тратится и считается за ней.
# Держите ключ в окружении, а не в исходниках.
export KUMO_API_KEY="kumo_sk_..."
# Каждый вызов несёт его в одном заголовке и больше нигде.
curl https://api.kumorouter.com/v1/chat/completions \
-H "Authorization: Bearer $KUMO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "<model>",
"max_tokens": 16,
"messages": [{ "role": "user", "content": "reply with OK" }]
}'Носитель Anthropic
Две операции в нативной форме Anthropic — POST /v1/messages и POST /v1/messages/count_tokens — принимают для того же ключа второй носитель: ключ передаётся без обёртки в заголовке x-api-key, а заголовка Authorization при этом нет вовсе. Это носитель нативного API Anthropic и потому единственный, который шлют его SDK: клиент, написанный под тот API, аутентифицируется здесь без единой правки.
Заголовок объявлен на этих двух операциях и больше нигде: это носитель тех клиентов, а не второй вход в остальную поверхность. Передать оба заголовка законно. Если они расходятся, отказа за это не будет: аутентифицирует присутствующий и корректно оформленный Authorization, а голый заголовок не читается вовсе. Сервер, выбирающий между двумя секретами, гадал бы, какой из них вы имели в виду. Сам ключ, поиск за ним и единственный отказ ниже остаются теми же, что у bearer-заголовка.
curl https://api.kumorouter.com/v1/messages \
-H "x-api-key: $KUMO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "<model>",
"max_tokens": 128,
"messages": [{ "role": "user", "content": "Объясни токены одной строкой." }]
}'Как выглядит ключ
Ключ — это префикс и случайный хвост за ним, и в таком виде платформа его не держит: хранится хэш ключа вместе с начальными символами случайной части и последними четырьмя.
Консоль показывает ключ целиком ровно один раз — на экране, который его создаёт, с элементом, копирующим его. После этого прочитать его не может ничто: ни экран, ни операция, ни обращение в поддержку.
Повтор того же вызова создания возвращает метаданные ключа и не возвращает секрета вовсе. Поэтому лекарство от потерянного ключа — новый ключ, а не восстановление.
Везде дальше ключ называют двумя фрагментами, которые безопасно показывать, — теми самыми начальными символами и хвостом, — плюс именем, которое вы ему дали, и номером версии.
Номер версии — тот самый токен, с которым сверяется правка. Он растёт при изменении метаданных ключа и намеренно не растёт, когда ключ просто используют: иначе ключ под нагрузкой обесценивал бы версию, которую его владелец держит в форме консоли, по нескольку раз в секунду.
Те же два фрагмента возвращает эхо личности, поэтому скриншот проверки ключа ничего не раскрывает.
Выпустить ключ → Прочитать ключ →
До чего дотягивается ключ
У ключа есть необязательная область по трём осям. Отсутствие — это разрешение: отсутствующая ось означает «все», поэтому ключ без единой оси ничем не ограничен, и проверка ключа говорит это словами, а не показывает пустой список.
modality_codesКакие виды работы можно запрашивать ключом — генерация текста, эмбеддинги, генерация изображений.model_idsКонкретные модели, которые можно называть ключом, когда его нужно ограничить точнее модальности.vendor_idsПровайдеры за этими моделями — для ключа, привязанного к одному из них.bindingИсходная привязка ключа, заданная при создании; дальше она неизменна. Тратит ли ключ из пакета аккаунта или с баланса, выбирается отдельно — в консоли, и этот выбор можно менять.Вызов, назвавший что-либо вне этих списков, отклоняется, а не маршрутизируется: область — это белый список, а не предпочтение. Что вообще можно назвать, перечислено в прайс-листе, а идентификаторы, которые несёт шлюз, он назовёт сам по адресу https://api.kumorouter.com/v1/models.
Один отказ, и только один
Любой отказ в предъявлении — это один и тот же 401. Одинаково отвечаются пять случаев:
- заголовка нет вовсе;
- ключ искажён;
- ключ неизвестен;
- ключ отозван;
- срок ключа истёк.
Ответ никогда не говорит, что из пяти это было. Так сделано намеренно: различать их значило бы превратить поверхность в оракул по ключевому материалу, у которого любой со списком догадок узнал бы, какие из них существуют.
Поэтому 401 — это одна инструкция, а не пять: предъявите работающий ключ. Какой из ваших ключей жив, отозван или истёк — вопрос к консоли; шлюз на него не ответит, кто бы ни спрашивал.