MAATRIX / Блог / LiteLLM не видит API-ключи: причины и решение

LiteLLM не видит API-ключи: причины и решение

LiteLLM не видит API-ключи: причины и решение

MAATRIX

Прокси поднят, конфиг выглядит правильно, а каждый запрос возвращает 401 и что-то про api_key. Почти всегда дело не в ключе: LiteLLM либо не подставил его из окружения, либо подставил вместе с кавычками и переносом строки, либо вы упёрлись в его собственную авторизацию — виртуальные ключи прокси. Разберём слои по порядку — с текстами ошибок и командами проверки.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Что именно «не видит» LiteLLM: два разных слоя ключей

В LiteLLM Proxy два независимых набора ключей, и жалоба «litellm api ключи не работают» в обоих случаях звучит одинаково, а лечится по-разному. Слой 1 — ключи провайдеров (OPENAI_API_KEY, ANTHROPIC_API_KEY): прокси подставляет их в исходящий запрос, ошибка приходит от провайдера. Слой 2 — виртуальные ключи прокси, те sk-..., что вы выдаёте приложениям.

Слой определяется по строке из ответа.

Текст ошибкиСлойГде искать причину
Authentication Error, Invalid proxy server token passedклиент → LiteLLMmaster_key, база ключей
OpenAIException - The api_key client option must be setLiteLLM → провайдерпеременная не долетела
403 unsupported_country_region_territoryLiteLLM → провайдердело не в ключе, а в IP

Спросите сам прокси: /health обходит все записи model_list реальным запросом.

curl -s http://127.0.0.1:4000/health/liveliness
curl -s -H "Authorization: Bearer $LITELLM_MASTER_KEY" \
     http://127.0.0.1:4000/health | jq '.unhealthy_endpoints'

/health/liveliness авторизации не требует и отвечает "I'm alive!": молчит — процесс не поднялся, ключи ни при чём. А /health в поле error вернёт дословный ответ провайдера по модели.

Ключ провайдера и config.yaml: os.environ вместо ${VAR}

Самая частая причина — синтаксис подстановки. LiteLLM не разворачивает шелловские переменные в YAML: строка api_key: ${OPENAI_API_KEY} для него обычный текст, который он честно отправит в OpenAI.

OpenAIException - Error code: 401 - {'error': {'message': 'Incorrect API key
provided: ${OPENA*****_KEY}.', 'code': 'invalid_api_key'}}

Видны $, { или } в маскированном ключе — ошибка найдена за пять секунд. Правильный синтаксис: префикс os.environ/ и имя переменной без скобок.

model_list:
  - model_name: gpt-4o-mini
    litellm_params:
      model: openai/gpt-4o-mini
      api_key: os.environ/OPENAI_API_KEY
  - model_name: claude-sonnet
    litellm_params:
      model: anthropic/claude-sonnet-4-5
      api_key: os.environ/ANTHROPIC_API_KEY
general_settings:
  master_key: os.environ/LITELLM_MASTER_KEY
  database_url: os.environ/DATABASE_URL

Ещё две ловушки в том же файле:

  • Не тот config или лишний пробел. В контейнере часто правится один файл, а монтируется другой; запись api_key: "os.environ/OPENAI_API_KEY " заставит искать переменную с пробелом в имени. Проверка — docker compose exec litellm cat /app/config.yaml.
  • Модель из базы перебивает конфиг. При store_model_in_db: true модели, добавленные через веб-интерфейс, лежат в Postgres со своим ключом и мержатся с YAML: вы правите файл, а запрос уходит со старым ключом. Что прокси реально видит: curl -s -H "Authorization: Bearer $LITELLM_MASTER_KEY" http://127.0.0.1:4000/v1/models | jq -r '.data[].id'.

При старте с --detailed_debug LiteLLM печатает LiteLLM: Proxy initialized with Config, Set models: со списком моделей. Списка нет — конфиг не прочитан, до ключей дело не дошло.

Развернуть за пару минут

Готовый образ на VPS MAATRIX: NVMe, AMD EPYC, root-доступ. Локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Развернуть LiteLLM

Переменные окружения: systemd, Docker и .env, который никто не прочитал

Вторая по частоте история: переменная есть в вашем шелле, но не у процесса. Проверяйте окружение PID, а не свой env:

PID=$(systemctl show -p MainPID --value litellm)
sudo tr '\0' '\n' < /proc/$PID/environ | grep -E 'API_KEY|MASTER|SALT|DATABASE'

Пусто — ключей у процесса нет, правки config.yaml не помогут.

systemd. Переменные подключают через EnvironmentFile, и в нём нельзя писать export: systemd считает всю строку именем переменной и пишет в журнал invalid variable name "export OPENAI_API_KEY", ignoring. Инлайновый # тоже уедет в значение ключа — комментарии только с начала строки.

[Service]
User=litellm
WorkingDirectory=/opt/litellm
EnvironmentFile=/etc/litellm/litellm.env
ExecStart=/opt/litellm/venv/bin/litellm --config /etc/litellm/config.yaml --host 127.0.0.1 --port 4000

Второй подвох — права. Если файл с ключами chmod 600 root:root, а сервис работает от пользователя litellm, юнит не стартует: в journalctl -u litellm будет Failed to load environment files: Permission denied. Лечится chown root:litellm плюс chmod 640.

Файл .env в рабочем каталоге. LiteLLM при импорте вызывает load_dotenv() и читает .env из текущего каталога процесса. В консоли вы запускаете прокси из /opt/litellm, и всё работает; systemd без WorkingDirectory стартует из /, где .env нет. Отсюда «руками работает, а сервисом — нет».

Docker Compose. Здесь подстановка ${OPENAI_API_KEY} как раз работает, но значение берётся из .env рядом с compose-файлом. Нет переменной — Compose не падает, а подставляет пустую строку и печатает WARN[0000] The "OPENAI_API_KEY" variable is not set. Defaulting to a blank string. Смотрите итог заранее: docker compose config | grep -A6 environment.

Невидимые символы: кавычки, перенос строки и CRLF

Ключ на месте, длина похожа на правду, а провайдер шлёт invalid_api_key. Значит, в значение приехало лишнее.

Кавычки из env-файла Docker. docker run --env-file передаёт строку как есть и кавычки не снимает: запись OPENAI_API_KEY="sk-proj-abc..." даёт ключ, начинающийся с символа ". Проверка: docker exec litellm sh -c 'echo "[$OPENAI_API_KEY]"' — кавычки будут видны в скобках. В env-файлах Docker пишите значения голыми.

CRLF. Если .env редактировали в Windows, каждая строка кончается на \r, и он входит в ключ:

cat -A /etc/litellm/litellm.env | head -5   # строки с ^M$ — это CRLF
printenv OPENAI_API_KEY | od -c | tail -2   # ищите \r перед \n
sed -i 's/\r$//' /etc/litellm/litellm.env   # лечение

Обрезанный ключ и невидимые пробелы. Проектный ключ OpenAI sk-proj- сегодня заметно длиннее ста символов, ключ Anthropic sk-ant-api03- — около ста восьми. Если printenv OPENAI_API_KEY | tr -d '\n' | wc -c показывает 51 или 64, вам достался огрызок. Копирование из PDF добавляет неразрывный пробел U+00A0; ищите его через LC_ALL=C grep -n '[^ -~]' /etc/litellm/litellm.env.

Сверяйте с автором ключа не значения, а хэши: printenv OPENAI_API_KEY | sha256sum.

Виртуальные ключи прокси: master_key, база и LITELLM_SALT_KEY

Ответ Authentication Error, Invalid proxy server token passed. Received API Key = sk-1234 с кодом 401 значит, что до провайдеров дело не дошло: LiteLLM не признал ключ, которым к нему постучались.

Жёсткое требование: master_key обязан начинаться с sk- — значение вида super-secret-2026 прокси мастер-ключом не примет. Генерируйте оба сразу: LITELLM_MASTER_KEY=sk-$(openssl rand -hex 24) и такой же строкой LITELLM_SALT_KEY в /etc/litellm/litellm.env.

Честный момент: если master_key не задан вовсе, LiteLLM не проверяет входящие ключи никак. Это не «прокси не видит ключи», а прокси, пускающий всех. По умолчанию он слушает 0.0.0.0:4000, и открытый шлюз с провайдерскими ключами внутри сжигает баланс за часы. Минимум гигиены — ufw allow 22/tcp, ufw deny 4000/tcp, запуск с --host 127.0.0.1, наружу только через Nginx с TLS.

Выдавать отдельные ключи можно только при подключённой базе: без DATABASE_URL эндпоинт /key/generate не работает, и единственный рабочий ключ — мастер, без бюджета и отзыва по отдельности. Postgres 17 на том же сервере закрывает вопрос:

curl -s -X POST http://127.0.0.1:4000/key/generate \
  -H "Authorization: Bearer $LITELLM_MASTER_KEY" \
  -d '{"models":["gpt-4o-mini"],"max_budget":25,"duration":"30d","rpm_limit":60}'

Три сценария, когда ключ есть, но «не работает»:

  • Модель не разрешена ключу — ответ прямо это и говорит; лечится через /key/update.
  • Истёк duration или выбран max_budget — смотрите spend и expires в выводе /key/info.
  • Сменили LITELLM_SALT_KEY. Это ломает всё и молча. Ключи провайдеров, добавленные через веб-интерфейс, лежат в базе зашифрованными на salt-ключе, а если он не задан — на мастер-ключе. Меняете мастер, не выставив salt заранее, — расшифровать старые записи прокси уже не может, и все модели из базы отдают ошибки авторизации, хотя вы «ничего не трогали». Задавайте LITELLM_SALT_KEY до первого добавления моделей: восстановить его нельзя.

Ключ верный, а провайдер всё равно отказывает

Бывает, что значение доехало идеально, а 401 или 403 всё равно приходит. Причины уже не в LiteLLM.

География IP. Самый обидный случай: ключ рабочий, но сервер стоит в России. OpenAI отвечает 403 {"code":"unsupported_country_region_territory"}, Google для Gemini — {"code":400,"message":"User location is not supported for the API use.","status":"FAILED_PRECONDITION"}, Anthropic отдаёт сухой 403 без подробностей. Перегенерацией ключа это не чинится: провайдер смотрит на исходящий адрес. Гнать трафик через сторонний HTTP-прокси можно, но это лишняя точка отказа, риск словить SSL: CERTIFICATE_VERIFY_FAILED на MITM-прокси и чужой узел, через который идут ваши ключи.

Права и область ключа. Ключи OpenAI sk-proj- привязаны к проекту: если модель в нём не включена, придёт не 401, а 404 The model 'gpt-4o' does not exist or you do not have access to it. У ограниченного ключа без права на inference — Missing scopes: model.request. Это не «LiteLLM не видит ключ», а «ключ не тот».

Другой способ аутентификации. Azure OpenAI требует AZURE_API_KEY вместе с api_base и api_version, Bedrock — aws_access_key_id с aws_secret_access_key и aws_region_name, Vertex AI — JSON сервис-аккаунта в vertex_credentials. Ключ в поле api_key там даёт ошибку, похожую на «ключ не подхватился». И помните: insufficient_quota с кодом 429 — это пустой баланс, а не рейт-лимит.

Nginx съел заголовок. auth_basic использует тот же заголовок Authorization, что и Bearer-ключ клиента: включив базовую авторизацию, вы отбираете у клиента возможность передать виртуальный ключ. А заголовки с подчёркиванием Nginx отбрасывает: клиенты с x_api_key не будут услышаны без underscores_in_headers on;. Заодно поставьте proxy_read_timeout 600s; и proxy_buffering off;.

Какой сервер под LiteLLM брать в MAATRIX

LiteLLM — шлюз, а не инференс: он не считает модели, а перекладывает запросы и ждёт ответа. Нагрузка целиком сетевая — важнее адрес, канал и память, а не процессор.

Минимум: 1 vCPU, 1–2 ГБ RAM, 20 ГБ NVMe. Хватает для конфигурации без базы и десятка одновременных запросов. Ограничение честное: без Postgres вы живёте на одном мастер-ключе. И базу рядом при 1 ГБ ставить нельзя: воркер LiteLLM занимает 300–450 МБ RSS, и первый всплеск закончится процессом, убитым по OOM.

Комфортный вариант: 2 vCPU, 4 ГБ RAM, 40–60 ГБ NVMe. Помещаются локальный Postgres, три-четыре воркера (--num_workers 4), логи и метрики: виртуальные ключи с бюджетами, история трат, спокойные перезапуски. Пойдут десятки тысяч запросов в день — берите 8 ГБ: узкое место не вычисления, а соединения.

Локация — США (Нью-Йорк). Прямое следствие раздела про 403: с американского адреса OpenAI, Anthropic и Google отвечают штатно, а маршрут до их API короткий. Отсюда же чистый IP — адрес не тащит репутацию соседей по подсети. Задержка Москва — Нью-Йорк добавляет к первому токену около сотни миллисекунд, на стриминге почти незаметно; команде в Европе стоит посмотреть на UK или FR, RTT до них меньше в разы. Российская локация тут не годится из-за тех самых региональных ограничений: точкой входа для клиентов она полезна, но за ключами всё равно идти через зарубежный узел.

Оплата — картами российских банков, по СБП, криптовалютой или токеном MAAT: иностранная карта не нужна, хотя сервер стоит в США. Выдаётся чистая Ubuntu 24.04; дальше вы ставите LiteLLM в venv или поднимаете контейнер — и сразу кладёте LITELLM_MASTER_KEY с LITELLM_SALT_KEY в менеджер паролей.

Развернуть за пару минут

Готовый образ на VPS MAATRIX: NVMe, AMD EPYC, root-доступ. Локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Развернуть LiteLLM

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Частые вопросы

Ключ работает в curl напрямую к OpenAI, но не через LiteLLM. Почему?

У сервиса другое окружение, чем у вашей сессии: проверьте sudo tr '\0' '\n' < /proc/$(systemctl show -p MainPID --value litellm)/environ | grep API_KEY. И убедитесь, что в config.yaml стоит api_key: os.environ/OPENAI_API_KEY, а не ${OPENAI_API_KEY}.

После смены master_key все модели отвалились с ошибками авторизации. Что делать?

Скорее всего, LITELLM_SALT_KEY не был задан и ключи провайдеров шифровались на мастер-ключе. Верните прежний мастер-ключ, задайте salt-ключ, перезаведите модели и только потом меняйте мастер.

Нужна ли база, чтобы раздать ключи команде?

Да. Без DATABASE_URL эндпоинт /key/generate недоступен и работает только мастер-ключ — без бюджетов, лимитов и отзыва по одному. Postgres рядом просит около 1 ГБ памяти сверху.

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.