# Аутентификация (/docs/authentication)
> API-ключи Hubris в формате sk-gw-, заголовок Bearer, безопасное хранение и ротация.
import { EndpointBadge } from '@/components/docs/endpoint-badge';
import { EnvVar } from '@/components/docs/env-var';
Все запросы к Hubris авторизуются API-ключом в заголовке `Authorization: Bearer sk-gw-<32-hex>`. Это совместимо с OpenAI SDK и большинством ML-инструментов.
## Создание ключа
1. Войдите в [/keys](/keys).
2. Нажмите «Новый ключ», задайте описательное имя (например, «production-bot», «dev-laptop»).
3. **Скопируйте ключ сразу** — он показывается один раз. Hubris хранит только хеш ключа (sha256), восстановить полное значение невозможно.
4. Сохраните в менеджер паролей или переменную окружения .
## Формат ключа
`sk-gw-` + 32 шестнадцатеричных символа (16 байт случайных данных). Пример:
```
sk-gw-a1b2c3d4e5f6789012345678901234ab
```
В UI показывается префикс `sk-gw-a1b2...34ab` — этого достаточно, чтобы отличить ключи между собой, но недостаточно для использования.
## Использование в запросах
Заголовок: `Authorization: Bearer sk-gw-...`. Например, через cURL:
```bash
curl https://api.hubris.pw/v1/models \
-H "Authorization: Bearer sk-gw-..."
```
В OpenAI SDK ключ передаётся через параметр `api_key` или env-переменную:
```python
from openai import OpenAI
client = OpenAI(api_key="sk-gw-...", base_url="https://api.hubris.pw/v1")
```
```ts
import OpenAI from "openai";
const client = new OpenAI({ apiKey: "sk-gw-...", baseURL: "https://api.hubris.pw/v1" });
```
## Что делать, если ключ скомпрометирован
1. Откройте [/keys](/keys).
2. Найдите подозрительный ключ по префиксу или имени.
3. Нажмите «Отозвать» — ключ немедленно перестаёт работать.
4. Создайте новый ключ и обновите его во всех местах использования.
## Безопасное хранение
* **Никогда не коммитьте ключи в git** — добавьте `.env` файлы в `.gitignore`.
* **Не отправляйте ключи** в логи, аналитику или error-tracking. Hubris не логирует содержимое запросов, но сторонние сервисы могут.
* **Ротируйте ключи** при смене работы, выходе сотрудника, передаче кода подрядчику. Это секунда работы — старый отозвать, новый создать.
* **Один ключ — одна цель.** Отдельные ключи на dev / staging / production упрощают аудит и быструю ревокацию при инциденте.
## Лимиты на ключе
В стандартном тарифе у пользовательских ключей **нет искусственных лимитов** — расход ограничен только балансом аккаунта. Служебные ключи (CI, скрипты) могут получить дневной лимит в копейках при создании — это страховка от утечки. Подробнее — в разделе про rate limits (готовится).
## Ошибки аутентификации
Без заголовка или с невалидным ключом возвращается **401**:
```json
{
"error": {
"message": "Missing API key",
"type": "invalid_request_error",
"code": "invalid_api_key"
}
}
```
Если ключ был отозван — то же самое: 401 с `code: "invalid_api_key"`.
## Что дальше
* [Быстрый старт](/docs/quickstart) — первый запрос с вашим новым ключом.
* [Каталог моделей](/models) — выбрать модель.
* [Биллинг](/billing) — пополнение баланса, минимальный остаток.
---
# Changelog (/docs/changelog)
> Записи о breaking changes API, новых моделях, обновлениях платформы Hubris.
Записи о значимых изменениях платформы, документации и SDK. Полная история коммитов — внутренняя.
## 2026-08-12 — Пять новых моделей в студии видео
В [студии видео](/videos) стало на пять моделей больше: **Seedance 2.5** (сцены до 30 секунд), **FLUX.3 Video** (720p и 1080p, ключевые кадры), **Hailuo 3** (2K), **Runway Gen-4.5** и **Grok Imagine Video 1.5** (до 1080p, форматы 3:2 и 2:3). У каждой проверены звук, доступные форматы и оживление картинки первым кадром — композер предлагает только то, что модель действительно умеет.
## 2026-07-27 — Озвучка текста: 15 моделей и готовый аудиофайл в ответе
Запустили [`/v1/audio/speech`](/docs/api/speech): отправляете текст — получаете сразу байты аудио (mp3 или pcm), тем же ключом `sk-gw-...`. Пригодится для голосовых ответов бота, аудиоверсий статей и рассылок, реплик персонажей: 15 моделей озвучки — MAI Voice, Aura-2, MiniMax Speech, Kokoro, Orpheus и другие, у каждой свой набор голосов (от двух до девяноста), список — в карточке модели [каталога](/models). Официальный OpenAI SDK работает после смены `base_url`. Тарификация — за символы отправленного текста, поэтому цена известна заранее; оплата в рублях по факту запроса.
## 2026-07-26 — Аудио прямо в чате: модель слушает запись и отвечает голосом
В [`/v1/chat/completions`](/docs/api/chat-completions) появился звук в обе стороны. Приложите запись к сообщению content-частью `input_audio` — и модель ответит на вопросы о ней: о чём говорили, каким тоном, что за шум на фоне; это не расшифровка, а рассуждение, которое можно совмещать с tool calling и структурированным выводом. В обратную сторону работает голосовой ответ: `modalities: ["text","audio"]` вместе со `stream: true`, аудио приходит чанками с текстовой расшифровкой. Оплата в рублях по факту, аудио-токены — по своей ставке модели; форматы, коды ошибок и примеры на cURL/Python/TypeScript — в [гиде](/docs/features/audio).
## 2026-07-26 — Распознавание речи: 12 моделей транскрибации
Запустили [`/v1/audio/transcriptions`](/docs/api/transcriptions): Whisper, GPT-4o Transcribe, Chirp 3, Voxtral, Nova-3 и другие — 12 моделей превращают аудио в текст тем же ключом `sk-gw-...`. Официальный OpenAI SDK работает после смены `base_url`, файлы до 25 МБ (WAV/MP3/FLAC/M4A/OGG/WebM/AAC). Цены — по фактическим единицам моделей: от 0,17 ₽ за минуту записи, оплата в рублях по факту; весь список — в [каталоге](/models) по фильтру «Транскрибация».
## 2026-07-25 — Контраст интерфейса по WCAG AA
Пересобрали палитру под требования доступности: мелкие подписи, реквизиты в футере и текст на фиолетовых кнопках теперь читаются при слабом зрении, ярком солнце и на плохих экранах. Кнопки стали чуть глубже по цвету, третичный текст — светлее. Все публичные страницы проходят WCAG 2.1 AA (контраст ≥ 4.5:1 для обычного текста).
## 2026-07-24 — Кеширование промптов Claude через `/v1/chat/completions`
[Кеширование промптов](/docs/features/prompt-caching) для Anthropic-моделей теперь работает и через OpenAI-совместимый [`/v1/chat/completions`](/docs/api/chat-completions) — маркер `cache_control` доходит до модели из любого OpenAI SDK, не только через `/v1/messages`. Закэшированная часть промпта при повторных запросах тарифицируется примерно в десять раз дешевле, а ответ на длинном контексте начинается заметно быстрее. Если вы передавали `cache_control` по нашей документации — с этого момента он действует, ничего менять не нужно.
## 2026-07-24 — Claude Code через Hubris: Claude 5 и любые чат-модели каталога
Официальный агент Anthropic — Claude Code, в терминале и как расширение VS Code, — подключается к Hubris за пару минут: две переменные окружения, оплата в рублях. Доступно всё семейство Claude (Fable 5, Sonnet 5, Opus 4.8, Haiku 4.5) с переключателем глубины рассуждений, а через тот же [`/v1/messages`](/docs/api/messages) — и любые чат-модели каталога: GPT, Gemini, DeepSeek и другие. Готовый конфиг, настройка VS Code и нюансы — в [гиде по подключению](/docs/integrations/claude-code).
## 2026-07-11 — Публичный API генерации видео
Запустили асинхронный [`/v1/videos`](/docs/api/videos): создаёте задачу, опрашиваете статус, скачиваете mp4 — тем же ключом `sk-gw-...`, оплата в рублях по факту. Готовый ролик хранится 72 часа. Scopes `videos:write` / `videos:read`, поддержка `Idempotency-Key`. Видео-модели видны в каталоге через `GET /v1/models?output_modalities=video` и `GET /v1/videos/models` (цены в рублях).
## 2026-07-08 — Студия генерации видео
Запустили [студию видео](/videos): Veo 3.1, Sora 2 Pro, Kling 3.0, Seedance 2.0 и другие — всего 16 моделей в одном окне. Опишите сцену — получите ролик со звуком, до 4K и до 20 секунд; картинку можно «оживить», сделав её первым кадром. Цена видна до запуска, оплата в рублях по факту, готовые ролики сохраняются в «Мои генерации».
## 2026-07-07 — Предпросмотр результата и ID генерации
Клик по готовой картинке в [студии](/images) открывает крупный предпросмотр: полный промпт (копируется кликом), стрелки между результатами и заметная кнопка «Скачать». У каждой генерации теперь виден её ID — скопируйте его кликом и приложите к обращению в поддержку, если что-то пошло не так: найдём запись мгновенно.
## 2026-07-07 — Стили с превью и несколько картинок за раз
В [студии картинок](/images) — 24 стиля с живыми превью: наведите, чтобы увидеть готовый промпт, кликните — он окажется в строке ввода. Модели GPT Image теперь честно генерируют до 4 изображений за один запрос и учитывают выбранное качество, а Enter в строке ввода сразу запускает генерацию.
## 2026-07-07 — Премиум-модели картинок в студии
Добавили в [генератор](/images) флагманские image-модели: **FLUX.2** (Max/Pro/Flex), **Seedream 4.5** (фотореализм), **Recraft V4.1** (дизайн и иллюстрации, + Pro) и **Grok Imagine** — генерируются прямо в UI, оплата в рублях по факту генерации. Для FLUX русский промпт автоматически переводим на английский (модель не понимает кириллицу) — пишете на английском, оставляем как есть. Результат по центру экрана, промпт — под картинкой.
## 2026-07-06 — Новая студия генерации изображений
Переделали [генератор картинок](/images) в полноэкранную студию. Выбираете реальную модель (Nano Banana, GPT Image и другие) — соотношение сторон, разрешение или качество подстраиваются под её возможности прямо в строке ввода. Можно приложить картинки-референсы, а все ваши генерации собираются в ленту с промптами. Оплата в рублях через СБП, первая картинка — в пару кликов.
## 2026-05-28 — OAuth 2.0 + PKCE для сторонних приложений (Phase A)
Сторонние приложения могут подключаться к Hubris через стандартный OAuth flow с тремя scopes (`chat:write`, `models:read`, `balance:read`) и опциональным дневным лимитом на токен. Claude Desktop, Claude.ai, Cursor и другие MCP-клиенты теперь подключаются нативно через «Add custom connector» UI — без `mcp-remote` шима. Подробности и Python-пример: [/docs/integrations/oauth](/docs/integrations/oauth).
## 2026-05-26 — MCP-сервер для Claude Desktop / Claude Code / Cline (Beta)
Подключите Hubris к AI-агенту по протоколу [Model Context Protocol](/docs/integrations/mcp): URL `https://api.hubris.pw/mcp`, тот же Bearer-ключ `sk-gw-...`. Агент получает каталог моделей, баланс и запросы к LLM (`chat/complete` — полный паритет с `/v1/chat/completions`) через одно подключение. Биллинг — как обычно. Фича в Beta — поведение и набор инструментов могут поменяться.
## 2026-05-25 — Чат в личном кабинете (Beta)
В ЛК появился раздел [**Чат**](https://hubris.pw/chat) — браузерный интерфейс к любой из 300+ моделей каталога. Альтернатива внешним чат-клиентам, когда нужно «быстро спросить» без curl и API-ключей.
* **История хранится только в этом браузере.** Чаты живут в localStorage — сервер Hubris их не видит и не может восстановить. В журнале запросов остаются только метаданные (модель, токены, стоимость), как и для API-вызовов. При очистке данных браузера или смене устройства чаты пропадут — для бэкапа есть «Скачать всё» и импорт из JSON в настройках чата.
* **Одна модель на чат.** Выбирается при создании, в шапке отображается название и провайдер. Чтобы попробовать другую модель — создаётся новый чат.
* **Edit и regen, без веток.** Любое сообщение пользователя можно отредактировать — хвост чата перепрогоняется заново (с подтверждением, если удаляется ≥2 сообщений). Под последним ответом ассистента — кнопка «Перегенерировать».
* **Лимиты.** До 50 сообщений в одном чате (мягкое предупреждение с 40), до 30 чатов в сайдбаре. При достижении — баннер «Создайте новый чат» с кнопкой «Скачать этот». Защита от тормозов DOM и переплаты за input-replay (каждый новый запрос отправляет всю историю в модель).
* **Кликабельные источники для Perplexity Sonar.** Маркеры `[1]`, `[2]` в ответах web-search моделей превращаются в маленькие фиолетовые pill-ссылки на оригинальные источники. Под ответом — полный список URL с hostname и путём.
* **Поддержка LaTeX-формул.** Inline `\(...\)` и блочные `\[...\]` рендерятся через KaTeX — нужно для reasoning-моделей (Sonar Reasoning, Sonar Deep Research), которые форматируют числа и формулы в математической нотации.
* **Stop с честным биллингом.** Кнопка «Остановить» во время стрима абортит чтение, но сервер дочитывает апстрим до конца и списывает полную стоимость (соответствие SPEC §10). В подсказке у кнопки — явное «не отменяет списание».
* **Списание как обычно, без прогноза.** Каждое сообщение оплачивается из основного баланса так же, как `/v1/*` запрос. При нулевом балансе — баннер «Пополнить», следующее сообщение не отправляется до пополнения.
* **Privacy-онбординг.** При первом открытии — диалог с явным объяснением, что чаты не покидают браузер. Один клик «Понятно» — больше не показывается.
* **Полный экран на десктопе.** Раздел занимает всю ширину/высоту окна — без узкой колонки и шапки дашборда. Колонка сообщений ограничена 760px для комфортного чтения.
Доступ — пункт «Чат» в сайдбаре ЛК (бейдж Beta).
## 2026-05-25 — Активированы Perplexity Sonar и другие web-search модели
5 моделей семейства **Perplexity Sonar** (`sonar`, `sonar-pro`, `sonar-pro-search`, `sonar-reasoning-pro`, `sonar-deep-research`) теперь активны в [каталоге](/models) и доступны через `/v1/*` и встроенный чат. Раньше были деактивированы — пока не было уверенности, что биллинг корректно покрывает web-search surcharge.
* **Биллинг через `usage.cost` от провайдера.** Стоимость каждого запроса (включая web-search per-request surcharge $0.005) приходит готовой от апстрима и попадает в `usage_logs.cost_kopecks` ровно так же, как для обычных моделей. Никаких отдельных формул на нашей стороне.
* **Sentinel-warning в логах.** Если апстрим вдруг вернёт `usage.cost=0` при `web_search_requests > 0` — пишем warn-лог, чтобы поймать проблему до того, как её заметят пользователи.
* **В чате `/chat` — кликабельные `[N]`.** Sonar возвращает массив `citations[]` URL'ов; чат превращает маркеры в тексте в маленькие фиолетовые pill-ссылки и показывает полный список под ответом.
## 2026-05-24 — Все ошибки `/v1/*` и `/api/internal/*` теперь на русском
`error.message` во всех публичных эндпоинтах переведены на формальный русский язык:
* `Недостаточно средств на балансе.`
* `Модель не найдена: `
* `Неверный API-ключ` / `API-ключ не передан`
* `Курс ЦБ РФ временно недоступен`
* `Сервис провайдера временно недоступен` (раньше — `Upstream returned an error`)
* `Превышено время ожидания апстрима` / `Превышен дневной лимит расхода для этого API-ключа`
* и другие.
`error.code` (`insufficient_balance`, `model_not_found`, `upstream_error`, `daily_limit_exceeded`, и т.д.) **остаётся стабильным машинно-читаемым контрактом** — английский. Если ваш код матчит ошибки по `code` — изменений не требуется. Если по `message` — обновите матч на русский (либо мигрируйте на `code`).
В песочнице на странице модели также убрали захардкоженные русские подсказки на фронте — теперь рендерится ровно то, что отдал бэкенд. При ошибке «недостаточно средств» рядом с сообщением появляется ссылка «Пополнить баланс» → `/billing`.
## 2026-05-23 — `usage.cost` теперь в копейках
В ответах всех `/v1/*` эндпоинтов (`/messages`, `/chat/completions`, `/responses`, `/embeddings`) поле `usage.cost` теперь содержит **итоговую цену запроса в копейках** (integer) — ту же сумму, что списывается с баланса и попадает в [/v1/usage](/docs/api/usage). Раньше там приходил raw cost в долларах. Применяется и к потоковому режиму — копейки попадают в финальный `message_delta` / `chat.completion.chunk` / `response.completed`.
## 2026-05-23 — Поддержка Claude Code: эндпоинт `/v1/messages`
* **Новый эндпоинт `POST /v1/messages`** — совместимый с [Anthropic Messages API](https://docs.anthropic.com/claude/reference/messages_post). Через него работают [Claude Code](/docs/integrations/claude-code), официальные SDK Anthropic для Python и TypeScript, любые сторонние клиенты, ожидающие Anthropic-формат.
* **Аутентификация: и Bearer, и `x-api-key`.** Поддержаны оба заголовка одновременно — Claude Code использует `ANTHROPIC_AUTH_TOKEN` через `Authorization: Bearer`, а Anthropic-SDK по умолчанию шлёт `x-api-key`. Hubris принимает обе формы для любого `/v1/*` эндпоинта.
* **Маппинг имён моделей.** Принимаются и Anthropic-стиль (`claude-sonnet-4-5`, `claude-3-5-sonnet-20241022`, `claude-haiku-4-5-latest`), и каноничные Hubris-имена (`anthropic/claude-sonnet-4.6`). В ответе поле `model` восстанавливается в то имя, что прислал клиент — для согласованности логов.
* **Полный набор Anthropic-фич: vision, tool use, prompt caching, extended thinking.** Все content-блоки (`text`, `image`, `tool_use`, `tool_result`, `document`, `thinking`) и параметры (`cache_control`, `tool_choice`, `thinking`, `stop_sequences`) проходят без изменений. Биллинг учитывает `usage.cost`, который провайдер возвращает с поправкой на cache hit / cache write.
* **Anthropic-нативный SSE-стриминг.** События `message_start` / `content_block_*` / `message_delta` / `message_stop` приходят в нативном формате. На разрыве соединения Hubris дочитывает апстрим до конца и списывает стоимость, без бесплатных токенов.
* **Anthropic-формат ошибок.** Вместо OpenAI-стиля `{error:{...}}` возвращаем `{type:"error",error:{type,message}}` с корректным `error.type` (`invalid_request_error`, `authentication_error`, `rate_limit_error`, `api_error`, `not_found_error`, `billing_error`, `timeout_error`). SDK Anthropic не падают.
* **Statusline-скрипт для Claude Code.** Bash (`https://hubris.pw/scripts/claude-statusline.sh`) и кросс-платформенный Node-вариант (`https://hubris.pw/scripts/claude-statusline.mjs`) — выводит активную модель и расход за сегодня в нижней строке TUI. Подробности — в [гиде по Claude Code](/docs/integrations/claude-code#statusline-с-балансом).
* **Документация.** [Гид «Подключение Claude Code»](/docs/integrations/claude-code) — установка, конфигурация, выбор моделей, statusline, FAQ. [API-референс POST /v1/messages](/docs/api/messages) — параметры, форматы, примеры.
* **Ограничение MVP.** Privacy Mode (маскирование PII) на `/v1/messages` **пока не работает** — фича сложнее в реализации для Anthropic-формата content-блоков. Если нужна маскировка, используйте `/v1/chat/completions` с заголовком `X-Hubris-Privacy-Mask`. Поддержка появится отдельно.
## 2026-05-23 — Каталог моделей: +23 модели генерации изображений
* **Подключены image-модели из расширенного каталога:** Flux 2 (pro/max/flex/klein-4b), Recraft v3/v4/v4.1 (11 вариантов: pro, vector, utility), Sourceful Riverflow v2 (pro, fast, preview-серии), xAI Grok Imagine, ByteDance Seedream 4.5, gemini-2.5-flash-image-preview. Полный список — в [каталоге](/models) с фильтром «Output: image».
* **Корректное отображение цены за изображение в каталоге.** Раньше unit-priced модели показывали «0 ₽ за 1М токенов» (UX-баг). Теперь — «X ₽ за изображение» / «X ₽ за мегапиксель» в зависимости от тарификации модели. То же поле `pricing.per_unit[]` доступно через `GET /v1/models` для разработчиков.
* **Документация: расширение [Генерации изображений](/docs/features/image-generation).** Появилась справка по `image_config` параметрам конкретных провайдеров: Recraft `style` / `text_layout` / `rgb_colors`, Sourceful `font_inputs` / `super_resolution_references`. Раздел «Какую модель когда выбирать».
## 2026-05-22 — Песочница умеет генерировать картинки и уважает Privacy Mode
* **Image-gen в песочнице.** На страницах моделей `google/gemini-2.5-flash-image`, `openai/gpt-image-*`, `google/gemini-3.1-flash-image-preview` и других моделей с картиночным выходом песочница теперь возвращает сгенерированное изображение прямо в чат-пузырь. Стоимость и токены считаются как для обычной генерации — без скрытых наценок.
* **Превью и сохранение.** Клик по сгенерированной картинке открывает полноразмерный лайтбокс. Кнопка «Сохранить» скачивает PNG (или WEBP/JPEG — расширение берётся из data URL) с именем `hubris-.`. ESC или клик мимо — закрытие. Файл существует только у вас: на серверах Hubris ничего не сохраняется, в логах остаются только метаданные биллинга.
* **Песочница для image-only моделей.** Модели, которые отдают только картинку без текста (Flux / SDXL-семейство), теперь тоже доступны в песочнице.
* **Privacy Mode теперь работает и в песочнице.** Раньше включённая в `/security` маска применялась только к запросам через API-ключ — а отправки прямо со страницы модели уходили без маски. Теперь оба пути идентичны: ФИО, телефоны, email, ИНН, паспорта и т.д. скрываются до отправки модели, а в ответе восстанавливаются обратно. На stream-ответах работает та же построчная подмена, что и в `/v1/chat/completions`.
## 2026-05-22 — Server-tools, новая секция «Интеграции», доки фич
* **API: новый namespace `hubris:*` для server-tools.** В `tools[]` запроса `/v1/chat/completions` и `/v1/responses` появилась поддержка `{ "type": "hubris:web_search" }` — это канонический способ включить встроенный веб-поиск. Гид: [Поиск в интернете](/docs/features/web-search). Старые namespace-варианты продолжают работать ради обратной совместимости.
* **Новая секция документации «Интеграции» (9 страниц).** Подключение Hubris к популярным AI-инструментам: [Cline](/docs/integrations/cline), [Roo Code](/docs/integrations/roo-code), [Kilo Code](/docs/integrations/kilo-code), [OpenCode](/docs/integrations/opencode), [Qwen Code CLI](/docs/integrations/qwen-code), [Hermes Agent](/docs/integrations/hermes-agent), [OpenClaw](/docs/integrations/openclaw), [Dify](/docs/integrations/dify), [n8n](/docs/integrations/n8n). У каждого инструмента — точные шаги настройки base URL и API-ключа, советы по выбору модели и решение типовых проблем.
* **Новые гиды в разделе «Возможности»:**
* [Вызов инструментов (tool calling)](/docs/features/tool-calling) — жизненный цикл tool-сессии, `tool_choice`, параллельные вызовы, стриминг, SDK-примеры.
* [Структурированный вывод](/docs/features/structured-output) — `json_object` и `json_schema` строгий режим, Pydantic / Zod helper'ы, обработка refusal'ов.
* [Поиск в интернете](/docs/features/web-search) — встроенный server-side инструмент, аннотации источников, стриминг.
* [Изображения на вход (Vision)](/docs/features/vision) — multimodal-ввод через `image_url` content-parts, URL и base64, параметр `detail`, биллинг.
* [Выбор модели программно](/docs/features/model-selection) — фильтрация каталога через `GET /v1/models`, обработка `404 model_not_found`, версионированные slug'и.
* [Стриминг ответов](/docs/features/streaming) — когда включать `stream: true`, парсинг в Python / Node / Vercel AI SDK / голом fetch, UX-паттерны (типографический эффект, прогресс на reasoning-моделях, отмена).
* [Мониторинг расходов](/docs/features/usage-tracking) — `GET /v1/usage` для cron-отчётов, бюджет-алёртов в коде агента, разбивки по проектам, `BigInt` для копеечной точности.
* [Обработка ошибок](/docs/features/error-handling) — стратегия retry по кодам (что ретраить, что нет), `tenacity` пример на Python, factory на TS, маппинг внутренних кодов в user-facing сообщения.
* **Боковое меню документации согласовано с дашбордом.** Шрифт, отступы и компоновка строк сайдбара `/docs` теперь матчат сайдбар личного кабинета (`13.5px` / `6×10` / иконки `15px`). До этого fumadocs-дефолты раздували сайдбар, и переходы между ЛК и доками выглядели визуально несогласованно.
## 2026-05-12 — Документация под общим хедером, AI-friendly выгрузка
* **Документация теперь под общей навигацией сайта.** В шапке `/docs` появилось основное меню Hubris (Главная, Модели, Использование, API-ключи, Биллинг). Для авторизованных — текущий баланс и меню пользователя. Из ЛК больше не нужно открывать документацию «в отрыве».
* **Русский интерфейс fumadocs.** `«На этой странице»`, `«Следующая»`, `«Предыдущая»`, `«Редактировать на GitHub»`, плейсхолдер поиска — теперь по-русски.
* **AI-friendly выгрузка документации.**
* Любую страницу `/docs/` можно открыть как plain-markdown: добавьте `.md` или `.mdx` к URL (например `https://hubris.pw/docs/quickstart.md`).
* В шапке каждой страницы — кнопки **«Скопировать как Markdown»** и **«Открыть в…»** (ChatGPT / Claude / .md-версия).
* `https://hubris.pw/llms.txt` — индекс всей документации в формате [llmstxt.org](https://llmstxt.org), для AI-агентов.
* `https://hubris.pw/llms-full.txt` — полный дамп всех страниц одним файлом, удобно скармливать в context-окно модели.
## 2026-05 — Запуск публичной документации
Опубликованы первые версии разделов:
* С чего начать: Quickstart, Аутентификация, Миграция с OpenAI.
* Базовые концепции: Модели, Цены, Биллинг, Ошибки, Rate limits, Конфиденциальность.
* API Reference: `/v1/models`, `/v1/chat/completions`, `/v1/responses` (BETA), Streaming.
* Возможности: Reasoning-токены, кеширование промптов.
* Фреймворки: OpenAI SDK Python/Node, Vercel AI SDK, LangChain Python/JS.
API сам по себе — без изменений с релизной версии 1.0. Все стабильные эндпоинты под `/v1/*` остаются совместимыми.
## Подписка на обновления
Пока что — следите за этой страницей. RSS / email-рассылка в работе.
Для критических изменений (breaking change в API, отзыв модели из каталога) — будем дополнительно слать на email вашего аккаунта.
## Если нужно, чтобы конкретная модель не пропала
Если вы строите интеграцию вокруг конкретной модели и боитесь, что её уберут — напишите на [support@hubris.pw](mailto:support@hubris.pw), мы согласуем, как минимум, заранее предупредим о выводе.
---
# FAQ (/docs/faq)
> Часто задаваемые вопросы про Hubris API, биллинг, ключи, поддерживаемые модели.
## Что такое Hubris?
Hubris — единый OpenAI-совместимый API ко всем ведущим LLM. Один ключ, оплата в рублях через СБП, OpenAI-совместимый формат запросов и ответов. Подробнее — на [главной](/docs).
## Через какие модели вы работаете?
Мы агрегатор. Маршрутизируем запросы к лучшим моделям мира — OpenAI, Anthropic, Google, DeepSeek, Mistral и другим — через единое API. Конкретные торговые отношения с поставщиками — наша внутренняя коммерческая информация. Полный список доступных моделей с ценами — в [каталоге](/models).
## Сколько стоит?
Pay-as-you-go без подписок. Платите только за фактически использованные токены, в рублях. Для каждой модели — своя цена за 1М входных и выходных токенов, она видна в каталоге. Подробности расчёта — на странице [Цены](/docs/concepts/pricing).
## Как пополнить баланс?
Через СБП на странице [Биллинг](/billing). Минимально 300 ₽, максимально 100 000 ₽ за одну операцию. Зачисление автоматическое в течение 30 секунд. Подробнее — на странице [Биллинг](/docs/concepts/billing).
## Что если баланс ушёл в минус?
Стоимость запроса известна только после ответа модели. Поэтому возможна ситуация: на балансе было 50 ₽, запрос стоил 60 ₽, баланс ушёл в −10 ₽. Это допустимо на одну операцию. Следующий запрос с отрицательным балансом вернёт 402 — пополнение разблокирует следующие запросы.
## Можно ли получить чек / счёт-фактуру?
Для физических лиц — нет, оплата через СБП с подтверждением банка достаточна. Для юридических лиц — пишите на [support@hubris.pw](mailto:support@hubris.pw), обсудим выставление счёта и закрывающие документы.
## Сохраняете ли вы мои запросы?
Нет. Содержимое запросов и ответов не пишется ни в логи, ни в БД. Передаётся провайдеру модели по TLS и сразу забывается. Подробнее — на странице [Конфиденциальность](/docs/concepts/privacy).
## Какие провайдеры моделей видят мои данные?
Перечень провайдеров, которым потенциально передаётся содержимое запроса, — в [Политике конфиденциальности](/legal/privacy), раздел 8. Кратко: модели Anthropic — Anthropic, OpenAI — OpenAI, Google — Google, и так далее. Каждый имеет свою политику обработки.
## Какие лимиты на ключе?
В стандартном тарифе у пользовательских ключей нет искусственных лимитов — расход ограничен только балансом. Дневной лимит на конкретном ключе можно задать или поменять прямо на странице [API-ключи](/keys): поле «Дневной лимит, ₽» в форме создания и кнопка «изменить» рядом с активным ключом. Подробнее — [Rate limits](/docs/concepts/rate-limits).
## Что делать, если ключ скомпрометирован?
Откройте [/keys](/keys), найдите подозрительный ключ, нажмите «Отозвать». Создайте новый и обновите его во всех местах. Подробнее — [Аутентификация](/docs/authentication).
## Поддерживаете ли вы embeddings? Generation картинок? Audio?
**Embeddings** — да, через [`POST /v1/embeddings`](/docs/api/embeddings). Доступны OpenAI `text-embedding-3-*`, Google Gemini, BGE, MiniLM и другие embedding-модели в каталоге.
**Image generation** и **Audio (Whisper / TTS)** — пока нет. Появятся в ближайших обновлениях. Если нужно срочно — напишите на [support@hubris.pw](mailto:support@hubris.pw), приоритезируем.
## Где changelog?
[/docs/changelog](/docs/changelog) — записи о breaking changes API, новых моделях, важных обновлениях.
## Куда писать про баги?
[support@hubris.pw](mailto:support@hubris.pw) — приложите модель, время запроса в UTC и e-mail аккаунта. Найти конкретный запрос потом поможет [/usage](/usage).
## Можно ли запустить self-hosted?
Сейчас нет. Если у вас регулируемые данные (медицинские, финансовые, гос. тайна) — пишите на [support@hubris.pw](mailto:support@hubris.pw), обсудим архитектуру под ваши требования.
---
# Документация Hubris (/docs)
> OpenAI-совместимый LLM-агрегатор с оплатой в рублях. Один ключ ко всем ведущим моделям.
import { Cards, Card } from 'fumadocs-ui/components/card';
import { Rocket, KeyRound, Boxes, BookOpen, GitCompare, Receipt } from 'lucide-react';
import { ModelLink } from '@/components/docs/model-link';
Hubris — единый OpenAI-совместимый API ко всем ведущим языковым моделям. Маршрутизируем запросы к лучшим моделям мира, с автоматическим переключением на запасную модель при сбоях. Оплата — в рублях через СБП.
## С чего начать
} title="Быстрый старт" href="/docs/quickstart" description="Первый запрос за 60 секунд: cURL, Python, TypeScript." />
} title="Аутентификация" href="/docs/authentication" description="API-ключи в формате sk-gw-, ротация, безопасное хранение." />
} title="Каталог моделей" href="/models" description="400+ моделей, актуальные цены в ₽ за 1M токенов." />
} title="Миграция с OpenAI" href="/docs/migration-from-openai" description="Один base URL — и существующий код работает." />
} title="API Reference" href="/docs/api/overview" description="/v1/chat/completions, /v1/responses, /v1/embeddings, /v1/models." />
} title="Биллинг и цены" href="/docs/concepts/pricing" description="Как считается стоимость, СБП-пополнения, free-модели." />
## Что вы получаете
* **Один API-ключ** для доступа к моделям OpenAI, Anthropic, Google, DeepSeek, Mistral и других.
* **Оплата в рублях** через Систему быстрых платежей. Без валютных карт и зарубежных платёжных провайдеров.
* **OpenAI-совместимый формат** — меняете один URL и подключаете существующий код без переписывания.
* **Прозрачные цены** — итоговая стоимость в ₽ за 1 миллион токенов сразу видна в каталоге.
* **Стриминг ответов** — Server-Sent Events работают как в OpenAI API.
## Популярные модели
* — быстрая и дешёвая Claude от Anthropic, для большинства задач.
* — баланс качества и стоимости от OpenAI.
* — большой контекст и мультимодальность от Google.
Полный список с ценами — в [каталоге моделей](/models).
## Поддержка
Не нашли нужную модель или нужна помощь с интеграцией — напишите на [support@hubris.pw](mailto:support@hubris.pw).
---
# Документация для AI (/docs/llms-txt)
> Скормите документацию и весь каталог моделей с ценами в ChatGPT, Claude или любого AI-агента одной ссылкой — и спрашивайте на естественном языке.
import { Cards, Card } from 'fumadocs-ui/components/card';
import { FileText, ListTree, Link2, Sparkles, Coins, Braces } from 'lucide-react';
Если вы не хотите читать документацию глазами — отдайте её AI-модели и задавайте вопросы голосом или текстом. Мы публикуем и документацию, и весь каталог моделей с ценами в специальном формате, который понимают ChatGPT, Claude, Gemini и любые другие LLM.
Это работает без регистрации и без копирования вручную. Достаточно одной ссылки.
## Самый быстрый способ — за 30 секунд
1. Откройте [ChatGPT](https://chatgpt.com) или [Claude](https://claude.ai).
2. Скопируйте промпт ниже и вставьте в окно чата.
3. Спрашивайте всё, что хотите узнать о Hubris.
```
Прочитай документацию Hubris по ссылке https://hubris.pw/llms-full.txt
и каталог моделей с ценами https://hubris.pw/models.txt.
Это OpenAI-совместимый LLM-шлюз с оплатой в рублях. Дальше отвечай на мои вопросы.
```
Модель сама загрузит файлы, разберёт их и будет отвечать со ссылками на нужные разделы. Дальше можно спрашивать что угодно: «как мне мигрировать с OpenAI?», «сколько стоит Claude Haiku в рублях?», «как включить стриминг в Python?» — модель ответит, опираясь на актуальную документацию и актуальные цены.
Ссылка на каталог здесь не лишняя: в документации цен нет, они живут в отдельном файле — иначе на вопрос про стоимость модель начнёт гадать.
## Цены и каталог моделей
Самый частый вопрос к ассистенту — «сколько это стоит в рублях». Для него есть отдельные файлы: в них весь каталог с актуальными ценами, без документации и лишнего текста.
} title="models.txt — все модели и цены текстом" href="https://hubris.pw/models.txt" description="Весь каталог с ценами в рублях одним файлом. Каждая строка — модель, цена, контекст и ссылка на карточку." />
} title="models.json — то же машиночитаемо" href="https://hubris.pw/models.json" description="Тот же каталог структурированными данными: единица тарификации, цены, модальности, контекст." />
} title=".md-версия карточки модели" href="https://hubris.pw/models/openai/gpt-5.1.md" description="Добавьте .md к адресу любой модели — получите её характеристики и цену в чистом Markdown." />
Спросить цену у модели можно так:
```
Открой https://hubris.pw/models.txt — это каталог Hubris с ценами в рублях.
Подбери мне самую дешёвую модель, которая понимает картинки,
и назови её цену за миллион токенов.
```
## Документация: три формата на выбор
} title="llms-full.txt — вся документация одним файлом" href="https://hubris.pw/llms-full.txt" description="Полный дамп всех страниц одним plain-text файлом. Скормите целиком в контекст модели." />
} title="llms.txt — оглавление всего сайта" href="https://hubris.pw/llms.txt" description="Компактный индекс: документация, каталог, интеграции, цены. Стандарт llmstxt.org для AI-агентов." />
} title=".md-версия любой страницы" href="https://hubris.pw/docs/quickstart.md" description="Добавьте .md или .mdx к URL любой страницы — получите её в чистом Markdown." />
### Когда какой формат использовать
| Сценарий | Что давать модели |
| ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| Нужны цены — «сколько стоит», «что дешевле», «подбери под бюджет» | [`models.txt`](https://hubris.pw/models.txt) |
| Пишете код, который читает каталог программно | [`models.json`](https://hubris.pw/models.json) |
| Нужна одна конкретная модель | адрес модели с суффиксом `.md`, например [`/models/openai/gpt-5.1.md`](https://hubris.pw/models/openai/gpt-5.1.md) |
| Хотите задавать вопросы по всей документации сразу | [`llms-full.txt`](https://hubris.pw/llms-full.txt) |
| Делаете своего AI-агента и хотите, чтобы он сам выбирал нужные страницы | [`llms.txt`](https://hubris.pw/llms.txt) (оглавление) — агент сам подтянет нужное |
| Нужна конкретная одна страница документации | URL с суффиксом `.md`, например [`/docs/quickstart.md`](https://hubris.pw/docs/quickstart.md) |
| Работаете в Cursor / Windsurf / Claude Code | Добавьте `https://hubris.pw/llms-full.txt` как контекст-документ |
## Готовые промпты
Скопируйте и вставьте — модель сделает остальное.
### Помощник по интеграции
```
Прочитай https://hubris.pw/llms-full.txt — это документация Hubris.
Я хочу подключить Hubris к своему проекту на [Python/Node.js/...].
Расскажи пошагово, что нужно сделать. Дай работающий код для первого запроса.
```
### Миграция с OpenAI
```
Загрузи https://hubris.pw/llms-full.txt и помоги мне мигрировать с OpenAI API на Hubris.
У меня код на [язык]. Что именно нужно поменять?
Объясни, что останется идентичным, а где могут быть отличия.
```
### Выбор модели под задачу
```
Изучи https://hubris.pw/llms-full.txt и каталог с ценами https://hubris.pw/models.txt.
Мне нужна модель для [задача — например «короткие классификации текста»,
«генерация длинных статей», «работа с картинками»]. Бюджет — [X ₽ в месяц].
Что посоветуешь? Покажи плюсы-минусы и итоговую цену в рублях за 1М токенов.
```
### Сравнить цены нескольких моделей
```
Открой https://hubris.pw/models.txt и сравни цены на [модель А], [модель Б]
и [модель В]. Посчитай, во сколько мне обойдётся [N] запросов в месяц,
если в среднем на запрос уходит [X] токенов на вход и [Y] на выход.
Ответ в рублях, таблицей.
```
### Отладка ошибок
```
Я работаю с Hubris API и получаю такую ошибку: [вставьте текст ошибки].
Документация: https://hubris.pw/llms-full.txt
Что это значит и как починить?
```
## Как скормить документацию AI-агенту
### ChatGPT (chatgpt.com)
1. Откройте новый чат.
2. Вставьте: `Прочитай https://hubris.pw/llms-full.txt и жди вопросов.`
3. ChatGPT автоматически загрузит файл через встроенный браузер. Дальше задавайте вопросы как обычно.
Можно ещё надёжнее: скачайте [`llms-full.txt`](https://hubris.pw/llms-full.txt) себе на компьютер и **перетащите файл в окно чата** как вложение. Так модель точно прочитает весь текст без зависимости от веб-поиска.
### Claude.ai (claude.ai)
1. Откройте новый чат на [claude.ai](https://claude.ai).
2. Нажмите на иконку скрепки внизу окна → «Add from URL» → вставьте `https://hubris.pw/llms-full.txt`.
3. Claude добавит документ в Project Knowledge и будет ссылаться на него во всех ответах.
Альтернатива: создайте Project, загрузите файл туда один раз — и все чаты внутри проекта будут видеть документацию.
### Google Gemini / AI Studio
В [Gemini](https://gemini.google.com) или [aistudio.google.com](https://aistudio.google.com) — просто вставьте ссылку `https://hubris.pw/llms-full.txt` в чат с фразой «прочитай и помоги по этой документации». У Gemini 2.5 контекст 1M токенов — вся документация поместится с большим запасом.
### Cursor
1. Откройте Cursor → Settings (⌘,) → **Features** → **Docs**.
2. Нажмите **Add new doc**.
3. Вставьте URL: `https://hubris.pw/llms-full.txt`, дайте имя `Hubris`.
4. В любом чате внутри Cursor напишите `@Hubris` — и документация подтянется в контекст.
### Claude Code
В диалоге Claude Code (терминал или IDE) просто упомяните URL:
```
Прочитай https://hubris.pw/llms-full.txt и помоги интегрировать Hubris в этот проект.
```
Claude Code сам загрузит файл через WebFetch. Чтобы это работало всегда — добавьте ссылку в `CLAUDE.md` вашего проекта:
```md
## Документация по Hubris API
Полная документация: https://hubris.pw/llms-full.txt
Оглавление сайта: https://hubris.pw/llms.txt
Модели и цены в рублях: https://hubris.pw/models.txt
```
### Cline / Roo-Code / Kilo-Code
В настройках расширения найдите раздел **Custom Instructions** или **Documentation** и добавьте:
```
При работе с Hubris API всегда сверяйся с документацией:
https://hubris.pw/llms-full.txt
```
Агент будет подгружать страницу при необходимости через свой web-fetch tool.
### Continue.dev
В `~/.continue/config.json` добавьте раздел `docs`:
```json
{
"docs": [
{
"title": "Hubris",
"startUrl": "https://hubris.pw/llms-full.txt",
"rootUrl": "https://hubris.pw/docs"
}
]
}
```
После перезапуска Continue в чате будет доступна команда `@Hubris`.
### Любой другой агент
Если ваш агент умеет ходить в web (большинство современных умеют — Perplexity, Phind, Poe, MetaGPT, AutoGen), просто скажите ему:
```
Документация Hubris: https://hubris.pw/llms-full.txt
Прочитай её перед ответами на вопросы про Hubris.
```
## Кнопки на каждой странице
Если вам нужна не вся документация, а одна конкретная страница — в шапке каждой страницы есть кнопка **«Открыть в ChatGPT / Claude»**. Она автоматически передаёт текущую страницу в выбранную AI-модель с готовым промптом.
Рядом — кнопка **«Скопировать как Markdown»**: содержимое страницы в чистом виде, без меню и навигации, готово к вставке в любую модель или ваш редактор.
## Для разработчиков AI-агентов
Если вы делаете чат-бот, RAG-систему, или плагин для IDE — используйте `llms.txt` и `.md`-суффиксы как обычные документы для индексации.
* Оглавление [`llms.txt`](https://hubris.pw/llms.txt) соответствует [стандарту llmstxt.org](https://llmstxt.org) — покрывает документацию, каталог, интеграции и разделы сайта.
* Любую страницу `/docs/` и любую карточку модели `/models/` можно получить как plain Markdown, добавив `.md` к URL.
* Каталог обновляется автоматически: цены в фидах — те же, что на сайте, ручной синхронизации нет.
Текстовые эндпоинты отдаются с `Content-Type: text/plain; charset=utf-8`, `models.json` — с `application/json`. Все кэшируются на 5 минут на клиенте и 1 час на CDN.
**У `models.json` нет гарантий обратной совместимости.** Схема может поменяться без предупреждения и без версионирования — это написано и в самом ответе, первым полем `notice`. Если вам нужен стабильный контракт, используйте [`GET /v1/models`](/docs/api/models): он OpenAI-совместим и просто так не меняется.
## Часто задаваемые вопросы
**Это бесплатно?** Да. Все перечисленные файлы — `llms.txt`, `llms-full.txt`, `models.txt`, `models.json` и `.md`-версии страниц — доступны без регистрации и без API-ключа.
**Документация и цены всегда свежие?** Да. Документация пересобирается при каждом обновлении сайта, каталог и цены подтягиваются из той же базы, что показывает сайт, — расхождения между фидом и карточкой модели быть не может.
**Можно использовать в своём продукте?** Да, ссылайтесь и цитируйте свободно. Учтите только, что у `models.json` схема без гарантий совместимости — см. предупреждение выше.
**Модель путается или галлюцинирует?** Попробуйте более крупную модель или сократите контекст: вместо `llms-full.txt` дайте конкретную страницу через `.md`-суффикс, а для вопросов про цены — `models.txt` вместо всей документации. Полный дамп документации весит около 600 КБ: он помещается в контекст современных моделей, но занимает его заметную часть.
## Что дальше
* [Быстрый старт](/docs/quickstart) — первый запрос за 60 секунд.
* [Каталог моделей](/models) — выбрать модель по цене и задаче глазами.
* [GET /v1/models](/docs/api/models) — тот же каталог по API, со стабильной схемой.
* [FAQ](/docs/faq) — короткие ответы на популярные вопросы.
---
# Миграция с OpenAI (/docs/migration-from-openai)
> Как переключиться с OpenAI на Hubris — что меняется, что 1-в-1.
import { EnvVar } from '@/components/docs/env-var';
Если у вас уже есть код, работающий через OpenAI API, для перехода на Hubris достаточно изменить **базовый URL и API-ключ**. Остальное — формат запросов, имена параметров, структура ответа — идентично.
## Что меняется
### 1. Базовый URL
Замените базовый URL OpenAI на `https://api.hubris.pw/v1`. Например, если у вас был стандартный OpenAI base URL — замените его на адрес выше.
### 2. API-ключ
OpenAI-ключ (`sk-...`) → Hubris-ключ (`sk-gw-...`). Получите на [/keys](/keys).
```diff
- api_key = os.environ["OPENAI_API_KEY"]
+ api_key = os.environ["HUBRIS_API_KEY"]
```
### 3. Имена моделей
OpenAI принимает короткие имена (например, `gpt-4o-mini`). Hubris использует полные идентификаторы вида `provider/model` — например, `openai/gpt-4o-mini`. Замените короткое имя на полный идентификатор провайдера.
Полный список — в [каталоге моделей](/models).
## Что НЕ меняется
* Формат тела запроса (`messages`, `tools`, `temperature`, и т. д.).
* Формат ответа (`choices[0].message.content`, `usage.prompt_tokens`, `id`, `created`).
* Стриминг через `stream: true` и Server-Sent Events.
* Инструменты (tool calling) — `tools`, `tool_choice`, `tool_calls`.
* JSON-режим — `response_format: { type: "json_object" }` или `json_schema`.
* Vision (multimodal входы) — `image_url` в `content`.
* OpenAI SDK работает без изменений — нужно только два параметра.
## Минимальный diff кода
### Python (с OpenAI SDK)
```python
from openai import OpenAI
client = OpenAI(
base_url="https://api.hubris.pw/v1", # was OpenAI base URL
api_key=os.environ["HUBRIS_API_KEY"], # was OPENAI_API_KEY
)
response = client.chat.completions.create(
model="openai/gpt-4o-mini", # was "gpt-4o-mini"
messages=[{"role": "user", "content": "Привет"}],
)
```
### Node.js (с OpenAI SDK)
```typescript
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.hubris.pw/v1", // was OpenAI base URL
apiKey: process.env.HUBRIS_API_KEY, // was OPENAI_API_KEY
});
const response = await client.chat.completions.create({
model: "openai/gpt-4o-mini", // was "gpt-4o-mini"
messages: [{ role: "user", content: "Привет" }],
});
```
## Что доступно дополнительно
В Hubris вы получаете **модели не только OpenAI**, через тот же API:
* **Anthropic Claude** — например, `anthropic/claude-haiku-4.5`.
* **Google Gemini** — например, `google/gemini-2.5-flash`.
* **OpenAI GPT** — например, `openai/gpt-4o-mini`.
Полный актуальный список — в [каталоге моделей](/models). Все они отвечают в том же OpenAI-формате `chat.completion`, поэтому код менять не нужно.
## Различия в биллинге
* **Цены в ₽**, не в USD. Списание происходит за каждый успешный запрос; стоимость возвращается в `usage` и логируется на [/usage](/usage).
* **Минимальный баланс** для запуска запроса — 1 ₽. Реальная стоимость списывается после успешного ответа и зависит от модели и числа токенов.
* **Без подписок и тарифов** — pay-as-you-go. Подробнее — на странице [Биллинг](/docs/concepts/billing).
## Что поддерживается на старте
Сейчас Hubris реализует основной OpenAI-эндпоинт `POST /v1/chat/completions` (с инструментами, JSON-режимом, vision, стримингом) и `GET /v1/models`. Этого достаточно для подавляющего большинства интеграций — чат-боты, агенты, RAG, классификация, извлечение данных.
Дополнительные эндпоинты (генерация эмбеддингов, картинок, аудио, batch-обработка) находятся в работе — следите за обновлениями. Для них пока используйте провайдера напрямую: когда Hubris их выпустит, переключение будет такой же сменой URL.
Assistants API (`/v1/assistants`, `/v1/threads`) — не планируется: Stateless API лучше для большинства интеграций.
## Что дальше
* [Быстрый старт](/docs/quickstart) — рабочий пример сразу.
* [Каталог моделей](/models) — все доступные `provider/model`.
* [Аутентификация](/docs/authentication) — детали по ключам.
---
# Быстрый старт (/docs/quickstart)
> Первый запрос к Hubris за 60 секунд. Примеры на cURL, Python, TypeScript и OpenAI SDK.
import { CodeTabs } from '@/components/docs/code-tabs';
import { EnvVar } from '@/components/docs/env-var';
import { EndpointBadge } from '@/components/docs/endpoint-badge';
import { TryItButton } from '@/components/docs/try-it-button';
Подключение к Hubris — это смена одного URL. Если у вас уже есть код под OpenAI API, замените базовый URL на `https://api.hubris.pw/v1` и подставьте ключ Hubris. Всё остальное работает как было.
## 1. Получите API-ключ
[Зарегистрируйтесь](/sign-in) (нужен email и одноразовый код), затем создайте ключ в разделе [API-ключи](/keys). Ключ показывается **один раз** — сохраните его в менеджер паролей или передайте в переменную окружения сразу.
## 2. Пополните баланс
В разделе [Биллинг](/billing) пополните баланс через СБП — минимально 300 ₽. Этого хватит на тысячи запросов к мелким моделям.
## 3. Сделайте первый запрос
Замените `sk-gw-...` на ваш ключ:
## С OpenAI SDK
Если у вас уже OpenAI SDK — поменяйте только `base_url` и ключ.
## Ожидаемый ответ
Стандартный OpenAI-формат:
```json
{
"id": "chatcmpl-...",
"object": "chat.completion",
"created": 1714000000,
"model": "anthropic/claude-haiku-4.5",
"choices": [
{
"index": 0,
"message": { "role": "assistant", "content": "OK" },
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 12,
"completion_tokens": 1,
"total_tokens": 13
}
}
```
Если получили `401` — ключ неверный или отсутствует заголовок `Authorization`. См. [Аутентификацию](/docs/authentication). Если `402` — мало денег на балансе, см. [Биллинг](/billing).
## Что дальше
* **[Аутентификация](/docs/authentication)** — управление ключами, безопасное хранение, ротация.
* **[Каталог моделей](/models)** — все доступные модели с актуальными ценами.
* **[Биллинг](/billing)** — пополнение, минимальный остаток.
---
# Статус сервиса (/docs/status)
> Текущая доступность Hubris API и историческая аптайм-статистика.
Hubris работает 24/7. Если у вас что-то не отвечает — сначала проверьте этот раздел.
## Текущий статус
Проверка живости — `GET /healthz` без авторизации:
```bash
curl https://api.hubris.pw/healthz
# {"status":"ok"}
```
Если получили `200 OK` с `{"status":"ok"}` — сервис в норме, проблема, скорее всего, у вас (баланс, ключ, формат запроса).
Если 5xx или таймаут — проблема на нашей стороне. Подождите 1–2 минуты и попробуйте снова.
## Что мониторим внутри
* Задержки `p50`, `p95`, `p99` на `/v1/chat/completions` для каждой активной модели.
* Доступность каталога `GET /v1/models`.
* Курс ЦБ РФ — обновляется раз в день в 12:00 МСК. Если ЦБ временно недоступен и кеш истёк — каталог отдаст `503` с `code: "exchange_rate_unavailable"`.
* Баланс БД, очереди в Redis, дисковое пространство.
## Если что-то сломалось
1. Посмотрите `/healthz`.
2. Если `/healthz` ок, а конкретная модель отвечает 502 — попробуйте другую модель из той же группы (например, `openai/gpt-4o-mini` вместо `openai/gpt-4o`). Часто это локальная проблема апстрима, не Hubris.
3. Если несколько разных моделей дают 502 одновременно — это инфра. Напишите на [support@hubris.pw](mailto:support@hubris.pw) с временем и request\_id из заголовка ответа.
## Публичный uptime-дашборд
В работе — будет на отдельном поддомене с историей 90 дней. Пока — пишите на support, ответим оперативно.
---
# Поддержка (/docs/support)
> Как связаться с командой Hubris — баги, вопросы, фичи, биллинг.
## Email
Главный канал — **[support@hubris.pw](mailto:support@hubris.pw)**. Отвечаем в рабочие часы по Москве, обычно в течение нескольких часов; критические инциденты — быстрее.
## Что приложить к письму
В зависимости от темы:
**Баг в API**:
* Время запроса (UTC или МСК) и используемую модель.
* Email аккаунта (по нему мы найдём запрос в логах через [/usage](/usage)).
* Тело запроса — без полного содержимого `messages`, достаточно структуры.
* Ответ, который пришёл — HTTP-статус и JSON ошибки.
**Биллинг**:
* Email вашего аккаунта.
* Дата и сумма операции.
* Если не пришло пополнение — ID СБП-перевода (виден в банке).
**Запрос новой модели или фичи**:
* Какая модель / провайдер.
* Юзкейс (что вы делаете — это помогает приоритезировать).
**Юр. лицо / договор**:
* Реквизиты компании.
* Объём планируемого использования (для оценки тарифа).
## Чего НЕ присылать
* **Не присылайте API-ключи** ни в каком виде. Если ключ скомпрометирован — отзывайте на [/keys](/keys), это не требует общения с поддержкой.
* **Не присылайте полное содержимое запросов с персональными данными**. Для воспроизведения бага достаточно структуры и метаданных.
## SLA
Стандартный pay-as-you-go — best-effort, ответ в течение рабочего дня.
Для бизнес-интеграций с гарантированным SLA (часовое реагирование, доступ 24/7) — пишите про enterprise-условия. Согласуем индивидуально.
## Что не делаем
* Не помогаем с написанием prompt-ов и интеграцией под конкретную задачу — это работа разработчика. Документация на сайте + примеры в [API Reference](/docs/api/overview) должны хватить для большинства случаев.
* Не комментируем содержимое ответов моделей — это поведение провайдера, не Hubris.
## Социальные сети
Пока единственный канал — email. Корпоративный аккаунт в Telegram / VK — в работе.
---
# Hello world (/docs/_sandbox/hello)
> Демо-страница со всеми компонентами документации.
import { ModelSampleCode } from '@/components/docs/model-sample-code';
import { EndpointBadge } from '@/components/docs/endpoint-badge';
import { EnvVar } from '@/components/docs/env-var';
import { ModelLink } from '@/components/docs/model-link';
Это sandbox-страница для Phase 1. Если вы её видите — Fumadocs стартанул на Tailwind 4 + React 19 RC.
Установите в окружение.
Попробуйте модель .
## cURL пример
```bash
curl https://api.hubris.pw/v1/models \
-H "Authorization: Bearer sk-gw-..."
```
## Code tabs (cURL / Python / TypeScript)
## Чек-лист
* [x] Маршрут `/docs/_sandbox/hello` отдаёт 200 публично
* [x] Sidebar и breadcrumbs рендерятся
* [x] Cmd+K открывает поиск
* [x] og-image генерится
---
# POST /v1/chat/completions (/docs/api/chat-completions)
> Создать chat completion. OpenAI-совместимый формат, поддержка стриминга, tool calling, structured outputs, vision.
import { CodeTabs } from '@/components/docs/code-tabs';
import { EndpointBadge } from '@/components/docs/endpoint-badge';
Главный эндпоинт Hubris — создаёт ответ языковой модели на цепочку сообщений. OpenAI-совместимый формат — без переписывания работают `openai` Python SDK, OpenAI TypeScript SDK, LangChain, Vercel AI SDK и любые другие клиенты под OpenAI Chat Completions API.
## Эндпоинт
Заголовки:
| Header | Значение |
| --------------- | ---------------------------------------------------------------------------------- |
| `Authorization` | `Bearer sk-gw-...` (обязательно) |
| `Content-Type` | `application/json` (обязательно для тела запроса) |
| `Accept` | `application/json` или `text/event-stream` (опционально; при `stream: true` — SSE) |
## Тело запроса
Обязательные поля помечены **жирным**. Остальное — опциональное.
| Поле | Тип | По умолчанию | Описание |
| ----------------------- | ----------------------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`model`** | `string` | — | Идентификатор модели, например `anthropic/claude-haiku-4.5`. Список доступных — [`GET /v1/models`](/docs/api/models) или [каталог](/models). |
| **`messages`** | `array` | — | Цепочка сообщений диалога. Минимум 1. Структура — [ниже](#сообщения). |
| `models` | `array` | — | До 10 fallback-моделей. Если основная недоступна — пробует следующие по порядку. Биллинг по фактически использованной (см. `model` в ответе). См. [Model fallbacks](/docs/features/model-fallbacks). |
| `provider` | `object` | — | Предпочтения по провайдерам инференса: `{"order": ["deepinfra"], "allow_fallbacks": false}`. Полезно для стабильных кеш-хитов — см. [Кеширование промптов](/docs/features/prompt-caching). |
| `temperature` | `number` 0–2 | `1` | Температура сэмплирования. 0 — детерминизм, 2 — максимум разнообразия. |
| `top_p` | `number` 0–1 | `1` | Nucleus sampling. Альтернатива `temperature` — обычно меняют что-то одно. |
| `n` | `integer` ≥ 1 | `1` | Сколько вариантов ответа сгенерировать. Каждый вариант оплачивается отдельно. |
| `stop` | `string \| string[]` | — | До 4 стоп-последовательностей. Модель прекратит генерацию при их встрече. |
| `max_tokens` | `integer` > 0 | — | Лимит токенов в ответе. OpenAI-совместимое имя. |
| `max_completion_tokens` | `integer` > 0 | — | Новое имя `max_tokens` (OpenAI deprecated старое для reasoning-моделей). Можно использовать любой из двух. |
| `frequency_penalty` | `number` -2…2 | `0` | Штраф за повторение токенов. |
| `presence_penalty` | `number` -2…2 | `0` | Штраф за повторение тем. |
| `logit_bias` | `object` | — | Карта `token_id → bias` (от -100 до 100). Управление вероятностью отдельных токенов. |
| `seed` | `integer` | — | Сид для best-effort детерминизма. Не гарантирует идентичности на разных прогонах. |
| `logprobs` | `boolean` | `false` | Вернуть вероятности выбранных токенов. |
| `top_logprobs` | `integer` 0–20 | — | Сколько top-альтернатив возвращать вместе с каждым выбранным токеном. Требует `logprobs: true`. |
| `tools` | `array` | — | Определения функций, которые модель может вызвать. Структура — [Tools](#tools). |
| `tool_choice` | `string \| object` | `"auto"` | Принудительный выбор инструмента. `"none"` / `"auto"` / `"required"` или `{type:"function", function:{name:"..."}}`. |
| `parallel_tool_calls` | `boolean` | `true` | Разрешить модели вызвать несколько tools в одном ответе. |
| `response_format` | `object` | `{type:"text"}` | Формат ответа. `text`, `json_object` или `json_schema`. См. [Structured outputs](#structured-outputs). |
| `modalities` | `array` | `["text"]` | Какие модальности возвращать. `["image","text"]` для image-gen моделей. |
| `image_config` | `object` | — | Параметры image-gen: `aspect_ratio`, `image_size` и model-specific. См. [Image generation](/docs/features/image-generation). |
| `stream` | `boolean` | `false` | Стриминг ответа через SSE. См. [Стриминг](#стриминг). |
| `stream_options` | `object` | — | Hubris автоматически выставляет `include_usage: true` для всех стримов. Указывать не обязательно. |
| `user` | `string` | — | Идентификатор конечного пользователя для трекинга абьюза на стороне провайдера. |
| `reasoning_effort` | `"low" \| "medium" \| "high"` | — | Для reasoning-моделей: бюджет «думания». Подробности — [Reasoning](/docs/features/reasoning). |
## Сообщения
Каждый элемент `messages` — объект с обязательным полем `role` и зависящими от роли остальными полями.
### system
```json
{ "role": "system", "content": "Ты — лаконичный ассистент." }
```
| Поле | Тип | Описание |
| ------------- | ---------- | ------------------------ |
| **`role`** | `"system"` | Роль. |
| **`content`** | `string` | Системная инструкция. |
| `name` | `string` | Опциональное имя автора. |
### user
```json
{ "role": "user", "content": "Привет! Как дела?" }
```
Content может быть строкой ИЛИ массивом мультимодальных частей (текст + картинки):
```json
{
"role": "user",
"content": [
{ "type": "text", "text": "Что на картинке?" },
{ "type": "image_url", "image_url": { "url": "https://example.com/photo.jpg" } }
]
}
```
Поддерживаемые `type`: `text`, `image_url`. URL может быть `https://...` или `data:image/;base64,...`. Подробности — [Vision](/docs/features/vision).
### assistant
```json
{ "role": "assistant", "content": "Привет! Хорошо, спасибо." }
```
С tool calls:
```json
{
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_abc",
"type": "function",
"function": { "name": "get_weather", "arguments": "{\"city\":\"Москва\"}" }
}
]
}
```
### tool
Результат вызова функции (отправляется после `assistant`-сообщения с `tool_calls`):
```json
{
"role": "tool",
"tool_call_id": "call_abc",
"content": "{\"temp\": -5, \"condition\": \"snow\"}"
}
```
## Tools
Каждый элемент `tools` — `function` (определение пользовательской функции) или `server` (server-side tool провайдера: web search, code interpreter и т.д.; зависит от модели).
### Function tool
```json
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Получить текущую погоду по городу",
"parameters": {
"type": "object",
"properties": { "city": { "type": "string" } },
"required": ["city"]
},
"strict": true
}
}
```
| Поле | Тип | Описание |
| ---------------------- | ------------ | ----------------------------------------------------------------------- |
| **`type`** | `"function"` | Дискриминатор. |
| **`function.name`** | `string` | Имя функции. Только латиница, цифры, `_` и `-`. |
| `function.description` | `string` | Описание для модели — что делает функция и когда её вызывать. |
| `function.parameters` | `object` | JSON Schema аргументов функции. |
| `function.strict` | `boolean` | Если `true`, модель гарантированно вернёт arguments, валидные по схеме. |
Полный гайд с примерами обработки — [Tool calling](/docs/features/tool-calling).
## Structured outputs
Поле `response_format` принимает три формы:
```json
{ "type": "text" }
```
```json
{ "type": "json_object" }
```
```json
{
"type": "json_schema",
"json_schema": {
"name": "city_info",
"schema": {
"type": "object",
"properties": {
"name": { "type": "string" },
"country": { "type": "string" },
"population": { "type": "number" }
},
"required": ["name", "country", "population"]
},
"strict": true
}
}
```
При `json_schema` модель гарантированно вернёт валидный JSON по вашей схеме. Подробности и примеры — [Structured output](/docs/features/structured-output).
## Минимальный пример
## Ответ
```json
{
"id": "chatcmpl-abc123",
"object": "chat.completion",
"created": 1714000000,
"model": "anthropic/claude-haiku-4.5",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Привет! Чем могу помочь?"
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 12,
"completion_tokens": 7,
"total_tokens": 19,
"cost": 24
}
}
```
### Поля ответа
| Поле | Тип | Описание |
| ----------------------------- | ------------------- | ---------------------------------------------------------------------------------------------------- |
| **`id`** | `string` | Уникальный идентификатор completion. Префикс `chatcmpl-`. |
| **`object`** | `"chat.completion"` | Тип объекта. Для стрима — `"chat.completion.chunk"`. |
| **`created`** | `integer` | Unix-timestamp (секунды). |
| **`model`** | `string` | Фактически использованная модель (отличается от запрошенной при срабатывании `models` fallback). |
| **`choices[]`** | `array` | Варианты ответа. Длина равна `n` из запроса (по умолчанию 1). |
| **`choices[].index`** | `integer` | Индекс варианта (0-based). |
| **`choices[].message`** | `object` | Сообщение модели с `role: "assistant"`, `content` и/или `tool_calls`. |
| **`choices[].finish_reason`** | `string` | Почему генерация завершилась: `stop` / `length` / `tool_calls` / `content_filter` / `function_call`. |
| `choices[].logprobs` | `object` | Если в запросе был `logprobs: true`. |
| **`usage.prompt_tokens`** | `integer` | Токены ввода. |
| **`usage.completion_tokens`** | `integer` | Токены вывода. |
| **`usage.total_tokens`** | `integer` | Сумма. |
| **`usage.cost`** | `integer` | Стоимость запроса **в копейках**. Hubris-расширение поверх стандартного OpenAI ответа. |
| `system_fingerprint` | `string` | Метка конфигурации модели у провайдера (для отладки воспроизводимости). |
## Стриминг
С `stream: true` ответ приходит chunk-ами через Server-Sent Events:
```bash
curl -N -s https://api.hubris.pw/v1/chat/completions \
-H "Authorization: Bearer sk-gw-..." \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-haiku-4.5",
"messages": [{"role": "user", "content": "Расскажи короткую историю"}],
"stream": true
}'
```
Формат каждого chunk, отслеживание usage и обработка `[DONE]` — на странице [Стриминг](/docs/features/streaming).
## Tool calling
```bash
curl -s https://api.hubris.pw/v1/chat/completions \
-H "Authorization: Bearer sk-gw-..." \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-haiku-4.5",
"messages": [{"role": "user", "content": "Какая погода в Москве?"}],
"tools": [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Получить текущую погоду по городу",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"]
}
}
}]
}'
```
Модель вернёт `tool_calls` вместо обычного `content` — выполните функцию у себя и отправьте результат вторым запросом с `role: "tool"`. Полный сценарий — [Tool calling](/docs/features/tool-calling).
## Vision
```bash
curl -s https://api.hubris.pw/v1/chat/completions \
-H "Authorization: Bearer sk-gw-..." \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-haiku-4.5",
"messages": [{
"role": "user",
"content": [
{"type": "text", "text": "Что на картинке?"},
{"type": "image_url", "image_url": {"url": "https://example.com/photo.jpg"}}
]
}]
}'
```
Поддерживается только моделями с `image` в `input_modalities` — см. [GET /v1/models](/docs/api/models) и страницу [Vision](/docs/features/vision).
## HTTP-коды
| Код | Когда | Полный список — |
| ----------------- | ------------------------------------------- | ------------------------------------------------- |
| `200` | Успешный ответ | — |
| `400` | Тело запроса не соответствует схеме | [Ошибки](/docs/concepts/errors) |
| `401` | Ключ отсутствует / отозван / невалиден | [Ошибки](/docs/concepts/errors) |
| `402` | Недостаточно средств на балансе | [Биллинг](/docs/concepts/billing) |
| `404` | Модели с таким `model` не существует | [Каталог](/models) |
| `413` | Тело запроса больше 36 МБ | [Ошибки](/docs/concepts/errors) |
| `429` | Превышен дневной лимит на ключе | [Rate limits](/docs/concepts/rate-limits) |
| `502 / 503 / 504` | Транзиентные сбои у провайдера или курса ЦБ | [Обработка ошибок](/docs/features/error-handling) |
Формат тела ошибки и стратегия retry — [Ошибки](/docs/concepts/errors).
## Что дальше
* [Стриминг](/docs/features/streaming) — формат SSE и обработка в клиенте.
* [Tool calling](/docs/features/tool-calling) — пошаговый сценарий с многократными итерациями.
* [Structured output](/docs/features/structured-output) — JSON Schema validation.
* [Ошибки](/docs/concepts/errors) — таблица всех кодов и retry-стратегий.
* [Цены](/docs/concepts/pricing) — как считается `usage.cost`.
---
# POST /v1/embeddings (/docs/api/embeddings)
> Создать эмбеддинги — вектор фиксированной длины из текста. OpenAI-совместимый формат.
import { CodeTabs } from '@/components/docs/code-tabs';
import { EndpointBadge } from '@/components/docs/endpoint-badge';
Превращает текст в вектор чисел фиксированной длины — для семантического поиска, RAG, классификации, кластеризации. OpenAI-совместимый формат — без переписывания работают `openai` Python/TypeScript SDK, LangChain, LlamaIndex и любые клиенты под OpenAI Embeddings API.
Стрима нет: один POST → один JSON-ответ. Полный список embedding-моделей — в [каталоге](/models) (фильтр «embeddings»).
## Эндпоинт
Заголовки:
| Header | Значение |
| --------------- | -------------------------------- |
| `Authorization` | `Bearer sk-gw-...` (обязательно) |
| `Content-Type` | `application/json` (обязательно) |
## Тело запроса
| Поле | Тип | По умолчанию | Описание |
| ----------------- | ---------------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------- |
| **`model`** | `string` | — | ID embedding-модели, например `openai/text-embedding-3-small`. |
| **`input`** | `string \| string[] \| number[] \| number[][]` | — | Текст. Поддерживает: одну строку, массив строк (батч), массив токен-ID или массив массивов токен-ID. Максимум 2048 элементов в батче. |
| `encoding_format` | `"float" \| "base64"` | `"float"` | `float` — массив чисел в `embedding`. `base64` — base64-строка (\~30% меньше трафика). |
| `dimensions` | `integer` > 0 | — | Уменьшенная размерность вектора. Поддерживается только моделями `openai/text-embedding-3-*`. |
| `user` | `string` | — | Идентификатор конечного пользователя для трекинга абьюза на стороне провайдера. |
## Минимальный пример
## Ответ
```json
{
"object": "list",
"data": [
{
"object": "embedding",
"index": 0,
"embedding": [-0.012, 0.034, 0.089, ...]
}
],
"model": "openai/text-embedding-3-small",
"usage": {
"prompt_tokens": 4,
"total_tokens": 4,
"cost": 1
}
}
```
### Поля ответа
| Поле | Тип | Описание |
| ------------------------- | -------------------- | -------------------------------------------------------------------------- |
| **`object`** | `"list"` | Тип контейнера. |
| **`data[]`** | `array` | Векторы в том же порядке что и `input`. |
| **`data[].object`** | `"embedding"` | Тип элемента. |
| **`data[].index`** | `integer` | Позиция в исходном `input` (0-based). Полезно при батче. |
| **`data[].embedding`** | `number[] \| string` | Сам вектор. `number[]` при `float`, base64-строка при `base64`. |
| **`model`** | `string` | Фактически использованная модель. |
| **`usage.prompt_tokens`** | `integer` | Токены ввода. |
| **`usage.total_tokens`** | `integer` | Сумма. У embeddings всегда равна `prompt_tokens` — completion-токенов нет. |
| **`usage.cost`** | `integer` | Стоимость **в копейках**. Hubris-расширение. |
## Батч
`input` принимает массив строк — за один запрос до 2048 эмбеддингов. Это значительно быстрее и дешевле, чем 2048 одиночных запросов.
```bash
curl -s https://api.hubris.pw/v1/embeddings \
-H "Authorization: Bearer sk-gw-..." \
-H "Content-Type: application/json" \
-d '{
"model": "openai/text-embedding-3-small",
"input": ["первый текст", "второй текст", "третий текст"]
}'
```
В ответе `data` — массив с тем же порядком, что и `input`. Поле `index` указывает позицию.
## Уменьшенная размерность
Параметр `dimensions` поддерживается моделями `openai/text-embedding-3-small` (по умолчанию 1536) и `openai/text-embedding-3-large` (по умолчанию 3072). Меньшая размерность = меньше места в vector store и быстрее cosine similarity, но падает качество поиска.
```bash
curl -s https://api.hubris.pw/v1/embeddings \
-H "Authorization: Bearer sk-gw-..." \
-H "Content-Type: application/json" \
-d '{
"model": "openai/text-embedding-3-small",
"input": "Привет",
"dimensions": 256
}'
```
## Base64-encoding
`encoding_format: "base64"` возвращает вектор как base64-строку вместо массива чисел — экономит \~30% трафика. Многие SDK сами декодируют обратно в float-массив.
```bash
curl -s https://api.hubris.pw/v1/embeddings \
-H "Authorization: Bearer sk-gw-..." \
-H "Content-Type: application/json" \
-d '{
"model": "openai/text-embedding-3-small",
"input": "Привет",
"encoding_format": "base64"
}'
```
## HTTP-коды
| Код | Когда |
| ----------------- | --------------------------------------------------------------------------------------------------------- |
| `200` | Успешный ответ. |
| `400` | Тело запроса не соответствует схеме (`input` пустой, размер батча > 2048, неподдерживаемый `dimensions`). |
| `401` | Ключ отсутствует / отозван / невалиден. |
| `402` | Недостаточно средств на балансе. |
| `404` | Модели с таким `model` не существует или это не embedding-модель. |
| `413` | Тело запроса больше 36 МБ. |
| `429` | Превышен дневной лимит на ключе. |
| `502 / 503 / 504` | Транзиентные сбои у провайдера или курса ЦБ. |
Формат тела ошибки и стратегия retry — [Ошибки](/docs/concepts/errors).
## Биллинг
Embeddings тарифицируются только по входным токенам — completion-токенов нет. Точная формула — на странице [Цены](/docs/concepts/pricing). Цены в ₽ за 1 миллион токенов и весь список embedding-моделей — в [каталоге](/models) с фильтром по выходной модальности «embeddings».
## Что дальше
* [Модели](/docs/concepts/models) — как выбрать embedding-модель.
* [Ошибки](/docs/concepts/errors) — что делать на 429/502.
* [Цены](/docs/concepts/pricing) — формула расчёта.
---
# POST /v1/images/generations (/docs/api/images)
> Генерация изображений в формате OpenAI Images API — FLUX.2, Recraft, Seedream, Riverflow, Grok Imagine. Фиксированная цена за изображение, image-to-image через input_references.
import { CodeTabs } from '@/components/docs/code-tabs';
import { EndpointBadge } from '@/components/docs/endpoint-badge';
Классический Images API: текстовый промпт на входе, готовые изображения на выходе. OpenAI-совместимый формат — официальные `openai` SDK работают без переписывания, достаточно поменять `base_url`.
Через этот эндпоинт работают модели с оплатой **за изображение**: Black Forest Labs FLUX.2, Recraft V3–V4.1, ByteDance Seedream 4.5, Sourceful Riverflow, xAI Grok Imagine. Полный список — фильтр «Output: image» в [каталоге](/models). Гибридные модели с токенной тарификацией (Google Gemini \*-image, OpenAI GPT-5 Image) сюда не ходят — они генерируют через [chat completions с `modalities`](/docs/features/image-generation): здесь такой `model` вернёт `404`.
## Эндпоинт
Заголовки:
| Header | Значение |
| --------------- | -------------------------------- |
| `Authorization` | `Bearer sk-gw-...` (обязательно) |
| `Content-Type` | `application/json` |
## Тело запроса
| Поле | Тип | По умолчанию | Описание |
| ----------------- | --------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`model`** | `string` | — | ID модели из каталога (только модели с ценой за изображение). |
| **`prompt`** | `string` | — | Описание изображения. FLUX лучше промптовать на английском — кириллицу рисует как текст на картинке. |
| `n` | `integer` | `1` | Сколько изображений, 1–10. Реальный потолок у каждой модели свой (Seedream до 10, Recraft до 6, FLUX и Grok — 1); списание — за фактически возвращённые. |
| `size` | `string` | — | Размер, напр. `"1024x1024"`. Большинство моделей задают размер сами — поле им передавать не нужно. |
| `response_format` | `string` | `b64_json` | Пока поддерживается только `b64_json`. |
Провайдер-специфичные поля схема не отбрасывает и передаёт модели как есть: `input_references` (image-to-image, см. ниже), `aspect_ratio` и `resolution` у Grok Imagine, `style` / `strength` / `text_layout` у Recraft, `font_inputs` у Riverflow. Что понимает конкретная модель — на её карточке в [каталоге](/models); незнакомое поле провайдер игнорирует или отклоняет запрос с `400`.
Тело запроса ограничено 36 МБ — хватает на максимальный заказ с base64-референсами; при больших исходниках сжимайте JPEG перед отправкой.
## Минимальный пример
## Ответ
```json
{
"created": 1753789200,
"data": [
{ "b64_json": "iVBORw0KGgoAAAANSUhEUg..." }
]
}
```
| Поле | Описание |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `created` | Unix-время (секунды). |
| `data[]` | По одному элементу на изображение. |
| `data[].b64_json` | Байты изображения в base64 — **без** префикса `data:`. Формат файла определяется по содержимому (обычно PNG или JPEG — как отдал провайдер). |
Ссылок (`url`) в ответе нет — изображения приходят телом ответа. Поля `usage` тоже нет: стоимость запроса смотрите в [журнале](/logs).
## Редактирование (image-to-image)
Исходные изображения передаются полем `input_references` — data URL с base64 или публичная https-ссылка:
```json
{
"model": "bytedance-seed/seedream-4.5",
"prompt": "Перенеси предмет с фото в интерьер лофта, мягкий дневной свет",
"input_references": [
{ "type": "image_url", "image_url": { "url": "data:image/jpeg;base64,/9j/4AAQ..." } }
]
}
```
В официальных SDK поле не объявлено — передавайте через `extra_body` (Python) или приведение типов (TypeScript). Сколько референсов принимает каждая модель и примеры целиком — в гайде [Генерация изображений](/docs/features/image-generation#редактирование-изображений).
## HTTP-коды
| Код | `code` | Когда |
| ----------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `200` | — | Успех, изображения в `data[]`. |
| `400` | `invalid_request` | Тело не соответствует схеме (нет `model`/`prompt`) либо модель отклонила параметры. |
| `401` | — | Ключ отсутствует / отозван / невалиден. |
| `402` | `insufficient_balance` | Баланса не хватает на ожидаемую стоимость заказа (`n × цена за изображение`). |
| `404` | `model_not_found` | Модели нет в каталоге, она выключена или тарифицируется токенами (Gemini/GPT-5 Image — те ходят через chat completions). |
| `413` | `request_too_large` | Тело запроса больше 36 МБ. |
| `429` | `daily_limit_exceeded` | Превышен дневной лимит расходов ключа. |
| `502` | `no_images_returned` | Провайдер ответил успехом, но без изображений. Списания нет, запрос стоит повторить. |
| `502` | `pricing_unavailable` | Не удалось определить стоимость генерации — запрос не выполняется, бесплатных генераций не бывает. |
| `502 / 503 / 504` | — | Транзиентные сбои провайдера, курса ЦБ или таймаут генерации. |
Формат тела ошибки и стратегия retry — [Ошибки](/docs/concepts/errors).
## Биллинг
Тарификация — **за изображение**: цена в рублях за штуку видна на карточке модели в [каталоге](/models), за запрос спишется `n × цена`. Стоимость известна до запроса и не зависит от длины промпта.
Списание — после успешного ответа, одной транзакцией; сумма попадает в [журнал запросов](/logs). За отклонённые запросы (`model_not_found`, невалидное тело), отказ провайдера и пустой ответ не списывается ничего.
## Что дальше
* [Генерация изображений](/docs/features/image-generation) — гайд целиком: оба пути, редактирование, выбор модели, параметры.
* [Vision](/docs/features/vision) — картинка на входе для анализа, а не генерации.
* [Модели](/docs/concepts/models) — как выбрать модель и прочитать её карточку.
* [Ошибки](/docs/concepts/errors) — что делать на 429/502.
* [Цены](/docs/concepts/pricing) — как считается стоимость запроса.
---
# Management API (/docs/api/management)
> Управление аккаунтом по API — ключи, баланс, расход, транзакции. Для скриптов, сервисов поверх Hubris и агентов.
import { CodeTabs } from '@/components/docs/code-tabs';
import { EndpointBadge } from '@/components/docs/endpoint-badge';
Management API позволяет управлять аккаунтом программно, без захода в личный кабинет: создавать и отзывать API-ключи, ставить лимиты, смотреть баланс, расход и историю операций.
Типовые сценарии:
* **Скрипты и мониторинг** — проверить баланс перед ночным батчем, отправить уведомление «пора пополнить», отозвать утёкший ключ одной командой.
* **Свой сервис поверх Hubris** — выдавать по отдельному ключу каждому своему клиенту, ставить дневной лимит в рублях, отключать неплательщиков и сверять расход по каждому ключу.
* **Агенты и автоматизации** — агент своим же рабочим ключом узнаёт лимит, потраченное и баланс через `GET /v1/key` и сам решает, хватит ли на задачу.
## Два типа ключей
| | Обычный ключ | Management-ключ |
| -------------------------------------------- | ------------------------------------ | --------------------------------------------------------- |
| Формат | `sk-gw-…` | `sk-gw-mgmt-…` |
| Вызов моделей (`/v1/chat/completions` и др.) | ✅ | ❌ (403) |
| Управление ключами, баланс, транзакции | ❌ (403) | ✅ |
| `GET /v1/key` (самоинтроспекция) | ✅ | ✅ |
| Как создаётся | В кабинете или через `POST /v1/keys` | Только в кабинете на [/management-keys](/management-keys) |
Разделение сделано намеренно: обычные ключи живут на серверах и в агентах — если такой ключ утёк, злоумышленник может только тратить (и упирается в дневной лимит), но не может создавать ключи или читать историю платежей. Management-ключ храните как пароль: не передавайте в клиентский код, агентам и третьим лицам.
**Если management-ключ скомпрометирован:** отзовите его в кабинете на [/management-keys](/management-keys), затем проверьте `GET /v1/keys` — не появились ли созданные без вас ключи (у них в кабинете видно время создания), и отзовите лишние.
## Лимиты
| Лимит | Значение |
| --------------------------------- | ---------------------- |
| Запросы к management-эндпоинтам | 60 в минуту на аккаунт |
| Создание ключей (`POST /v1/keys`) | 30 в час |
| Активных ключей на аккаунт | 500 |
Превышение — `429` с кодом `rate_limit_exceeded` (или `key_quota_exceeded` для потолка ключей).
## Ключи — `/v1/keys`
Все эндпоинты этого раздела требуют management-ключ: `Authorization: Bearer sk-gw-mgmt-…`. Обычный ключ получит `403 management_key_required`.
### Список ключей
Query-параметры: `offset` (по умолчанию 0), `limit` (1–100, по умолчанию 100). Возвращаются только активные обычные ключи — management-ключи и отозванные в список не попадают.
```json
{
"object": "list",
"data": [
{
"id": "3f1c9b2e-…",
"name": "Клиент А",
"key_prefix": "sk-gw-ab12...cd34",
"disabled": false,
"daily_limit_kopecks": 20000,
"spent_24h_kopecks": 512,
"privacy_mask_default": "inherit",
"last_used_at": "2026-07-16T09:00:00.000Z",
"created_at": "2026-07-01T12:00:00.000Z"
}
],
"has_more": false
}
```
Полное значение ключа не возвращается никогда — только маскированный `key_prefix`.
### Создать ключ
| Поле | Тип | Описание |
| ---------------------- | ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------- |
| `name` | `string` (1–100) | Название ключа — например, имя вашего клиента или сервиса. |
| `daily_limit_kopecks` | `number \| null` | Дневной лимит расхода в копейках (скользящие 24 часа), максимум 10 000 000 (100 000 ₽). Не указан или `null` — без лимита. |
| `privacy_mask_default` | `"inherit" \| "off" \| "on" \| "required"` | Режим [маскирования данных](/docs/features/privacy) для запросов этим ключом. |
Ответ `201` — та же форма, что в списке, плюс поле `key` с **полным значением ключа. Оно показывается один раз** — сохраните сразу, повторно получить нельзя.
### Один ключ
Та же форма, что элемент списка. Чужой, отозванный или management-ключ — `404 not_found`.
### Изменить ключ
Частичное обновление — передайте хотя бы одно поле:
| Поле | Описание |
| ---------------------- | -------------------------------------------------------------------------------------------------------- |
| `name` | Переименовать. |
| `disabled` | `true` — временно отключить (ключ получает `401` на любой запрос), `false` — включить обратно. Обратимо. |
| `daily_limit_kopecks` | Новый дневной лимит; `null` — снять лимит. |
| `privacy_mask_default` | Режим маскирования данных. |
Ответ — обновлённый ключ. Типовой приём для сервиса поверх Hubris: клиент не оплатил период — `{"disabled": true}`, оплатил — `{"disabled": false}`.
### Отозвать ключ
Необратимо. Ответ `{"id": "…", "deleted": true}`; повторный вызов по уже отозванному ключу тоже вернёт `200` (идемпотентно). Для временной блокировки используйте `PATCH` с `disabled: true`.
## Баланс — `GET /v1/credits`
Требует management-ключ.
```json
{
"data": {
"balance_kopecks": 152030,
"bonus_balance_kopecks": 5000,
"currency": "RUB"
}
}
```
`152030` копеек = 1520,30 ₽. Бонусный баланс — невыводимые кредиты (реферальные начисления и акции).
## Текущий ключ — `GET /v1/key`
Самоинтроспекция: работает **обычным** ключом — management-ключ не нужен. Главная ручка для агентов: один запрос своим же рабочим ключом — и агент знает свой лимит, расход за сутки и баланс аккаунта.
```json
{
"data": {
"name": "prod-bot",
"key_prefix": "sk-gw-ab12...cd34",
"key_type": "inference",
"disabled": false,
"daily_limit_kopecks": 20000,
"spent_24h_kopecks": 512,
"balance_kopecks": 152030,
"created_at": "2026-07-01T12:00:00.000Z"
}
}
```
## Транзакции — `GET /v1/transactions`
Требует management-ключ. Пополнения, списания, возвраты и бонусные операции, свежие сверху — удобно для сверки и бухгалтерии.
Query-параметры: `offset`, `limit` (1–100, по умолчанию 50), `type` (например, `topup`, `usage`, `refund`), `from`/`to` (ISO 8601).
```json
{
"object": "list",
"data": [
{
"id": "9d2e4c11-…",
"type": "topup",
"status": "succeeded",
"amount_kopecks": 100000,
"balance_after_kopecks": 152030,
"payment_method": "sbp",
"created_at": "2026-07-15T10:00:00.000Z"
}
],
"has_more": true
}
```
Положительный `amount_kopecks` — зачисление, отрицательный — списание. `payment_method` (`sbp` / `card` / `invoice`) заполнен только у пополнений.
## Расход по всем ключам — `GET /v1/usage`
[Обычный вызов `/v1/usage`](/docs/api/usage) показывает расход только того ключа, которым сделан запрос. **Management-ключом** тот же эндпоинт видит весь аккаунт и получает два дополнительных параметра:
| Параметр | Описание |
| ---------- | ------------------------------------------------------------------------------------- |
| `key_id` | Сузить до одного ключа (UUID из `GET /v1/keys`). |
| `group_by` | `key` — добавить массив `by_key` (расход каждого ключа), `model` — массив `by_model`. |
В `by_key[]`: `key_id`, `key_prefix`, `name`, `requests`, `total_tokens`, `cost_kopecks` (строка, копейки). В `by_model[]`: `model`, `requests`, `prompt_tokens`, `completion_tokens`, `cost_kopecks`.
## HTTP-коды
| Код | Когда |
| ----------------------------- | ------------------------------------------------------------------ |
| `400 invalid_request` | Невалидное тело или параметры; `key_id`/`group_by` обычным ключом. |
| `401 invalid_api_key` | Ключ не передан, неверный, отозван или отключён. |
| `403 management_key_required` | Management-эндпоинт вызван обычным ключом. |
| `403 inference_key_required` | Модельный эндпоинт вызван management-ключом. |
| `404 not_found` | Ключ не найден (или не ваш). |
| `429 rate_limit_exceeded` | Превышен лимит 60/мин или 30 созданий/час. |
| `429 key_quota_exceeded` | Достигнут потолок 500 активных ключей. |
Формат ошибок стандартный: `{"error": {"message", "type", "code"}}` — см. [ошибки](/docs/concepts/errors).
## Что дальше
* [GET /v1/usage](/docs/api/usage) — формы ответа расхода, шорткаты периодов.
* [Обзор API](/docs/api/overview) — авторизация, базовый URL, совместимость с OpenAI SDK.
* [Маскирование данных](/docs/features/privacy) — что такое `privacy_mask_default`.
---
# POST /v1/messages (/docs/api/messages)
> Эндпоинт, совместимый с Anthropic Messages API. Используется Claude Code и любым SDK Anthropic.
import { CodeTabs } from '@/components/docs/code-tabs';
import { EndpointBadge } from '@/components/docs/endpoint-badge';
`/v1/messages` — эндпоинт, совместимый с [Anthropic Messages API](https://docs.anthropic.com/claude/reference/messages_post). Через него работают [Claude Code](/docs/integrations/claude-code), официальные SDK Anthropic для Python/TypeScript и любые инструменты, которые ожидают именно этот формат запросов.
## Когда использовать
* Вы подключаете Claude Code или другой инструмент, поддерживающий только Anthropic-формат.
* Вам нужно нативное `prompt caching` (`cache_control: { "type": "ephemeral" }`).
* Вам удобнее работать с Anthropic-style блоками контента (`text`, `image`, `tool_use`, `tool_result`).
Для типичных задач — [POST /v1/chat/completions](/docs/api/chat-completions) проще и поддерживает больше моделей.
## Эндпоинт
## Аутентификация
Принимаются оба заголовка:
| Заголовок | Когда используется |
| --------------------------------- | ---------------------------------------------------------------- |
| `Authorization: Bearer sk-gw-...` | Claude Code (`ANTHROPIC_AUTH_TOKEN`), произвольные cURL-запросы. |
| `x-api-key: sk-gw-...` | Anthropic Python/TypeScript SDK по умолчанию. |
Если переданы оба — приоритет у `Authorization`. Подойдут API-ключи Hubris формата `sk-gw-…`, созданные на [/keys](https://hubris.pw/keys).
Также нужен `anthropic-version: 2023-06-01` (Anthropic-спека) и `Content-Type: application/json`.
## Тело запроса
| Поле | Тип | По умолчанию | Описание |
| ------------------ | ------------------- | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`model`** | `string` | — | Идентификатор модели. Принимает Anthropic-имена (`claude-sonnet-4-5`) и каноничные Hubris-имена (`anthropic/claude-sonnet-4.6`). См. [Имена моделей](#имена-моделей). |
| **`max_tokens`** | `integer` > 0 | — | Лимит токенов в ответе. Обязательное (Anthropic-спека). |
| **`messages`** | `array` | — | Цепочка сообщений. Минимум 1. `role: "user" \| "assistant"`. |
| `system` | `string \| Block[]` | — | Системная инструкция (отдельно от `messages`, как в Anthropic). |
| `tools` | `array` | — | Anthropic Tool Use. См. [ниже](#использование-инструментов-tools). |
| `tool_choice` | `object` | `{type:"auto"}` | `{type:"auto" \| "any" \| "tool", name?}`. |
| `temperature` | `number` 0–1 | `1` | Температура сэмплирования. |
| `top_p` | `number` 0–1 | — | Nucleus sampling. |
| `top_k` | `integer` ≥ 0 | — | Top-K sampling. |
| `stop_sequences` | `string[]` | — | До 4 стоп-последовательностей. |
| `stream` | `boolean` | `false` | Anthropic SSE. См. [Потоковая передача](#потоковая-передача). |
| `metadata.user_id` | `string` | — | Anthropic-метка пользователя для трекинга абьюза. |
## Минимальный запрос
`max_tokens` — обязательное поле (Anthropic-спека). Если не указать, вернётся `400 invalid_request_error`.
## Имена моделей
Эндпоинт принимает три формы записи имени модели:
| Форма | Пример | Куда маппится |
| ----------------------------------- | ------------------------------------------------------- | ----------------------------------------------------------- |
| Каноничная (с префиксом провайдера) | `anthropic/claude-sonnet-4.6` | без изменений |
| Стабильная (без даты) | `claude-sonnet-4-5`, `claude-haiku-4-5` | `anthropic/claude-sonnet-4.5`, `anthropic/claude-haiku-4.5` |
| С суффиксом даты или `-latest` | `claude-3-5-sonnet-20241022`, `claude-haiku-4-5-latest` | соответствующая каноничная версия |
Список активных Claude-моделей доступен через [/v1/models](/docs/api/models) с фильтром `provider=anthropic`.
## Ответ
Anthropic-нативный формат:
```json
{
"id": "msg_01XYZ",
"type": "message",
"role": "assistant",
"content": [
{ "type": "text", "text": "Привет!" }
],
"model": "claude-sonnet-4-5",
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": {
"input_tokens": 17,
"output_tokens": 5,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 0
}
}
```
Поле `model` в ответе содержит то имя, которое вы прислали в запросе (а не каноничное), — это удобно для согласованности логов и совместимости со старыми SDK.
## Потоковая передача
Параметр `stream: true` включает Anthropic SSE в нативном формате:
```
event: message_start
data: {"type":"message_start","message":{...,"usage":{"input_tokens":17}}}
event: content_block_start
data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Привет"}}
event: content_block_stop
data: {"type":"content_block_stop","index":0}
event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"end_turn"},"usage":{"output_tokens":5}}
event: message_stop
data: {"type":"message_stop"}
event: data
data: [DONE]
```
Финальное `usage` приходит в событии `message_delta`. Если клиент разорвал соединение раньше — Hubris всё равно дочитывает апстрим до конца и списывает стоимость с баланса.
## Использование инструментов (tools)
Поддерживается полный набор Anthropic Tool Use: `tools`, `tool_choice`, блоки `tool_use` в ответе, блоки `tool_result` во входе. Параметры передаются без изменений на провайдера:
```bash
curl https://api.hubris.pw/v1/messages \
-H "Authorization: Bearer sk-gw-..." \
-d '{
"model": "claude-sonnet-4-5",
"max_tokens": 1024,
"tools": [{
"name": "get_weather",
"description": "Возвращает погоду в указанном городе",
"input_schema": {
"type": "object",
"properties": { "city": { "type": "string" } },
"required": ["city"]
}
}],
"messages": [
{ "role": "user", "content": "Какая погода в Москве?" }
]
}'
```
## Vision (изображения на вход)
Блоки `image` в `content[]` принимают и base64, и URL-источник:
```json
{
"role": "user",
"content": [
{ "type": "text", "text": "Что на картинке?" },
{
"type": "image",
"source": {
"type": "base64",
"media_type": "image/png",
"data": "iVBORw0KGgoAAAA..."
}
}
]
}
```
## Кэширование промптов
`cache_control: { "type": "ephemeral" }` ставится на блок контента или инструмента так же, как и в нативном Anthropic API. Hubris передаёт поле без изменений; биллинг учитывает `cache_creation_input_tokens` и `cache_read_input_tokens` по фактической стоимости, которую вернул провайдер.
```json
{
"role": "user",
"content": [
{
"type": "text",
"text": "<длинный системный контекст>",
"cache_control": { "type": "ephemeral" }
},
{ "type": "text", "text": "Вопрос пользователя" }
]
}
```
## Ошибки
Возвращаются в нативном Anthropic-формате:
```json
{
"type": "error",
"error": {
"type": "invalid_request_error",
"message": "max_tokens: Required"
}
}
```
| HTTP | error.type | Что значит |
| --------- | ----------------------- | ------------------------------------------------------------------------- |
| 400 | `invalid_request_error` | Неверное тело запроса (нет `max_tokens`, пустые `messages` и т.д.). |
| 401 | `authentication_error` | Невалидный API-ключ или проблемы у апстрима. |
| 402 | `billing_error` | Баланс ниже минимума. Пополните на [/billing](https://hubris.pw/billing). |
| 404 | `not_found_error` | Модель не найдена в каталоге или имя не парсится. |
| 413 | `request_too_large` | Тело запроса больше 36 МБ. |
| 429 | `rate_limit_error` | Превышен дневной лимит ключа или лимит апстрима. |
| 500 / 502 | `api_error` | Внутренняя ошибка апстрима. |
| 504 | `timeout_error` | Апстрим не ответил в отведённое время. |
## Privacy Mode
На этом эндпоинте Privacy Mode (маскирование PII) **пока не поддерживается** — для запросов с маскированием используйте [POST /v1/chat/completions](/docs/api/chat-completions). Это ограничение MVP, поддержка планируется.
## Тарификация
Считаем по `usage.cost`, который провайдер возвращает в ответе (включая поправку на cache hit / cache write). В редком случае, когда `cost` отсутствует, переключаемся на расчёт по токенам и каталожной цене модели. Подробности — в [Биллинге](/docs/concepts/billing).
## Что дальше
* [Подключение Claude Code](/docs/integrations/claude-code) — подробный гид с настройкой и statusline-скриптом.
* [POST /v1/chat/completions](/docs/api/chat-completions) — основной эндпоинт, поддерживает больше моделей.
* [Каталог моделей](/models) — фильтр `provider:anthropic` для списка Claude-моделей.
* [Ошибки](/docs/concepts/errors) — полная таблица кодов.
---
# GET /v1/models (/docs/api/models)
> Список доступных моделей с актуальными ценами в рублях.
import { CodeTabs } from '@/components/docs/code-tabs';
import { EndpointBadge } from '@/components/docs/endpoint-badge';
Возвращает список всех активных моделей с актуальными ценами в ₽. Стандартный OpenAI-формат с расширениями Hubris: `display_name`, `description`, `context_window`, `modalities`, `pricing`.
Полный [каталог в UI](/models) — там удобнее сравнивать цены и фильтровать.
## Эндпоинт
Заголовки:
| Header | Значение |
| --------------- | -------------------------------- |
| `Authorization` | `Bearer sk-gw-...` (обязательно) |
Тело запроса не передаётся. Параметры query-string не поддерживаются — для фильтрации обрабатывайте ответ на стороне клиента.
## Пример
console.log(m.id));`
},
]}
/>
## Ответ
```json
{
"object": "list",
"data": [
{
"id": "anthropic/claude-haiku-4.5",
"object": "model",
"created": 1714000000,
"owned_by": "anthropic",
"display_name": "Claude Haiku 4.5",
"description": "Быстрая и дешёвая модель Anthropic. Подходит для большинства задач.",
"context_window": 200000,
"max_output_tokens": 8192,
"modalities": {
"input": ["text", "image"],
"output": ["text"]
},
"pricing": {
"prompt_per_1m_rub": 96.50,
"completion_per_1m_rub": 482.50,
"image_input_per_1k_rub": 14.48
}
}
]
}
```
### Поля ответа
| Поле | Тип | Описание |
| ------------------------------------------ | -------------- | -------------------------------------------------------------------------------- |
| **`object`** | `"list"` | Тип контейнера. |
| **`data[]`** | `array` | Массив моделей. |
| **`data[].id`** | `string` | Идентификатор для использования в `model` запроса (формат `/`). |
| **`data[].object`** | `"model"` | Тип элемента. |
| **`data[].created`** | `integer` | Unix-timestamp когда модель появилась в каталоге Hubris. |
| **`data[].owned_by`** | `string` | Провайдер модели (`anthropic`, `openai`, `google`, ...). |
| **`data[].display_name`** | `string` | Человеко-читаемое имя для UI. |
| **`data[].description`** | `string` | Краткое описание модели. |
| **`data[].context_window`** | `integer` | Размер контекста в токенах. |
| **`data[].max_output_tokens`** | `integer` | Максимум токенов в ответе. |
| **`data[].modalities.input`** | `string[]` | Что принимает: `text`, `image`, `audio`. |
| **`data[].modalities.output`** | `string[]` | Что возвращает: `text`, `image`, `embeddings`. |
| **`data[].pricing.prompt_per_1m_rub`** | `number` | Цена входных токенов за 1 миллион, в ₽. |
| **`data[].pricing.completion_per_1m_rub`** | `number` | Цена выходных токенов за 1 миллион, в ₽. |
| `data[].pricing.image_input_per_1k_rub` | `number` | Цена 1000 входных изображений (для vision-моделей). |
Цены пересчитываются каждые несколько часов по курсу ЦБ РФ + наш markup. Точная формула — [Цены](/docs/concepts/pricing).
## Фильтрация
API не поддерживает query-параметры. Фильтруйте локально:
```python
models = client.models.list().data
# Только embedding-модели
embed_models = [m for m in models if 'embeddings' in m.modalities['output']]
# Vision-модели Anthropic дешевле 100₽/1M токенов
cheap_vision = [
m for m in models
if m.owned_by == 'anthropic'
and 'image' in m.modalities['input']
and m.pricing['prompt_per_1m_rub'] < 100
]
```
## HTTP-коды
| Код | Когда |
| ----- | ----------------------------------------------------------------------------- |
| `200` | Успешный ответ. |
| `401` | Ключ отсутствует / отозван / невалиден. |
| `429` | Превышен дневной лимит на ключе. |
| `503` | Курс ЦБ временно недоступен — каталог не отдаёт цены. Повторите через минуту. |
## Что дальше
* [Модели](/docs/concepts/models) — обзор каталога и как выбрать модель.
* [Цены](/docs/concepts/pricing) — формула расчёта стоимости.
* [Каталог в UI](/models) — удобнее искать и сравнивать.
---
# Обзор API (/docs/api/overview)
> Базовый URL, заголовки, аутентификация и общие соглашения Hubris API.
Hubris API совместим с OpenAI Chat Completions API. Все запросы — поверх HTTPS, авторизация через Bearer-токен, ответы — JSON с UTF-8.
## Базовый URL
```
https://api.hubris.pw/v1
```
Все эндпоинты идут от этого префикса. Например, `https://api.hubris.pw/v1/chat/completions` или `https://api.hubris.pw/v1/models`.
## Заголовки
Обязательные:
* `Authorization: Bearer sk-gw-<32-hex>` — ваш API-ключ. См. [Аутентификацию](/docs/authentication).
* `Content-Type: application/json` — для всех `POST`-запросов.
Опциональные:
* `Accept: application/json` — явно ожидать JSON-ответ. По умолчанию мы и так отдаём JSON.
* `Accept: text/event-stream` — при `stream: true` сервер автоматически переключается в SSE-режим.
## Эндпоинты
| Метод | Путь | Описание |
| ----- | ---------------------- | ---------------------------------------------------- |
| GET | `/v1/models` | Список доступных моделей с ценами в ₽ |
| POST | `/v1/chat/completions` | Создать ответ модели (с потоковой передачей или без) |
| POST | `/v1/responses` | Создать ответ через Responses API (бета) |
## Версионирование
Префикс `/v1` — стабильный. Все несовместимые изменения эндпоинтов будут идти под `/v2/...` (или новее), не ломая существующие интеграции.
Метка **бета** у `/v1/responses` относится только к составу внутренних блоков ответа (`output[]`) — туда со временем добавляются новые типы (рассуждения, вызовы инструментов и т.д.) по мере того, как их вводят провайдеры моделей. Авторизация, тарификация и потоковая передача — стабильны и подходят для продакшена. Если ваш клиент строго разбирает конкретные типы блоков, будьте готовы расширять обработку по мере их появления.
## Идемпотентность
`POST /v1/chat/completions` и `POST /v1/responses` не идемпотентны — каждый запрос порождает новый ответ модели, независимо от тела. Если у вас на стороне клиента включены повторы при сбоях, помните: каждая повторная попытка спишет деньги, если первая прошла успешно (что иногда бывает при кратковременных сбоях сети).
`GET /v1/models` идемпотентен — ответ кешируется на стороне Hubris \~60 секунд.
## Потоковая передача
При `stream: true` ответ приходит через Server-Sent Events. Подробности формата — на отдельной странице [Потоковая передача](/docs/api/streaming).
## Ошибки
Все ошибки в OpenAI-формате `{ error: { message, type, code } }`. Полный список кодов — на странице [Ошибки](/docs/concepts/errors).
## Что дальше
* [POST /v1/chat/completions](/docs/api/chat-completions) — основной эндпоинт.
* [GET /v1/models](/docs/api/models) — каталог.
* [Потоковая передача](/docs/api/streaming) — формат SSE.
* [POST /v1/responses](/docs/api/responses) — бета-эндпоинт для Responses API (рассуждающие модели, Codex CLI и т.п.).
---
# POST /v1/responses (бета) (/docs/api/responses)
> Эндпоинт OpenAI Responses API. Подходит для рассуждающих моделей и агентов.
import { CodeTabs } from '@/components/docs/code-tabs';
import { EndpointBadge } from '@/components/docs/endpoint-badge';
> **Бета.** Авторизация, тарификация и потоковая передача стабильны и подходят для продакшена. Метка беты относится к составу внутренних блоков `output[]` (текст, рассуждения, вызовы инструментов) — туда со временем добавляются новые типы по мере появления у провайдеров моделей. Если ваш клиент строго разбирает конкретные блоки — будьте готовы расширять обработку.
`/v1/responses` — альтернативный эндпоинт OpenAI Responses API (без сохранения состояния на стороне сервера). Подходит для рассуждающих моделей (o-серия, Claude с расширенным рассуждением), для агентских CLI вроде Codex и для абстракций, где нужен более гибкий контейнер ответа, чем `chat.completion`.
## Когда использовать
Чаще всего вам нужен [POST /v1/chat/completions](/docs/api/chat-completions) — он совместим с большинством OpenAI-инструментов. `/v1/responses` стоит выбрать только если:
* Вы работаете с рассуждающими моделями (o1, o3, Claude с расширенным рассуждением) и хотите явно управлять блоками рассуждения.
* Вы строите агентский фреймворк, которому нужен единый контейнер для разных типов выходных блоков.
* Используете агентский CLI вроде [Codex](/docs/frameworks/codex-cli), который ходит только в Responses API.
* Конкретный клиент или провайдер требует именно этот формат.
## Эндпоинт
Заголовки:
| Header | Значение |
| --------------- | ---------------------------------------------------------------------------------- |
| `Authorization` | `Bearer sk-gw-...` (обязательно) |
| `Content-Type` | `application/json` (обязательно) |
| `Accept` | `application/json` или `text/event-stream` (опционально; при `stream: true` — SSE) |
## Тело запроса
| Поле | Тип | По умолчанию | Описание |
| ----------- | -------------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
| **`model`** | `string` | — | Идентификатор модели, например `openai/o1-mini`. |
| **`input`** | `string \| object[]` | — | Запрос. Простая строка ИЛИ массив input-блоков (см. [OpenAI Responses API](https://platform.openai.com/docs/api-reference/responses)). |
| `stream` | `boolean` | `false` | Стриминг ответа через SSE. |
Дополнительные параметры (instructions, tools, temperature и т.д.) передаются модели как есть — Hubris их не нормализует. Полная схема — в [OpenAI Responses API docs](https://platform.openai.com/docs/api-reference/responses).
### Хранение ответов не поддерживается
Hubris не хранит созданные response — поэтому поле `store` всегда трактуется как `false`, каким бы вы его ни прислали, а `previous_response_id` и `conversation` не работают. Историю диалога передавайте целиком в `input`.
Практическое следствие: клиенты, которые ставят `store: true` по умолчанию (официальный OpenAI SDK, нода n8n «Message a model»), работают без дополнительной настройки — значение просто игнорируется.
## Минимальный пример
`input` может быть строкой или массивом блоков — передаваемый формат зависит от модели.
## Ответ
```json
{
"id": "resp_abc123",
"object": "response",
"created_at": 1714000000,
"model": "openai/gpt-4o-mini",
"output": [
{
"type": "message",
"role": "assistant",
"content": [
{ "type": "output_text", "text": "Привет! Хорошо, спасибо." }
]
}
],
"usage": {
"input_tokens": 8,
"output_tokens": 12,
"total_tokens": 20,
"cost": 24
},
"status": "completed"
}
```
### Поля ответа
| Поле | Тип | Описание |
| ------------------------- | ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`id`** | `string` | Уникальный идентификатор response. Префикс `resp_`. |
| **`object`** | `"response"` | Тип объекта. |
| **`created_at`** | `integer` | Unix-timestamp (секунды). |
| **`model`** | `string` | Фактически использованная модель. |
| **`output[]`** | `array` | Выходные блоки. Структура зависит от модели и версии — может содержать `message`, `reasoning`, `tool_call` и другие типы. Hubris передаёт как есть, без нормализации. |
| **`usage.input_tokens`** | `integer` | Токены ввода. |
| **`usage.output_tokens`** | `integer` | Токены вывода (включая reasoning-токены, если модель их использует). |
| **`usage.total_tokens`** | `integer` | Сумма. |
| **`usage.cost`** | `integer` | Стоимость **в копейках**. Hubris-расширение. |
| **`status`** | `"completed" \| "in_progress" \| "failed" \| ...` | Состояние генерации. |
## Стриминг
`stream: true` включает SSE. Завершающее событие `response.completed` содержит полный объект `response` вместе с `usage`. Hubris списывает стоимость из этого события (или, в редком случае обрыва до его прихода, — из оценки по длине ввода).
```bash
curl -N -s https://api.hubris.pw/v1/responses \
-H "Authorization: Bearer sk-gw-..." \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-4o-mini",
"input": "Объясни квантовую запутанность",
"stream": true
}'
```
Формат событий и их обработка — на странице [Стриминг](/docs/features/streaming).
## HTTP-коды
| Код | Когда |
| ----------------- | ----------------------------------------------------------------------- |
| `200` | Успешный ответ. |
| `400` | Тело запроса не соответствует схеме. |
| `401` | Ключ отсутствует / отозван / невалиден. |
| `402` | Недостаточно средств на балансе. |
| `404` | Модели с таким `model` не существует или не поддерживает Responses API. |
| `413` | Тело запроса больше 36 МБ. |
| `429` | Превышен дневной лимит на ключе. |
| `502 / 503 / 504` | Транзиентные сбои у провайдера или курса ЦБ. |
Формат тела ошибки и стратегия retry — [Ошибки](/docs/concepts/errors).
## Тарификация
Считаем так же, как и для `/v1/chat/completions`: входные + выходные токены × цены модели в ₽. Токены рассуждения (если модель их использует) учитываются в `output_tokens` и тарифицируются как обычные выходные токены.
## Что дальше
* [POST /v1/chat/completions](/docs/api/chat-completions) — основной эндпоинт, подходит большинству задач.
* [Codex CLI](/docs/frameworks/codex-cli) — как подключить агентский CLI к Hubris через Responses API.
* [Стриминг](/docs/features/streaming) — формат событий SSE.
* [Ошибки](/docs/concepts/errors) — таблица кодов.
---
# POST /v1/audio/speech (/docs/api/speech)
> Озвучка текста (TTS) — текст на входе, готовый аудиофайл на выходе. OpenAI-совместимый формат, работает официальный openai SDK.
import { CodeTabs } from '@/components/docs/code-tabs';
import { EndpointBadge } from '@/components/docs/endpoint-badge';
Превращает текст в речь: озвучка статей и рассылок, голосовые ответы бота, аудиоверсии инструкций, реплики персонажей. OpenAI-совместимый формат — без переписывания работают `openai` Python/TypeScript SDK и любые клиенты под OpenAI Audio API: достаточно поменять `base_url`.
Ответ — **сами байты аудио**, а не JSON: сохраняйте их в файл или отдавайте плееру. Стрима по флагу нет — один POST возвращает один готовый ответ целиком (его можно читать потоково, но это всё тот же единственный ответ). Модели озвучки — в [каталоге](/models) (фильтр «Озвучка») или запросом `GET /v1/models?output_modalities=speech`: MAI Voice, Aura-2, MiniMax Speech, Kokoro, Orpheus, Zonos, Voxtral TTS и другие.
## Эндпоинт
Заголовки:
| Header | Значение |
| --------------- | -------------------------------- |
| `Authorization` | `Bearer sk-gw-...` (обязательно) |
| `Content-Type` | `application/json` |
## Тело запроса
| Поле | Тип | По умолчанию | Описание |
| ----------------- | -------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| **`model`** | `string` | — | ID модели озвучки из каталога. |
| **`input`** | `string` | — | Текст для озвучки, до 50 000 символов. Тарифицируется по количеству символов. |
| **`voice`** | `string` | — | Голос модели. Обязателен, у каждой модели свой набор — см. ниже. |
| `response_format` | `string` | зависит от модели | `mp3` или `pcm` (16-bit LE). Если поле не передать, формат выбирает модель — как правило, это `pcm`; для файла на диске указывайте `mp3` явно. |
Дополнительные поля, которые понимает конкретная модель (например, скорость речи или инструкции по стилю), передаются ей как есть — схема их не отбрасывает. Тело запроса ограничено 1 МБ: это текст, а не медиа, лимита хватает с большим запасом.
## Голоса
`voice` обязателен, и наборы голосов у моделей не пересекаются: где-то их два, где-то девяносто. Список голосов конкретной модели — в её карточке в [каталоге](/models). Часть провайдеров список не публикует — тогда берите значение из документации самой модели.
Если голос не из списка, который знает каталог, запрос отклоняется **до** обращения к модели — с кодом `400`, `code: "voice_not_supported"`, и в тексте ошибки перечислены доступные значения:
```json
{
"error": {
"message": "Голос «нет-такого» недоступен у модели microsoft/mai-voice-2-flash. Доступные: en-US-Harper:MAI-Voice-2, …",
"type": "invalid_request_error",
"code": "voice_not_supported"
}
}
```
Если голосов у модели много, в сообщении перечислены первые восемь и число оставшихся. За отклонённый таким образом запрос ничего не списывается.
## Минимальный пример
Модель и голос всегда идут парой: сменили модель — берите голос из её карточки в каталоге.
Python-вариант с `with_streaming_response` читает тот же единственный ответ по мере поступления байтов — это удобно для длинного текста, но никакого пофразового стрима, как в чате, здесь нет.
## Ответ
`200` — тело целиком состоит из байтов аудио. JSON в успешном ответе не приходит, поэтому и поля `usage` в нём нет: расход по запросу смотрите в [журнале](/logs).
| Заголовок | Значение |
| ---------------- | ----------------------------------------------------------------- |
| `Content-Type` | `audio/mpeg` при `response_format: "mp3"`, `audio/pcm` при `pcm`. |
| `Content-Length` | Размер аудио в байтах. |
| `Cache-Control` | `no-store` — ответ не кэшируется. |
`pcm` — это сырые сэмплы без заголовка WAV: их либо скармливают плееру/микшеру напрямую, либо оборачивают в контейнер. Если нужен файл, который просто откроется двойным кликом, берите `mp3`.
Ошибки, наоборот, всегда приходят JSON-ом в обычном формате — по `Content-Type` ответа легко отличить одно от другого.
## HTTP-коды
| Код | `code` | Когда |
| ----------------- | ---------------------- | -------------------------------------------------------------------------------------------------------------- |
| `200` | — | Успех, в теле — аудио. |
| `400` | `voice_not_supported` | Голос не входит в набор этой модели (в тексте ошибки — доступные). |
| `400` | `invalid_request` | Тело не соответствует схеме: нет `model`, `input` или `voice`, неизвестный `response_format`, невалидный JSON. |
| `401` | — | Ключ отсутствует / отозван / невалиден. |
| `402` | `insufficient_balance` | Недостаточно средств на балансе. |
| `404` | `model_not_found` | Модели с таким `model` не существует или это не модель озвучки. |
| `413` | `request_too_large` | Тело запроса больше 1 МБ. |
| `429` | — | Превышен дневной лимит на ключе. |
| `502` | `pricing_unavailable` | У модели нет ставки в каталоге — считать стоимость нечем, запрос не выполняется. |
| `502` | `no_audio_returned` | Модель ответила успехом, но пустым телом. Списания нет, запрос стоит повторить. |
| `502 / 503 / 504` | — | Транзиентные сбои провайдера, курса ЦБ или таймаут озвучки. |
Формат тела ошибки и стратегия retry — [Ошибки](/docs/concepts/errors).
## Биллинг
Озвучка тарифицируется **за символы входного текста** — не за токены и не за длительность получившегося аудио. Стоимость поэтому известна заранее: сколько символов отправили, столько и оплатили, независимо от того, насколько неторопливо модель их проговорила. Ставка каждой модели видна в её карточке в [каталоге](/models).
Списание — по факту выполненного запроса, в рублях; сумма попадает в [журнал запросов](/logs) вместе с моделью и временем. За отклонённые до обращения к модели запросы (`voice_not_supported`, `model_not_found`, невалидное тело) и за пустой ответ модели не списывается ничего.
## Что дальше
* [POST /v1/audio/transcriptions](/docs/api/transcriptions) — обратная задача: запись в текст.
* [Аудио в чате](/docs/features/audio) — модель рассуждает о записи или отвечает голосом внутри диалога.
* [Модели](/docs/concepts/models) — как выбрать модель и прочитать её карточку.
* [Ошибки](/docs/concepts/errors) — что делать на 429/502.
* [Цены](/docs/concepts/pricing) — как считается стоимость запроса.
---
# Потоковая передача (/docs/api/streaming)
> Формат Server-Sent Events для потокового ответа /v1/chat/completions.
При `stream: true` ответ от `/v1/chat/completions` приходит как Server-Sent Events: каждый chunk — отдельная строка `data: ` с разделителем `\n\n`. Поток завершается строкой `data: [DONE]`.
## Заголовки ответа
```
HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
X-Accel-Buffering: no
```
## Формат chunk-а
Каждый chunk — JSON-объект `chat.completion.chunk`:
```json
{
"id": "chatcmpl-abc123",
"object": "chat.completion.chunk",
"created": 1714000000,
"model": "anthropic/claude-haiku-4.5",
"choices": [
{
"index": 0,
"delta": { "role": "assistant", "content": "О" },
"finish_reason": null
}
]
}
```
В первом chunk-е `delta` обычно содержит `role: "assistant"`. Следующие — только инкремент `content`. Последний chunk перед `[DONE]` имеет `finish_reason: "stop"` (или `length`, `tool_calls`, и т.д.) и поле `usage` с итоговыми токенами:
```json
{
"id": "chatcmpl-abc123",
"object": "chat.completion.chunk",
"created": 1714000000,
"model": "anthropic/claude-haiku-4.5",
"choices": [
{
"index": 0,
"delta": {},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 12,
"completion_tokens": 1,
"total_tokens": 13
}
}
```
После `usage`-chunk-а поток завершается:
```
data: [DONE]
```
Hubris автоматически добавляет `stream_options: { include_usage: true }` ко всем stream-запросам, поэтому `usage` приходит всегда — в том числе если вы явно передали `include_usage: false` (значение перезаписывается: по итоговому `usage` считается списание). Учитывайте, что финальный usage-чанк приходит с пустым `choices: []` — читайте `chunk.choices[0]` с проверкой.
## Парсинг
Парсите построчно с буфером: один chunk может прийти разорванным на несколько TCP-сегментов. Псевдокод:
```python
buffer = ""
for raw_bytes in response.iter_content():
buffer += raw_bytes.decode("utf-8")
while "\n\n" in buffer:
line, buffer = buffer.split("\n\n", 1)
if not line.startswith("data: "):
continue
payload = line[len("data: "):]
if payload == "[DONE]":
return
chunk = json.loads(payload)
# обработка delta
```
Не парсьте `[DONE]` как JSON — это литерал-маркер, не JSON.
## Дисконнект
Если клиент закрыл соединение до окончания стрима, Hubris всё равно дочитывает ответ от провайдера и списывает деньги — это защищает от «бесплатных токенов» через abort. Если вы намеренно прерываете запрос (например, по timeout-у), ожидайте полное списание стоимости.
## Что дальше
* [POST /v1/chat/completions](/docs/api/chat-completions) — параметры запроса.
* [Ошибки](/docs/concepts/errors) — что бывает при сбое стрима.
---
# POST /v1/audio/transcriptions (/docs/api/transcriptions)
> Распознавание речи (транскрибация аудио в текст). OpenAI-совместимый формат — работает официальный openai SDK.
import { CodeTabs } from '@/components/docs/code-tabs';
import { EndpointBadge } from '@/components/docs/endpoint-badge';
Превращает аудиозапись в текст: голосовые сообщения, звонки, подкасты, интервью, субтитры. OpenAI-совместимый формат — без переписывания работают `openai` Python/TypeScript SDK и любые клиенты под OpenAI Audio API: достаточно поменять `base_url`.
Стрима нет: один POST → один JSON-ответ с готовым текстом (`stream: true` отклоняется с кодом 400). Полный список моделей распознавания — в [каталоге](/models) (фильтр «Транскрибация»): Whisper, GPT-4o Transcribe, Voxtral, Chirp и другие.
## Эндпоинт
Заголовки:
| Header | Значение |
| --------------- | ------------------------------------------------------------ |
| `Authorization` | `Bearer sk-gw-...` (обязательно) |
| `Content-Type` | `multipart/form-data` (файл) или `application/json` (base64) |
## Тело запроса
Поддерживаются два формата — результат одинаковый.
### Вариант 1: multipart/form-data (как у OpenAI)
| Поле | Тип | По умолчанию | Описание |
| --------------------------- | ---------- | ------------ | --------------------------------------------------------------------------------------------------------------------- |
| **`file`** | `file` | — | Аудиофайл. Форматы: WAV, MP3, FLAC, M4A, OGG, WebM, AAC. До 25 МБ. |
| **`model`** | `string` | — | ID модели распознавания, например `openai/whisper-1`. |
| `language` | `string` | авто | Язык записи в ISO-639-1 (`ru`, `en`). Явное указание ускоряет и уточняет распознавание. |
| `prompt` | `string` | — | Подсказка: имена, термины, аббревиатуры, которые встречаются в записи. |
| `temperature` | `number` | `0` | 0–1. Выше — креативнее, ниже — детерминированнее. |
| `response_format` | `string` | `json` | `json` или `verbose_json` (поддержка `verbose_json` зависит от модели). Форматы `text`/`srt`/`vtt` не поддерживаются. |
| `timestamp_granularities[]` | `string[]` | — | `segment` и/или `word` — таймстемпы в `verbose_json` (поддерживается не всеми моделями). |
### Вариант 2: application/json (аудио в base64)
| Поле | Тип | Описание |
| ------------------------ | -------- | ---------------------------------------------------------- |
| **`model`** | `string` | ID модели распознавания. |
| **`input_audio.data`** | `string` | Аудио в base64 (без `data:`-префикса). |
| **`input_audio.format`** | `string` | Формат: `wav`, `mp3`, `flac`, `m4a`, `ogg`, `webm`, `aac`. |
Прямые URL на аудио не поддерживаются — только файл или base64. Записи длиннее \~10 минут лучше резать на части: у провайдеров есть таймаут обработки (\~60 секунд на запрос).
## Минимальный пример
## Ответ
```json
{
"text": "Привет! Это тестовая запись для распознавания речи.",
"usage": {
"seconds": 4,
"input_tokens": 120,
"output_tokens": 18,
"total_tokens": 138,
"cost": 6
}
}
```
### Поля ответа
| Поле | Тип | Описание |
| --------------------- | --------- | ------------------------------------------------------------------------- |
| **`text`** | `string` | Распознанный текст. Пустая строка — валидный результат (тишина в записи). |
| `usage.seconds` | `number` | Длительность обработанного аудио в секундах (если модель её сообщает). |
| `usage.input_tokens` | `integer` | Токены аудио-входа. |
| `usage.output_tokens` | `integer` | Токены текста на выходе. |
| `usage.total_tokens` | `integer` | Сумма. |
| **`usage.cost`** | `integer` | Стоимость **в копейках**. Hubris-расширение. |
При `response_format: "verbose_json"` модели, которые это поддерживают, дополнительно возвращают `language`, `duration`, `segments[]` и `words[]` с таймстемпами.
## HTTP-коды
| Код | Когда |
| ----------------- | ------------------------------------------------------------------------- |
| `200` | Успешное распознавание. |
| `400` | Нет `file`/`model` (multipart) или `input_audio` (JSON), невалидное тело. |
| `401` | Ключ отсутствует / отозван / невалиден. |
| `402` | Недостаточно средств на балансе. |
| `404` | Модели с таким `model` не существует или это не модель распознавания. |
| `413` | Тело запроса больше лимита (файл до 25 МБ). |
| `429` | Превышен дневной лимит на ключе. |
| `502 / 503 / 504` | Транзиентные сбои у провайдера или курса ЦБ. |
Формат тела ошибки и стратегия retry — [Ошибки](/docs/concepts/errors).
## Биллинг
Стоимость считается по факту каждого запроса и возвращается в `usage.cost` в копейках — та же сумма, что списывается с баланса и видна в [логах](/logs). Часть моделей тарифицируется по длительности записи, часть — по токенам; ориентиры цен — в [каталоге](/models) с фильтром «Транскрибация».
## Что дальше
* [Модели](/docs/concepts/models) — как выбрать модель распознавания.
* [Ошибки](/docs/concepts/errors) — что делать на 429/502.
* [Цены](/docs/concepts/pricing) — формула расчёта.
---
# GET /v1/usage (/docs/api/usage)
> Расход по API-ключу за выбранный период. Удобно для дашбордов и алертов.
import { CodeTabs } from '@/components/docs/code-tabs';
import { EndpointBadge } from '@/components/docs/endpoint-badge';
Возвращает итоговый расход (рубли, копейки, токены, число запросов) **по тому API-ключу, которым сделан запрос**. Расход других ключей того же аккаунта сюда не попадёт — это специально, чтобы один скомпрометированный ключ не показывал общий бюджет.
По умолчанию — последние 24 часа. Период задаётся либо шорткатом `?period=`, либо явными `?from=&to=` (ISO 8601, UTC). Опциональный `?granularity=hour|day` добавит массив `buckets` для построения графика.
## Эндпоинт
Заголовки:
| Header | Значение |
| --------------- | -------------------------------- |
| `Authorization` | `Bearer sk-gw-...` (обязательно) |
## Query-параметры
| Параметр | Тип | По умолчанию | Описание |
| ------------- | -------------------------------------------- | ------------ | ---------------------------------------------------------------------------------------------------------- |
| `period` | `"today" \| "24h" \| "7d" \| "30d" \| "mtd"` | `"24h"` | Шорткат периода. Взаимоисключающий с `from`/`to`. |
| `from` | `string` (ISO 8601, UTC) | — | Начало периода включительно. Требует `to`. |
| `to` | `string` (ISO 8601, UTC) | — | Конец периода включительно. Требует `from`. |
| `granularity` | `"hour" \| "day"` | — | Если задано — в ответе появится `buckets[]` с поминутной разбивкой. Без `granularity` ответ только totals. |
`period` и `from`/`to` нельзя передавать одновременно — будет `400 invalid_request`. Максимальный диапазон при явных датах — 365 дней.
### Шорткаты периода
| `period` | Что считается |
| -------- | ------------------------------------ |
| `today` | С 00:00 UTC текущих суток до сейчас. |
| `24h` | Последние 24 часа (по умолчанию). |
| `7d` | Последние 7 суток. |
| `30d` | Последние 30 суток. |
| `mtd` | С 1-го числа текущего месяца (UTC). |
## Минимальный пример
## Ответ
```json
{
"object": "usage",
"period": {
"from": "2026-05-13T09:29:31.192Z",
"to": "2026-05-14T09:29:31.192Z",
"shortcut": null
},
"scope": {
"key_id": "31fb0d0d-ac86-4f72-815b-bdefd5978747",
"key_prefix": "sk-gw-758f...d4e3"
},
"totals": {
"requests": 2,
"prompt_tokens": 6000,
"completion_tokens": 130,
"total_tokens": 6130,
"cache_read_tokens": 198000,
"cache_write_tokens": 0,
"reasoning_tokens": 0,
"cache_hit_rate": 0.97,
"cache_savings_kopecks": "48150",
"cache_savings_rub": 481.5,
"cost_rub": 54.76,
"cost_kopecks": "5476"
},
"granularity": null,
"buckets": null
}
```
### Поля ответа
| Поле | Тип | Описание |
| ---------------------------------- | ------------------------- | ---------------------------------------------------------------------------------------------- |
| **`object`** | `"usage"` | Тип объекта. |
| **`period.from`** | `string` (ISO 8601 UTC) | Начало периода включительно. |
| **`period.to`** | `string` (ISO 8601 UTC) | Конец периода включительно. |
| **`period.shortcut`** | `string \| null` | Если использовался шорткат — его имя; иначе `null`. |
| **`scope.key_id`** | `string` (UUID) | ID ключа, по которому собирается расход. |
| **`scope.key_prefix`** | `string` | Видимый префикс ключа для логов. |
| **`totals.requests`** | `integer` | Кол-во запросов за период. |
| **`totals.prompt_tokens`** | `integer` | Сумма входных токенов. |
| **`totals.completion_tokens`** | `integer` | Сумма выходных токенов. |
| **`totals.total_tokens`** | `integer` | Сумма обоих. |
| **`totals.cache_read_tokens`** | `integer` | Токены, прочитанные из кеша (дешевле обычного входа). |
| **`totals.cache_write_tokens`** | `integer` | Токены записи кеша (дороже обычного входа). |
| **`totals.reasoning_tokens`** | `integer` | Токены рассуждения — подмножество `completion_tokens`, не прибавляйте их отдельно. |
| **`totals.cache_hit_rate`** | `number` (0..1) | Доля попаданий в кеш за период. |
| **`totals.cache_savings_kopecks`** | `string` | Сколько кеш сэкономил за период (нетто), в копейках. |
| **`totals.cache_savings_rub`** | `number` | То же значение в рублях. |
| **`totals.cost_rub`** | `number` | Итоговая стоимость в рублях. Эквивалент `cost_kopecks / 100`. |
| **`totals.cost_kopecks`** | `string` | Итоговая стоимость в копейках. Строка — чтобы не терять точность на больших суммах (`BigInt`). |
| `granularity` | `"hour" \| "day" \| null` | Если в запросе был задан — повторяется здесь. |
| `buckets[]` | `array \| null` | Если `granularity` задан — массив значений по интервалам. |
| `buckets[].bucket` | `string` (ISO 8601) | Начало интервала. |
| `buckets[].cost_rub` | `number` | Стоимость интервала. |
| `buckets[].cost_kopecks` | `string` | Стоимость в копейках. |
| `buckets[].requests` | `integer` | Кол-во запросов в интервале. |
Всего входных токенов = `prompt_tokens` + `cache_read_tokens` + `cache_write_tokens`. `reasoning_tokens` входят в `completion_tokens`, не прибавляйте их отдельно.
## С разбивкой по дням
С `granularity` в ответе появится `buckets`:
```bash
curl -s -H "Authorization: Bearer $HUBRIS_API_KEY" \
"https://api.hubris.pw/v1/usage?period=7d&granularity=day"
```
```json
{
"granularity": "day",
"buckets": [
{ "bucket": "2026-05-07T00:00:00.000Z", "cost_rub": 0, "cost_kopecks": "0", "requests": 0 },
{ "bucket": "2026-05-08T00:00:00.000Z", "cost_rub": 0, "cost_kopecks": "0", "requests": 0 },
{ "bucket": "2026-05-14T00:00:00.000Z", "cost_rub": 54.76, "cost_kopecks": "5476", "requests": 2 }
]
}
```
## Произвольный диапазон
```bash
curl -s -H "Authorization: Bearer $HUBRIS_API_KEY" \
"https://api.hubris.pw/v1/usage?from=2026-04-01T00:00:00Z&to=2026-05-01T00:00:00Z"
```
## Точность
* `cost_kopecks` — **строка**, чтобы не терять точность на больших суммах (JavaScript `Number` не выдержит, если когда-нибудь биллим сильно дорогих агентов). Парсить через `BigInt`.
* `cost_rub` — то же значение, делённое на 100, для удобства отображения.
* Время в UTC. Если нужно «сутки по Москве» — пришлите явные `from`/`to`, конвертированные на стороне клиента.
## HTTP-коды
| Код | Когда |
| ----- | --------------------------------------------------------------------------- |
| `200` | Успешный ответ. |
| `400` | Конфликт `period` + `from`/`to`, диапазон > 365 дней, неверный формат даты. |
| `401` | Ключ отсутствует / отозван / невалиден. |
| `429` | Превышен дневной лимит на ключе. |
## Что дальше
* [Биллинг](/docs/concepts/billing) — как формируется итоговая стоимость и из чего складываются токены.
* [Цены](/docs/concepts/pricing) — формула расчёта.
* В UI: страница [/usage](/usage) в дашборде с фильтрами по модели, ключу и статусу + CSV-экспорт.
---
# Генерация видео (API) (/docs/api/videos)
> Асинхронный /v1/videos — создание, опрос статуса, скачивание mp4. Оплата в рублях по факту, хранение результата 72 часа.
import { CodeTabs } from '@/components/docs/code-tabs';
import { EndpointBadge } from '@/components/docs/endpoint-badge';
`/v1/videos` — асинхронный API генерации видео. Вы создаёте задачу, опрашиваете её статус и, когда видео готово, скачиваете mp4. Тот же Bearer-ключ `sk-gw-...`, что и для остального API. Цена — в рублях, списывается по факту.
Идентификатор задачи (`id`), `polling_url` и `unsigned_urls` указывают **только на эндпоинты Hubris** (`https://api.hubris.pw`).
## Scope ключа
* `POST /v1/videos` требует scope **`videos:write`** (денежно-мутирующий — создаёт задачу и резервирует средства).
* `GET /v1/videos/{id}` и `GET /v1/videos/{id}/content` требуют **`videos:read`**.
* `GET /v1/videos/models` требует **`models:read`** — тот же scope, что и обычный каталог [`GET /v1/models`](/docs/api/models), потому что это тоже просто листинг каталога.
Полноправные ключи `sk-gw-` (без ограничения scope) покрывают все три. У scoped-ключей и OAuth-токенов проверьте, что нужный scope выдан на [/keys](/keys).
## Жизненный цикл
```
POST /v1/videos → 202 { id, status: "pending", polling_url }
│
▼ (опрос раз в несколько секунд)
GET /v1/videos/{id} → 200 { status: "pending" | "in_progress" | "completed" | "failed" }
│
▼ (когда status = "completed")
GET /v1/videos/{id}/content?index=0 → video/mp4 (сырые байты)
```
Статусы наружу: `pending`, `in_progress`, `completed`, `failed`. Промежуточные внутренние состояния схлопываются в `in_progress`.
## Создание — `POST /v1/videos`
Обязательны `model` (из каталога видео-моделей, см. [`GET /v1/videos/models`](#каталог-видео-моделей--get-v1videosmodels)) и `prompt`. Ответ — **HTTP 202**.
```json
{
"id": "b0f2c1a4-3e9d-4b7a-9c2e-8f1a6d5c4b3a",
"status": "pending",
"polling_url": "https://api.hubris.pw/v1/videos/b0f2c1a4-3e9d-4b7a-9c2e-8f1a6d5c4b3a",
"created": 1752192000
}
```
Опциональные поля: `size` (`"1280x720"`, шорткат к `resolution`+`aspect_ratio`; при явном `resolution` и/или `aspect_ratio` они перекрывают `size`), `seed` (int, только если модель поддерживает детерминизм), `frame_images[]` — до двух опорных кадров, чтобы оживить картинку первым и/или последним кадром:
```json
{
"frame_images": [
{
"type": "image_url",
"image_url": { "url": "https://example.com/frame.png" },
"frame_type": "first_frame"
}
]
}
```
`image_url.url` принимает `https://`-ссылку (без логина/пароля в URL) или `data:image/png;base64,...` / `data:image/jpeg;base64,...` (до 10 МБ в base64). Поля `callback_url` и `input_references` в v1 не поддерживаются — запрос с ними вернёт `400 invalid_request` (не молчаливый дроп).
### Идемпотентность
Передайте заголовок `Idempotency-Key: <ваш-ключ>` — повторный `POST` с тем же ключом от того же аккаунта вернёт **уже созданную** задачу, не создавая вторую и не резервируя средства повторно. Используйте при сетевых ретраях создания.
## Опрос статуса — `GET /v1/videos/{id}`
```bash
curl -s https://api.hubris.pw/v1/videos/b0f2c1a4-3e9d-4b7a-9c2e-8f1a6d5c4b3a \
-H "Authorization: Bearer $HUBRIS_API_KEY"
```
```json
{
"id": "b0f2c1a4-3e9d-4b7a-9c2e-8f1a6d5c4b3a",
"status": "completed",
"created": 1752192000,
"unsigned_urls": [
"https://api.hubris.pw/v1/videos/b0f2c1a4-3e9d-4b7a-9c2e-8f1a6d5c4b3a/content?index=0"
],
"usage": { "cost": 4200 }
}
```
* **`usage.cost`** — стоимость **в копейках** (integer), появляется только при `status: "completed"`. `4200` = 42,00 ₽. Это итоговая сумма, списанная с баланса — не оценка.
* **`unsigned_urls`** появляются только при `status: "completed"` и, несмотря на имя (унаследовано от формата апстрима), **требуют тот же Bearer-ключ** — это эндпоинты Hubris, не публичные подписанные ссылки.
* При `status: "failed"` приходит поле `error` с нейтральным текстом.
### Пример полного цикла (bash)
```bash
ID=$(curl -s https://api.hubris.pw/v1/videos \
-H "Authorization: Bearer $HUBRIS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"kwaivgi/kling-v3.0-pro","prompt":"Закат над океаном, дрон"}' \
| python3 -c 'import sys,json;print(json.load(sys.stdin)["id"])')
while true; do
STATUS=$(curl -s "https://api.hubris.pw/v1/videos/$ID" \
-H "Authorization: Bearer $HUBRIS_API_KEY" \
| python3 -c 'import sys,json;print(json.load(sys.stdin)["status"])')
echo "status=$STATUS"
[ "$STATUS" = "completed" ] && break
[ "$STATUS" = "failed" ] && { echo "generation failed"; exit 1; }
sleep 5
done
curl -s "https://api.hubris.pw/v1/videos/$ID/content?index=0" \
-H "Authorization: Bearer $HUBRIS_API_KEY" -o result.mp4
```
Опрашивайте не чаще раза в несколько секунд — частые повторы одного и того же `GET` не ускорят генерацию.
## Скачивание — `GET /v1/videos/{id}/content`
Отдаёт сырые байты `video/mp4`. **Требует Bearer-ключ** и доступен только владельцу задачи. В v1 доступен единственный клип — `index=0` (параметр можно не передавать, по умолчанию тот же); `index` больше нуля вернёт `400 index_out_of_range`. Запрос до готовности видео или после истечения хранения — `404 video_not_ready`.
## Хранение результата — 72 часа
Готовый ролик хранится у Hubris **72 часа** с момента создания задачи, затем удаляется без возможности восстановления. Скачайте и сохраните файл у себя в течение этого окна. Запрос `/content` после удаления вернёт `404 video_not_ready` — так же, как если бы видео ещё не было готово (Hubris не палит разницу между «протухло» и «не готово»).
## Каталог видео-моделей — `GET /v1/videos/models`
```bash
curl -s https://api.hubris.pw/v1/videos/models \
-H "Authorization: Bearer $HUBRIS_API_KEY"
```
Возвращает `{ "object": "list", "data": [...] }` — активные видео-модели с поддерживаемыми разрешениями, соотношениями сторон, длительностями, доступностью звука/seed/опорных кадров и **ценами в рублях** за единицу (секунда или видео). Видео-модели скрыты из обычного [`GET /v1/models`](/docs/api/models) — получить их там тоже можно, но отдельным запросом: `GET /v1/models?output_modalities=video`.
## Коды ошибок
| HTTP | code | type | Когда |
| --------- | --------------------------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| 400 | `invalid_request` | `invalid_request_error` | Кривой запрос, неподдерживаемая ось (длительность/разрешение/пропорции не из каталога модели), `callback_url`/`input_references` |
| 400 | `index_out_of_range` | `invalid_request_error` | `index` в `/content` вне диапазона (v1 — только `index=0`) |
| 402 | `insufficient_balance` | `invalid_request_error` | Недостаточно средств для резерва под задачу |
| 404 | `model_not_found` | `invalid_request_error` | Модель с таким `id` не видео-модель или неактивна |
| 404 | `not_found` | `invalid_request_error` | Задачи с таким `id` не существует или она принадлежит другому аккаунту |
| 404 | `video_not_ready` | `invalid_request_error` | `/content` до `completed` или после 72-часового TTL |
| 413 | `request_too_large` | `invalid_request_error` | Тело запроса (обычно — `frame_images` в base64) превышает лимит |
| 429 | `too_many_active_jobs` | `rate_limit_error` | Достигнут лимит одновременных видео-задач аккаунта |
| 502 / 504 | `upstream_error` | `api_error` | Провайдер модели не принял задачу (502) или не ответил вовремя (504) |
| 503 | `exchange_rate_unavailable` | `api_error` | Курс ЦБ РФ временно недоступен — нельзя посчитать резерв под задачу |
Полный формат ошибок и стратегия retry — на странице [Ошибки](/docs/concepts/errors).
## Приватность и хранение данных
Видео-генерация выполняется на инфраструктуре внешних провайдеров моделей и **не покрывается режимом Zero Data Retention**: провайдеры временно хранят входные данные (промпт, кадры `frame_images`) и результат на своей стороне на время генерации. У Hubris готовый ролик хранится 72 часа, затем удаляется.
**Privacy Mode на видео не распространяется.** Автоматическое маскирование персональных данных ([Privacy Mode](/docs/features/privacy)) работает только для текстовых запросов Chat Completions; промпты и кадры видео уходят провайдеру как есть — не передавайте туда персональные данные третьих лиц.
**Трансграничная передача (152-ФЗ).** Если вы вызываете `/v1/videos` от имени своих пользователей, вы отвечаете за наличие их согласия на обработку и трансграничную передачу входов и выходов (промпты, кадры, результат) за пределы РФ, а также на 72-часовое хранение результата у Hubris. Условия использования API фиксируют это в оферте.
Hubris **не логирует** содержимое ваших запросов: `prompt`, байты видео и `frame_images` не попадают в логи — только метаданные (модель, стоимость, статус, латентность).
## Что дальше
* [Ошибки](/docs/concepts/errors) — таблица всех кодов и retry-стратегий.
* [Биллинг](/docs/concepts/billing) — как считается `usage.cost`.
* [Privacy Mode](/docs/features/privacy) — что покрывает маскирование, а что нет.
* [Студия видео](/videos) в личном кабинете — тот же API под UI, без curl.
---
# Биллинг (/docs/concepts/billing)
> Баланс, минимальный остаток, пополнение через СБП, поведение при нехватке средств.
Hubris работает по предоплатной модели: вы пополняете баланс через СБП, и каждый успешный запрос списывает с него стоимость. Подписок и тарифов нет.
## Баланс
Текущий баланс отображается на странице [/billing](/billing) в режиме реального времени. Там же — история пополнений и история расходов.
Также баланс виден в шапке дашборда (рядом с меню профиля).
## Пополнение через СБП
1. На странице [/billing](/billing) нажмите «Пополнить».
2. Введите сумму (минимум 300 ₽, максимум 100 000 ₽ за одну операцию).
3. Сканируйте QR-код в банковском приложении или нажмите «Открыть в приложении» (если на телефоне).
4. Подтвердите перевод в банке.
5. Зачисление происходит автоматически в течение 10–30 секунд.
Если зачисление не пришло за 5 минут — напишите на [support@hubris.pw](mailto:support@hubris.pw) и приложите ID операции СБП (его видно в банковском приложении).
## Минимальный баланс
Hubris требует минимум **100 копеек (1 ₽)** на балансе для обработки запроса. При меньшем балансе любой запрос вернёт **402 Payment Required** ДО обращения к модели:
```json
{
"error": {
"message": "Insufficient balance",
"type": "invalid_request_error",
"code": "insufficient_balance"
}
}
```
Это не значит, что 1 ₽ хватит на весь день. Это значит, что Hubris не запустит запрос, если баланса заведомо мало. Реальная стоимость запроса может быть от 1 копейки до нескольких рублей в зависимости от модели и размера сообщений.
## Уход в минус
Стоимость запроса известна **только после** ответа модели — мы не можем угадать заранее, сколько токенов сгенерирует модель. Поэтому возможна ситуация: на балансе было 50 ₽, запрос стоил 60 ₽, баланс ушёл в **−10 ₽**.
Это допустимо, но только на одну операцию. Следующий запрос с отрицательным балансом вернёт `402 Insufficient balance` — пополнение разблокирует следующие запросы.
## История
В разделе [/billing](/billing) видно:
* Каждое пополнение (дата, сумма, источник СБП).
* Каждый расход — со ссылкой на запрос в `/usage`.
* Корректировки от поддержки (если вы запрашивали возврат).
## Возврат средств
Hubris работает по pay-as-you-go — фактически использованные токены не возвращаются. Однако если вас списали ошибочно (некорректный billing у провайдера, баг на нашей стороне) — напишите на [support@hubris.pw](mailto:support@hubris.pw) с request\_id из ответа или временем запроса. Мы вернём деньги.
Возврат неиспользованного остатка с баланса — по запросу через support, обычно в течение 3 рабочих дней через тот же СБП-перевод.
## Лимиты
В стандартном тарифе у пользовательских ключей **нет искусственных лимитов** запросов в секунду или в день. Расход ограничен только балансом.
Служебным ключам (CI, скрипты) можно задавать дневной лимит — это страховка от утечки ключа. Подробнее — в разделе [Rate limits](/docs/concepts/rate-limits).
## Что дальше
* [Цены](/docs/concepts/pricing) — формула расчёта стоимости запроса.
* [Аутентификация](/docs/authentication) — как создать и отозвать ключ.
* [/billing](/billing) — текущий баланс и история.
---
# Дотратить баланс OpenRouter (BYOK) (/docs/concepts/byok)
> Подключите свой ключ OpenRouter — Hubris потратит его баланс вместо рублёвого. Комиссия — 1 копейка за запрос.
import { CodeTabs } from '@/components/docs/code-tabs';
Если у вас уже есть аккаунт и оплаченный баланс в OpenRouter — этот раздел для вас.
## Что это
OpenRouter не принимает запросы напрямую из России. Если у вас есть аккаунт OpenRouter с оплаченным балансом, он может попросту зависнуть — потратить его штатным способом из РФ не получится.
**«Дотратить баланс OpenRouter»** решает эту проблему: вы подключаете свой ключ OpenRouter к Hubris, и дальше расходуете баланс через нашу инфраструктуру. Комиссия Hubris — **1 копейка за успешный запрос**, сама генерация оплачивается напрямую с вашего баланса OpenRouter — по тем же ценам, что там указаны.
Такой режим иногда называют BYOK (bring your own key — «принесите свой ключ»).
## Как это работает
1. Вы подключаете свой ключ OpenRouter (`sk-or-v1-...`) на странице [«BYOK»](/byok) в личном кабинете.
2. Hubris автоматически создаёт связанный ключ Hubris (`sk-gw-...`) — пара «один ключ OpenRouter → один ключ Hubris».
3. Запросы этим ключом Hubris форвардятся на OpenRouter по вашему ключу и списывают токены с вашего баланса OpenRouter.
4. Ваши обычные ключи Hubris при этом не меняются и продолжают работать в рублях с баланса Hubris, как и раньше — подключение BYOK-ключа ничего в них не трогает.
## Подключение
1. Откройте страницу [«BYOK»](/byok) в личном кабинете.
2. Вставьте ключ OpenRouter. Ключ создаётся в кабинете OpenRouter → [Keys](https://openrouter.ai/settings/keys) — скопируйте его оттуда.
3. Нажмите «Подключить и создать ключ Hubris». В открывшемся окне появится готовый ключ Hubris (`sk-gw-...`) — он показывается **один раз**, сохраните его сразу.
4. В вашей интеграции замените прежний ключ на этот. Адрес `api.hubris.pw` менять не нужно — меняется только значение заголовка `Authorization`:
Больше в коде ничего не меняется — тот же `base_url`, тот же формат запроса, тот же формат ответа.
## Несколько ключей и проекты
Можно подключить сразу несколько ключей OpenRouter — например, если у вас несколько аккаунтов OpenRouter или вы хотите развести проекты по разным балансам. Каждому ключу OpenRouter соответствует свой отдельный ключ Hubris, поэтому расход по проектам не смешивается.
## Обновление ключа (rotate)
Ключ Hubris показывается один раз — если вы его не сохранили, не нужно подключать ключ OpenRouter заново. На странице [«BYOK»](/byok) у нужной пары нажмите **«Обновить ключ Hubris»**: выдастся новый ключ Hubris, старый сразу перестанет работать. Привязанный ключ OpenRouter при этом остаётся прежним — вписывать его снова не требуется.
## Где работает
BYOK-ключ работает на тех же эндпоинтах, что и обычный ключ Hubris, кроме генерации видео:
| Эндпоинт | Путь | Комментарий |
| --------------------- | ------------------------ | ---------------------- |
| Чат | `/v1/chat/completions` | |
| Генерация изображений | `/v1/images/generations` | |
| Эмбеддинги | `/v1/embeddings` | |
| Responses API | `/v1/responses` | |
| Messages API | `/v1/messages` | Anthropic-совместимый |
| Видео | `/v1/videos` | пока не поддерживается |
Видео (`/v1/videos`) для BYOK-ключей добавим позже — пока запрос на этот эндпоинт с BYOK-ключом вернёт ошибку.
## Стоимость и логи
За каждый успешный запрос BYOK-ключом с вашего баланса Hubris списывается фиксированная **1 копейка** — это комиссия за форвардинг запроса. Стоимость самих токенов при этом не входит в эту копейку и не проходит через баланс Hubris вообще — она списывается напрямую с вашего баланса OpenRouter по его собственным ценам.
В разделе [/logs](/logs) такие запросы помечены значком OpenRouter и подписью «через ваш OpenRouter» — их легко отличить от запросов, оплаченных с обычного баланса Hubris.
## Когда баланс OpenRouter закончится
Как только баланс на вашем аккаунте OpenRouter будет исчерпан, запросы BYOK-ключом перестанут проходить — вернётся ошибка:
```json
{
"error": {
"message": "Баланс вашего аккаунта OpenRouter исчерпан. Пополните его на openrouter.ai или переключитесь на оплату в рублях через Hubris.",
"type": "insufficient_quota",
"code": "byok_insufficient_credits"
}
}
```
В этом случае можно либо пополнить баланс на стороне OpenRouter, либо просто продолжить работу на обычном балансе Hubris — переключитесь на любой из ваших ключей Hubris без пометки BYOK. Пополнение баланса Hubris — в рублях через СБП, подробнее в разделе [Биллинг](/docs/concepts/billing).
## Безопасность
Ключ OpenRouter хранится на сервере Hubris в зашифрованном виде. В интерфейсе и в логах виден только его префикс — полный ключ нигде, кроме момента подключения, не отображается и не логируется.
> Использование ключа OpenRouter через посредника может подпадать под условия использования самого OpenRouter — оцените это самостоятельно перед подключением.
## Что дальше
* [«BYOK»](/byok) — страница подключения ключей в личном кабинете.
* [Биллинг](/docs/concepts/billing) — пополнение обычного баланса Hubris через СБП.
* [Ошибки](/docs/concepts/errors) — формат ошибок и таблица кодов.
---
# Ошибки (/docs/concepts/errors)
> Формат ответа об ошибке, таблица всех кодов, что делать в каждом случае.
Все ошибки Hubris API возвращаются в OpenAI-совместимом формате. Это значит — стандартные SDK обрабатывают их без специальной адаптации.
## Формат
```json
{
"error": {
"message": "Описание ошибки на английском",
"type": "<категория ошибки>",
"code": "<машинно-читаемый код>"
}
}
```
* **`message`** — человекочитаемое сообщение (на английском, для совместимости с OpenAI-инструментами).
* **`type`** — категория. Возможные значения: `invalid_request_error`, `rate_limit_error`, `api_error`.
* **`code`** — машинный код для программной обработки. Список ниже.
## Таблица кодов
| HTTP | code | Что значит | Что делать |
| ---- | --------------------------- | ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| 400 | `invalid_request` | Тело запроса не соответствует схеме (пропущенное поле, неверный тип, выход за допустимые границы) | Проверить JSON по [Быстрому старту](/docs/quickstart) |
| 401 | `invalid_api_key` | Ключ отсутствует, отозван, или невалиден | Проверить заголовок `Authorization`, создать новый ключ на [/keys](/keys) |
| 402 | `insufficient_balance` | На балансе меньше минимального остатка (100 копеек) | Пополнить через [/billing](/billing) |
| 404 | `model_not_found` | Модель с таким `id` не существует или неактивна | Проверить написание, посмотреть [/models](/models) |
| 429 | `daily_limit_exceeded` | Превышен дневной лимит на конкретном ключе (только для служебных) | Дождаться сброса лимита или использовать ключ без лимита |
| 429 | `too_many_active_jobs` | Достигнут лимит одновременных видео-задач аккаунта — только `/v1/videos` | Опрашивать `GET /v1/videos/{id}`, дождаться завершения текущих задач, затем повторить POST |
| 404 | `video_not_ready` | Запрошен `/content` видео-задачи, которая ещё не `completed` (или истёк 72-часовой TTL) — только `/v1/videos` | Опрашивать `GET /v1/videos/{id}` до `status: "completed"`, затем качать `/content` |
| 400 | `index_out_of_range` | Параметр `index` в `/content` вне диапазона (v1 отдаёт только `index=0`) — только `/v1/videos` | Использовать `index=0` |
| 502 | `upstream_error` | Провайдер модели вернул ошибку (5xx, контент-фильтр, internal error) | Повторить через несколько секунд; если повторяется — попробовать другую модель |
| 503 | `exchange_rate_unavailable` | Курс ЦБ РФ временно недоступен — каталог не отдаёт цены | Повторить через минуту |
| 504 | `upstream_timeout` | Провайдер модели не ответил за 120 секунд | Повторить; если повторяется — модель перегружена, попробовать другую |
## Примеры
### 401 (нет ключа)
Запрос: `curl https://api.hubris.pw/v1/models`
```json
{
"error": {
"message": "Missing API key",
"type": "invalid_request_error",
"code": "invalid_api_key"
}
}
```
### 402 (баланс ноль)
```json
{
"error": {
"message": "Insufficient balance",
"type": "invalid_request_error",
"code": "insufficient_balance"
}
}
```
### 404 (модели не существует)
Запрос с несуществующим идентификатором модели:
```json
{
"error": {
"message": "Model not found: fake-provider/nonexistent",
"type": "invalid_request_error",
"code": "model_not_found"
}
}
```
### 502 (апстрим лёг)
```json
{
"error": {
"message": "Upstream returned an error",
"type": "api_error",
"code": "upstream_error"
}
}
```
## Стратегия обработки
В вашем коде имеет смысл:
1. **На 401, 402, 404** — не повторять. Это ошибки конфигурации, retry не поможет.
2. **На 502, 503, 504** — повторить с экспоненциальным backoff (1с, 2с, 4с, до 3 попыток). Это транзиентные сбои.
3. **На 429** — посмотреть на ключ: если у него выставлен дневной лимит, дождаться сброса (окно скользящее, считается от каждого списания); если лимит не выставлен — проверьте [/keys](/keys).
4. **На 400** — почти всегда баг в коде. Логируйте полный запрос (без ключа) для разбора.
## Стриминг
При `stream: true` и ошибке ДО начала стрима возвращается стандартный JSON-ответ выше.
Если ошибка случается **во время** стрима (модель упала на середине) — стрим обрывается на серверной стороне. Клиент получает событие `[DONE]` с пустым `delta` и `finish_reason: "stop"`. Точное поведение зависит от провайдера. Hubris записывает в логи ваш конкретный сценарий, чтобы вы могли разобраться через [/usage](/usage).
## Что дальше
* [Быстрый старт](/docs/quickstart) — рабочий пример без ошибок.
* [Аутентификация](/docs/authentication) — про 401.
* [Биллинг](/docs/concepts/billing) — про 402.
---
# Бесплатные модели (/docs/concepts/free-models)
> Модели с нулевой ценой во всех единицах тарификации. Как их найти по флагу is_free и какой действует лимит запросов.
В каталоге Hubris есть модели с нулевой ценой: open-source LLM и embedding-модели. Они отмечены значком «Бесплатно» в [каталоге моделей](/models) — актуальный список открывается фильтром [«бесплатные»](/models?free=true), состав со временем меняется.
Бесплатные модели — это:
* **0 ₽ за 1М токенов** — ничего не списывается с баланса.
* **Доступны без депозита** — даже с балансом 0 ₽ запрос проходит.
* **Лимит 100 запросов в сутки** на пользователя — скользящее окно 24 ч.
Идеально подходят для:
* Знакомства с API без оплаты.
* Тестов и прототипов в проектах с маленьким бюджетом.
* Embedding-задач (BGE, MiniLM, GTE — отличные модели для русского/английского текста).
## Как использовать
Просто передайте ID бесплатной модели — никаких особых заголовков или флагов:
```bash
curl -s https://api.hubris.pw/v1/chat/completions \
-H "Authorization: Bearer sk-gw-..." \
-H "Content-Type: application/json" \
-d '{
"model": "meta-llama/llama-3.2-3b-instruct:free",
"messages": [{"role": "user", "content": "Привет"}]
}'
```
Список бесплатных моделей в каталоге доступен через API:
```bash
curl -s 'https://api.hubris.pw/v1/models' \
-H "Authorization: Bearer sk-gw-..." \
| jq '.data[] | select(.pricing.is_free) | .id'
```
## Что считается «бесплатной» моделью
Модель бесплатна, когда **нулевые все единицы тарификации** — за токены, за единицу (изображение, мегапиксель, секунда) и дополнительные компоненты — **и выход не медийный**. Итог этой проверки приходит готовым флагом `pricing.is_free`:
```json
{
"id": "openai/gpt-oss-20b:free",
"pricing": {
"input_rub_per_million": 0,
"output_rub_per_million": 0,
"currency": "RUB",
"is_free": true
}
}
```
**Не определяйте бесплатность по нулевой цене за токены.** У моделей с оплатой в других единицах — генерация изображений, видео, аудио — цена за токены нулевая, а запрос платный: стоимость считается за картинку или за секунду. Такие модели приходят с `is_free: false`, даже если оба поля `*_rub_per_million` равны нулю.
Суффикс `:free` в идентификаторе — не признак: Hubris смотрит на прайс, а не на имя. Если модель станет платной, она автоматически перестанет считаться бесплатной.
## Лимит и его правила
**100 запросов на пользователя в сутки** (скользящее окно 24 ч). Считаются только запросы к бесплатным моделям — платные не учитываются. При превышении возвращается **`429 daily_limit_exceeded`**:
```json
{
"error": {
"message": "Free-tier daily limit reached: 100 requests per 24h. Upgrade by using a paid model or wait for the rolling window to reset.",
"type": "rate_limit_error",
"code": "daily_limit_exceeded"
}
}
```
Когда лимит сбросится — зависит от того, когда был сделан первый из 100 запросов: окно скользит. Через 24 часа после первого запроса один слот освобождается; и так далее.
Лимит **общий для всех бесплатных моделей** — нельзя «накопить» по 100 на каждой модели. Это сделано чтобы:
1. Защитить наш общий ключ к провайдеру от 429 от него самого.
2. Не дать ботам жечь бесплатный пул и блокировать его для реальных пользователей.
Лимит сейчас в Hubris не настраивается на стороне пользователя. Если вам нужен больше — переходите на платные модели.
**Связь с лимитом ключа.** Поле «Дневной лимит, ₽» на странице [API-ключи](/keys) считает только сумму `cost_kopecks` за 24 ч. Бесплатные модели стоят 0 ₽, поэтому в этот лимит не входят и не «съедают» его. Это два независимых счётчика.
## Лимиты на стороне провайдера
Помимо нашего лимита 100 запросов в сутки, **у самого провайдера модели есть свои лимиты на бесплатный пул**, которые мы не контролируем и не публикуем:
* лимиты RPM/RPD выставляет провайдер модели, не Hubris;
* конкретные значения провайдер не публикует — мы их тоже не знаем заранее;
* бесплатный пул общий для всех клиентов провайдера, поэтому в часы пиковой нагрузки он быстро переполняется;
* в этот момент Hubris транслирует от провайдера **HTTP 429** с сообщением вида:
```json
{
"error": {
"message": ":free is temporarily rate-limited upstream. Please retry shortly...",
"type": "invalid_request_error",
"code": "invalid_request"
}
}
```
Это не сбой Hubris и не превышение вашего собственного дневного лимита — провайдер просто временно отказывает всему бесплатному трафику.
**Что делать при 429 от провайдера:**
1. **Повторить запрос с экспоненциальной задержкой** (1 с → 2 с → 4 с → 8 с, с разумным потолком). Часто бесплатный пул освобождается за несколько секунд.
2. **Указать платный fallback** через [Model Fallback](/docs/features/model-fallbacks) — в массиве `models` поставить платную версию той же модели последним элементом:
```bash
curl -s https://api.hubris.pw/v1/chat/completions \
-H "Authorization: Bearer sk-gw-..." \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek/deepseek-v4-flash:free",
"models": [
"deepseek/deepseek-v4-flash:free",
"deepseek/deepseek-v4-flash"
],
"messages": [{"role": "user", "content": "..."}]
}'
```
Hubris автоматически переключится на следующий элемент при 429 от бесплатного. Тарифицируется тот, кто реально ответил.
3. **Для боевой нагрузки** — использовать платную версию модели изначально. У платных нет общего бесплатного пула и нет таких всплесков 429.
## Чем бесплатные отличаются от платных
| Параметр | Бесплатные | Платные |
| ------------------ | ---------------------------------------------- | ---------------------------------------------- |
| Цена | 0 ₽ | По каталогу |
| Минимальный баланс | Не требуется | 1 ₽ |
| Дневной лимит | 100 запросов / 24 ч | Не применяется (или ваш custom-лимит на ключе) |
| Качество | Мелкие/средние LLM | Любые, включая флагманы |
| Латентность | Часто выше — провайдеры приоритезируют платных | Стандартная |
| SLA | Best-effort | Соответствует провайдеру |
**Внимание:** бесплатные модели на стороне провайдера часто ограничены гораздо жёстче платных. Может прийти **429 от провайдера**, даже если ваш собственный дневной лимит не исчерпан. Подробнее — в разделе [Лимиты на стороне провайдера](#лимиты-на-стороне-провайдера) выше.
## Сочетание с Model Fallback
Хороший паттерн — дешёвая основа, платная подстраховка:
```bash
curl -s https://api.hubris.pw/v1/chat/completions \
-H "Authorization: Bearer sk-gw-..." \
-H "Content-Type: application/json" \
-d '{
"model": "meta-llama/llama-3.2-3b-instruct:free",
"models": [
"meta-llama/llama-3.2-3b-instruct:free",
"anthropic/claude-haiku-4.5"
],
"messages": [{"role": "user", "content": "..."}]
}'
```
Если бесплатная Llama упадёт от провайдерского 429 — Hubris автоматически переключится на платный Haiku. Биллится тот, кто реально ответил — то есть в большинстве случаев это будет 0 ₽, и только в редких сбоях — копейки за Haiku.
## Что дальше
* [Модели](/docs/concepts/models) — обзор каталога.
* [Model Fallback](/docs/features/model-fallbacks) — про автоматическое переключение.
* [Цены](/docs/concepts/pricing) — формула расчёта стоимости платных моделей.
---
# Список моделей и выбор (/docs/concepts/models)
> Каталог моделей Hubris, формат GET /v1/models, как выбрать модель под задачу.
import { EndpointBadge } from '@/components/docs/endpoint-badge';
import { ModelLink } from '@/components/docs/model-link';
Hubris предоставляет доступ к моделям ведущих провайдеров через единый API. Все модели отвечают в OpenAI-совместимом формате — код не нужно адаптировать под каждого провайдера.
## Каталог
Полный список — на странице [/models](/models). Там же можно посмотреть актуальные цены в ₽, размер контекста и поддерживаемые модальности (текст, изображения, аудио).
Программно список доступен через API:
```bash
curl -H "Authorization: Bearer sk-gw-..." https://api.hubris.pw/v1/models
```
Ответ — стандартный OpenAI-список с расширениями Hubris:
```json
{
"object": "list",
"data": [
{
"id": "anthropic/claude-haiku-4.5",
"object": "model",
"created": 1714000000,
"owned_by": "anthropic",
"display_name": "Claude Haiku 4.5",
"description": "Быстрая и дешёвая модель Anthropic для большинства задач.",
"context_window": 200000,
"modalities": ["text", "image"],
"pricing": {
"input_rub_per_million": 30.45,
"output_rub_per_million": 152.25,
"currency": "RUB"
}
}
]
}
```
## Формат идентификатора
Все модели используют формат `provider/model`:
* — Anthropic, модель Claude Haiku 4.5.
* — OpenAI, модель GPT-4o mini.
* — Google, модель Gemini 2.5 Flash.
В каталоге доступны модели от Anthropic, OpenAI, Google и других ведущих провайдеров. Все обращения идут через единый эндпоинт `https://api.hubris.pw/v1/chat/completions` — без необходимости подключать SDK каждого провайдера.
## Как выбрать модель
Краткие ориентиры:
* **Для простых задач** (классификация, извлечение, короткие ответы) — самые дешёвые «mini» / «haiku» модели вроде или . Стоят копейки за запрос.
* **Для сложных рассуждений и кода** — флагманские модели (Opus, GPT-4o, Pro). Дороже, но качество выше.
* **Для большого контекста** (длинные документы, кодбазы) — модели с `context_window` от 200K. У ряда моделей Gemini есть варианты до 2M токенов.
* **Для мультимодальных задач** (анализ изображений) — модели с `"image"` в `modalities`.
В каталоге есть фильтры по этим параметрам. Если не уверены — начните с , она работает быстро и дёшево для большинства задач.
## Какие модели в каталоге
В `data` попадают только активные модели с явно заданной фиксированной ценой. Модели с переменной/динамической ценой и устаревшие — фильтруются автоматически.
Каталог обновляется примерно раз в сутки — новые модели появляются в течение дня после релиза провайдером.
## Что дальше
* [Цены](/docs/concepts/pricing) — как считаются деньги за запрос.
* [Быстрый старт](/docs/quickstart) — отправьте первый запрос к выбранной модели.
* [/models](/models) — открыть каталог.
---
# Цены (/docs/concepts/pricing)
> Как считается стоимость запроса в рублях. Таблицы цен — в каталоге моделей.
import { ModelLink } from '@/components/docs/model-link';
Hubris работает по модели **pay-as-you-go**: вы платите только за фактически использованные токены. Никаких подписок, минимальных платежей или скрытых комиссий. Цены — в ₽ за 1 миллион токенов.
## Что такое токен
Токен — это единица обработки текста для модели. Грубо: одно английское слово — это примерно 1.3 токена; русское слово — примерно 2 токена (кириллица токенизируется агрессивнее). Точное число зависит от языка и токенизатора модели. Один абзац обычно — 50–100 токенов.
При расчёте стоимости запроса различаются:
* **prompt\_tokens** (входные) — то, что вы отправили в `messages`.
* **completion\_tokens** (выходные) — то, что модель вернула в `choices[0].message.content`.
Цены за вход и выход разные — выход обычно в 3–5 раз дороже.
## Формула расчёта
```
стоимость_запроса = prompt_tokens × цена_входа + completion_tokens × цена_выхода
```
Где цены — в ₽ за токен (то есть `input_rub_per_million / 1_000_000`). Округление — в большую сторону до копейки, чтобы Hubris не накапливал долг на дробных копейках.
## Цены в каталоге
В каталоге моделей `/models` для каждой модели видно:
* **Вход** — ₽ за 1М входных токенов.
* **Выход** — ₽ за 1М выходных токенов.
Программно те же значения возвращает `GET /v1/models` в поле `pricing`:
```json
{
"id": "anthropic/claude-haiku-4.5",
"pricing": {
"input_rub_per_million": 30.45,
"output_rub_per_million": 152.25,
"currency": "RUB"
}
}
```
## Пример расчёта
Запрос к с 1000 prompt-токенов и 500 completion-токенов при цене 30.45 ₽ / 152.25 ₽ за 1М:
```
стоимость = 1000 × (30.45 / 1_000_000) + 500 × (152.25 / 1_000_000)
= 0.03045 + 0.0761
= 0.10655 ₽
≈ 11 копеек (округление вверх)
```
## Как узнать стоимость конкретного запроса
В ответе любого запроса к `/v1/chat/completions` есть поле `usage`:
```json
{
"usage": {
"prompt_tokens": 1000,
"completion_tokens": 500,
"total_tokens": 1500
}
}
```
В разделе [/usage](/usage) дашборда отображается полный лог запросов с фактической стоимостью каждого. Баланс в реальном времени — на странице [/billing](/billing).
## Стоимость стриминга
При `stream: true` цена считается так же — по `usage` из последнего chunk-а перед `[DONE]`. Hubris автоматически добавляет `stream_options: { include_usage: true }` к стрим-запросам, чтобы вы (и мы) знали окончательное число токенов.
Если соединение разорвалось до окончания стрима — мы всё равно дочитываем ответ от провайдера и списываем деньги (защита от «бесплатных токенов» через abort).
## Что входит в счёт
* Успешные запросы (`200 OK`).
* Запросы со стримингом до получения `usage` (включая прерванные клиентом).
## Что НЕ входит в счёт
* Запросы с ошибкой авторизации (`401`, `403`).
* Запросы с невалидным телом (`400`).
* Запросы к несуществующим моделям (`404`).
* Ошибки апстрима (`502`, `503`, `504`) — модель не отработала, денег не списываем.
* Запросы при недостаточном балансе (`402`) — отвергаются ДО запроса к модели.
## Что дальше
* [Биллинг](/docs/concepts/billing) — пополнение баланса, минимальный остаток.
* [Каталог моделей](/models) — посмотреть все цены.
* [/usage](/usage) — лог ваших запросов с расходами.
---
# Конфиденциальность (/docs/concepts/privacy)
> Что Hubris хранит и не хранит о ваших запросах. Передача данных провайдерам моделей.
Hubris работает по принципу **минимального хранения данных**. Содержимое ваших запросов (`messages`, `tools`, `input`) никогда не записывается на сервере и не используется для обучения моделей.
## Что мы НЕ храним
* Тексты ваших сообщений (`messages[*].content`).
* Системные промпты.
* Описание инструментов (`tools[*].function.parameters`).
* Содержимое ответов от моделей.
* Изображения, аудио, файлы (когда поддержим мультимодальные эндпоинты).
Эти данные проходят через наш сервер исключительно в момент передачи запроса провайдеру и не сохраняются ни в логах, ни в БД.
## Что мы храним
В таблице `usage_logs` для каждого запроса:
* ID пользователя и API-ключа (для биллинга).
* ID модели (для отчётности).
* Число входных и выходных токенов (для биллинга).
* Стоимость в копейках.
* Латентность ответа.
* Статус (успех / ошибка / тайм-аут).
* Текст ошибки (если была), без содержимого запроса.
Этого достаточно для биллинга, аналитики и поддержки. Содержимое разговоров мы не видим — даже наша команда не имеет к нему доступа.
## Передача данных провайдерам моделей
Чтобы модель ответила на ваш запрос, его содержимое физически передаётся провайдеру модели (Anthropic, OpenAI, Google и т. д.). Передача происходит по защищённому соединению (TLS).
Каждый провайдер имеет собственную политику обработки данных:
* Большинство **не используют API-данные для обучения** по умолчанию.
* Многие хранят логи 30 дней для безопасности и контроля злоупотреблений.
* Часть провайдеров предоставляет режим Zero Data Retention за дополнительную плату или на enterprise-тарифе — на момент 2026-05 Hubris это не пробрасывает; следите за обновлениями.
Полный перечень провайдеров и юридическая обвязка — на странице [Политика конфиденциальности](/legal/privacy), раздел 8.
## Логи на стороне Hubris
Сервер Hubris пишет два класса логов:
1. **Метаданные запросов** (выше): хранятся в БД `usage_logs`, доступны вам в [/usage](/usage).
2. **Сервисные логи** (Pino → systemd journal): только метаданные — req\_id, userId, keyId, modelId, токены, статусы. Никакого содержимого. Хранятся 30 дней, доступ есть у нашей DevOps-команды для отладки инцидентов.
## Файлы и обработка изображений
Когда мы добавим поддержку генерации изображений и обработки аудио — содержимое (промпты для генерации картинок, голосовые файлы) будет передаваться провайдерам по тем же правилам. В нашей БД эти файлы оставаться не будут.
## GDPR / 152-ФЗ
Hubris соответствует требованиям 152-ФЗ для обработки персональных данных. Полная политика — на странице [/legal/privacy](/legal/privacy). Кратко:
* Юр. лицо: ИП Романкова А. В. (ИНН и реквизиты — там же).
* Персональные данные собираются только для биллинга (email, при необходимости — банковские реквизиты для возврата).
* Согласие на обработку даётся через акцепт оферты при регистрации.
* Удаление аккаунта по запросу на [support@hubris.pw](mailto:support@hubris.pw) — все данные стираются в течение 30 дней.
## Если у вас высокие требования
Если ваши данные регулируются (медицинские, финансовые, гос. тайна) — напишите на [support@hubris.pw](mailto:support@hubris.pw). Мы обсудим:
* Какие провайдеры подходят под ваше регулирование.
* Возможен ли self-hosted режим (отдельный inference на вашем железе).
* Дополнительные гарантии в DPA.
## Что дальше
* [Политика конфиденциальности](/legal/privacy) — полные юридические условия.
* [Условия использования](/legal/terms) — публичная оферта.
* [Аутентификация](/docs/authentication) — про безопасное хранение ключей.
---
# Rate limits (/docs/concepts/rate-limits)
> Что ограничивает запросы в Hubris — баланс, апстрим, дневные лимиты на служебных ключах.
Hubris **не применяет искусственных rate-limit-ов** на пользовательские API-ключи. Стандартный тариф даёт неограниченное число запросов в секунду — расход ограничен только балансом.
## Что реально ограничивает запросы
### 1. Баланс
Каждый запрос списывает с баланса фактическую стоимость. При недостаточном балансе (\< 100 копеек) запрос отклоняется с `402 insufficient_balance` ДО обращения к модели. Подробнее — на странице [Биллинг](/docs/concepts/billing).
### 2. Лимиты провайдера
Каждая модель имеет собственные ограничения от провайдера: requests-per-minute, tokens-per-minute, concurrent connections. При превышении провайдер возвращает 429, мы транслируем это как `502 upstream_error`.
Что делать: добавить retry с экспоненциальным backoff. Hubris агрегирует трафик многих клиентов через свой сервисный ключ — вероятность упереться в лимит провайдера ниже, чем при работе напрямую.
### 3. Дневной лимит на служебных ключах
Опционально — при создании ключа можно задать `daily_limit_kopecks`. Например, 5000 копеек = 50 ₽ в сутки. Превышение возвращает **429 daily\_limit\_exceeded**:
```json
{
"error": {
"message": "Daily spending limit exceeded for this API key",
"type": "rate_limit_error",
"code": "daily_limit_exceeded"
}
}
```
Это используется для:
* **CI-runner-ов**, чтобы утечка ключа не разорила баланс.
* **Внутренних скриптов** с ограниченным бюджетом.
* **Sandbox-аккаунтов** для разработчиков.
Окно — скользящее (rolling) 24 часа, а не календарный день. Старые расходы выпадают из счётчика по мере того как проходят сутки с момента списания, поэтому после превышения лимит постепенно «отпускает». Расходы за последние 24ч кешируются в Redis с TTL 60 секунд, то есть UI и middleware могут отставать от точного значения на минуту.
Задать или поменять лимит — на странице [API-ключи](/keys): поле «Дневной лимит, ₽» в форме создания и кнопка «изменить» рядом с каждым активным ключом. Очистите поле, чтобы снять лимит.
Генерация изображений (`modalities: ["image"]` / `["image","text"]`) учитывается в счётчике 24-часового расхода ключа на общих основаниях: одна сгенерированная картинка добавляется к расходу по итоговой стоимости (см. [Генерация изображений](/docs/features/image-generation)).
## Стратегия retry
Если получили 429 от Hubris (`daily_limit_exceeded`) или 502 (от провайдера):
```python
import time
def with_retry(fn, max_attempts=3):
delay = 1
for attempt in range(max_attempts):
try:
return fn()
except APIError as e:
if e.code in ("upstream_error", "upstream_timeout"):
if attempt < max_attempts - 1:
time.sleep(delay)
delay *= 2
continue
raise
```
Не делайте retry на:
* `invalid_api_key` (401) — ключ не починится сам.
* `insufficient_balance` (402) — пополните баланс сначала.
* `model_not_found` (404) — название не изменится.
* `invalid_request` (400) — баг в коде.
## Concurrent requests
Параллельные запросы на одном ключе — без ограничений. Если ваш сервис делает 100 запросов одновременно, они все пойдут к моделям параллельно. Узкое горлышко — провайдер модели, не Hubris.
## Что дальше
* [Ошибки](/docs/concepts/errors) — все коды и стратегия retry.
* [Биллинг](/docs/concepts/billing) — про баланс.
* [Аутентификация](/docs/authentication) — про создание ключей.
---
# Кабинеты клиентов (/docs/dashboard/client-accounts)
> Как подрядчику получить доступ к кабинету заказчика, что он может делать, чего не может и как доступ отозвать.
Если вы настраиваете нейросети для заказчиков, раньше мешали две вещи: код из письма на каждый вход и невозможность зайти в кабинет клиента. Теперь клиент может подключить вас к своему кабинету, и вы переключаетесь между кабинетами одной кнопкой в шапке.
Деньги при этом остаются деньгами клиента: каждый кабинет платит со своего баланса.
## Как подключиться
1. Вы отправляете приглашение на почту клиента — [Профиль](/profile?tab=access) → вкладка «Доступы» → «Пригласить».
2. Клиент открывает письмо и одной кнопкой попадает в свой кабинет — регистрироваться заново или искать код не нужно.
3. Клиент подтверждает доступ на той же вкладке «Доступы».
4. У вас в шапке появляется переключатель кабинетов рядом с меню профиля.
Приглашать можно и тех, у кого аккаунта ещё нет: связка привяжется, когда человек зарегистрируется на этот адрес.
Пока вы работаете в чужом кабинете, наверху видна янтарная полоса с его названием — чтобы не перепутать, где вы находитесь.
## Что подрядчик может
* Входить в кабинет клиента и работать в нём как он сам
* Создавать и отзывать API-ключи
* Видеть траты, журнал запросов и историю платежей
* Работать в чате, генерировать картинки и видео — за счёт баланса клиента
## Чего подрядчик не может
* Менять пароль, почту и способы входа
* Пополнять баланс, включать автопополнение и привязывать карты
* Править реквизиты юридического лица и выставлять счета
* Выдавать доступ к кабинету другим
Запреты действуют на стороне сервера, а не только в интерфейсе: даже прямой запрос к API вернёт `403 agent_forbidden`.
## Отзыв доступа
Клиент в любой момент отзывает доступ на вкладке «Доступы» — подтверждения с вашей стороны не требуется.
Вместе с доступом гасятся и ключи, которые подрядчик создал в этом кабинете. Если на них работают интеграции клиента, они перестанут отвечать — ключи придётся выпустить заново. Это защита: иначе выпущенный ключ пережил бы отзыв доступа и продолжал тратить баланс.
Ключи, которые клиент создал сам, отзыв не трогает.
## Чем это отличается от общего пароля
Совместный пароль означает полный доступ, включая деньги и смену почты, и не оставляет следов, кто что сделал. Подключение кабинета — это именованный доступ с границами: клиент видит, кто подключён, любые действия подрядчика попадают в журнал, а отзыв занимает одно нажатие.
## Что дальше
* [Ключи и проекты](/docs/dashboard/keys-and-projects) — разложить ключи клиентов по проектам
* [Вход в аккаунт](/docs/dashboard/sign-in) — пароль вместо кода на почту
---
# Кабинет (/docs/dashboard)
> Что где лежит в личном кабинете Hubris — вход, ключи и проекты, доступ подрядчиков, расход и оплата.
Этот раздел — про интерфейс: куда нажимать и что произойдёт. Если вы ищете параметры запросов и коды ошибок, вам в [API](/docs/api/overview) и [Базовые концепции](/docs/concepts/models).
## Разделы
| Страница | О чём |
| ---------------------------------------------------- | --------------------------------------------------------------------------- |
| [Вход в аккаунт](/docs/dashboard/sign-in) | Код на почту, пароль, вход через Яндекс и VK, что делать при потере доступа |
| [Ключи и проекты](/docs/dashboard/keys-and-projects) | Создание ключей, дневной лимит, группировка по проектам, расход по каждому |
| [Кабинеты клиентов](/docs/dashboard/client-accounts) | Работа в кабинете заказчика: приглашение, границы доступа, отзыв |
## Где что находится
Слева в кабинете — разделы, сгруппированные по смыслу.
* **Дашборд** — баланс, последние траты, подсказки по началу работы.
* **Использование** — графики расхода: по дням, моделям, ключам и проектам.
* **Логи** — журнал запросов с фильтрами и выгрузкой в CSV.
* **Ключи** — API-ключи и проекты.
* **Биллинг** — пополнение, автопополнение, история платежей, документы для юрлиц.
* **Профиль** — почта и пароль, доступы, уведомления, юридические данные.
Баланс всегда виден слева вверху, рядом — кнопка пополнения.
## Быстрые ответы
**Где взять ключ.** [Ключи](/keys) → «Создать ключ». Полное значение показывается один раз — сохраните сразу, в базе остаётся только его хеш.
**Почему списалось больше, чем ожидалось.** Откройте [Логи](/logs) и найдите запрос: там видно модель, токены входа и выхода, попадание в кеш и итоговую стоимость. Подробнее — [Цены](/docs/concepts/pricing).
**Как разделить расходы по клиентам.** Заведите проекты и разложите ключи по ним — [Ключи и проекты](/docs/dashboard/keys-and-projects).
**Как пустить подрядчика в свой кабинет.** Пригласите его на вкладке «Доступы» в профиле — [Кабинеты клиентов](/docs/dashboard/client-accounts).
---
# Ключи и проекты (/docs/dashboard/keys-and-projects)
> Создание API-ключей, дневной лимит, отключение и отзыв, группировка ключей по проектам и расход по каждому.
Ключ — это пропуск к API. Проект — папка для ключей, чтобы считать расход по клиенту или направлению отдельно. Всё живёт на странице [Ключи](/keys).
## Создание ключа
1. Откройте [Ключи](/keys) → «Создать ключ».
2. Дайте понятное имя: по нему вы будете узнавать ключ в журнале и в статистике. Хорошо — «прод-бот», «сайт-виджет»; плохо — «ключ 2».
3. При желании задайте дневной лимит и проект.
4. Скопируйте значение — **оно показывается один раз**. В базе остаётся только хеш, восстановить нельзя.
Потеряли ключ — создайте новый и отзовите старый; это нормальная практика, а не авария.
## Дневной лимит
Лимит ограничивает траты по ключу за скользящие 24 часа. Превышение — ошибка `429 daily_limit_exceeded`, остальные ключи продолжают работать.
Лимит полезен, когда ключ уезжает в чужие руки: подрядчику, в тестовый стенд, в публичное демо. Без лимита один цикл в чужом коде способен съесть весь баланс.
Задать или изменить — на странице ключа, поле «Дневной лимит». Пусто = без лимита.
## Отключение и отзыв
На странице ключа есть два разных действия:
| Действие | Что происходит | Обратимо |
| --------- | ---------------------------------------------------- | ---------------------- |
| Отключить | Ключ отвечает 401, статистика и значение сохраняются | Да, включается обратно |
| Отозвать | Ключ перестаёт работать навсегда | Нет |
Отключение удобно, когда нужно быстро остановить подозрительный трафик и разобраться. Отзыв — когда ключ точно скомпрометирован или больше не нужен.
История запросов в обоих случаях остаётся в журнале: отзыв ключа не стирает прошлые траты.
## Проекты
Проект группирует ключи внутри вашего кабинета. Это удобно, если вы ведёте нескольких клиентов или несколько направлений и хотите видеть, сколько потратил каждый.
Своего баланса у проекта нет: деньги общие, проект делит **учёт**, а не кошелёк.
### Как завести
1. На странице [Ключи](/keys) нажмите «+ Проект».
2. Задайте название и цвет метки.
3. Разложите ключи: на странице ключа поле «Проект», либо выберите проект сразу при создании.
Над списком ключей появится полоса вкладок: «Все ключи», затем ваши проекты и «Без проекта». Выбранная вкладка фильтрует список, а в подзаголовке показывает сводку — сколько ключей и сколько потрачено за 30 дней.
### Расход по проекту
* На странице [Использование](/activity) — карточка «Распределение расхода по проектам».
* В [журнале запросов](/logs) — фильтр «Проект» рядом с фильтром «API-ключ».
Ключ можно переложить в другой проект в любой момент — история его расходов переезжает вместе с ним. Это сделано специально: первая раскладка ключей по клиентам почти всегда получается неточной, и её нужно уметь исправить задним числом.
### Удаление проекта
Удаляется только группировка: ключи возвращаются в «Без проекта» и продолжают работать. Ни одна интеграция от удаления проекта не сломается.
## Что дальше
* [Аутентификация](/docs/authentication) — как передавать ключ в запросах
* [Кабинеты клиентов](/docs/dashboard/client-accounts) — если ключи заводит подрядчик
* [Лимиты частоты](/docs/concepts/rate-limits) — ограничения на стороне API
---
# Вход в аккаунт (/docs/dashboard/sign-in)
> Код на почту, вход по паролю, Яндекс ID и VK ID — и что делать, если доступ потерян.
Аккаунт в Hubris — это почта. Пароль необязателен: по умолчанию вход идёт по одноразовому коду, а пароль можно завести отдельно, если так удобнее.
## Код на почту
Основной способ. Он же регистрация: отдельной формы «создать аккаунт» нет.
1. Откройте [страницу входа](/sign-in) и введите почту.
2. Нажмите «Получить код» — письмо приходит в течение нескольких секунд.
3. Введите шестизначный код.
Код живёт ограниченное время и сгорает после первого использования. Если письмо не пришло — проверьте папку «Спам»; если и там пусто, напишите в [поддержку](/docs/support).
## Пароль
Пароль удобен, когда вы заходите часто или ведёте несколько кабинетов и не хотите каждый раз лезть в почту.
1. Откройте [Профиль](/profile) → карточка «Пароль».
2. Задайте пароль — от 8 символов.
3. Дальше на странице входа переключитесь на «Войти по паролю».
Мы храним не сам пароль, а его хеш (argon2id), поэтому подсмотреть или восстановить исходное значение нельзя — только задать новое.
При смене пароля все остальные сессии завершаются: на других устройствах придётся войти заново. Это защита на случай, если пароль меняют потому, что доступ к аккаунту мог утечь.
Вход по коду продолжает работать и после того, как вы завели пароль — это два способа, а не замена одного другим.
## Яндекс ID и VK ID
На странице входа есть кнопки «Войти с Яндекс ID» и «Войти через VK ID». Если почта в социальном профиле совпадает с почтой существующего аккаунта, вход произойдёт в него же — второй аккаунт не создастся.
## Если доступ потерян
* **Не приходит письмо с кодом.** Проверьте спам и правильность адреса. Почтовые провайдеры иногда задерживают письма на несколько минут.
* **Забыт пароль.** Войдите по коду на почту и задайте новый в профиле — отдельной формы восстановления нет, она и не нужна.
* **Нет доступа к почте.** Напишите в [поддержку](/docs/support) с адреса, к которому доступ есть, и опишите ситуацию: мы проверим принадлежность аккаунта.
* **Открывается пустой кабинет вместо формы входа.** Обновите страницу: сеанс закончился, и приложение само вернёт вас на форму.
## Что дальше
* [Ключи и проекты](/docs/dashboard/keys-and-projects) — создать первый ключ
* [Кабинеты клиентов](/docs/dashboard/client-accounts) — пустить подрядчика в свой кабинет
* [Аутентификация в API](/docs/authentication) — как ключ передаётся в запросах
---
# Codex CLI (/docs/frameworks/codex-cli)
> Подключить агентский CLI от OpenAI к Hubris через Responses API.
[Codex CLI](https://github.com/openai/codex) — это локальный агент для написания и правки кода от OpenAI. По умолчанию он ходит в OpenAI Responses API (`/v1/responses`), а не в Chat Completions, и его можно перенаправить на Hubris через файл конфигурации.
## Установка
```bash
npm install -g @openai/codex
```
Минимум — Node.js 20. На Windows работает через WSL2 или обычный PowerShell с глобальным npm.
## Подключение к Hubris
Codex CLI читает настройки из `~/.codex/config.toml` (на Windows — `%USERPROFILE%\.codex\config.toml`). Добавьте туда блок с провайдером Hubris и выберите его по умолчанию:
```toml
[model_providers.hubris]
name = "Hubris"
base_url = "https://api.hubris.pw/v1"
env_key = "HUBRIS_API_KEY"
wire_api = "responses"
requires_openai_auth = false
model_provider = "hubris"
model = "openai/gpt-4o-mini"
```
Что в этих полях:
* `base_url` — корень API Hubris вместе с префиксом `/v1`.
* `env_key` — имя переменной окружения, из которой Codex CLI возьмёт ваш API-ключ Hubris. Сам ключ держите в переменной, а не в файле:
```bash
export HUBRIS_API_KEY="sk-gw-..." # macOS / Linux / WSL
```
```powershell
$env:HUBRIS_API_KEY = "sk-gw-..." # PowerShell
```
* `wire_api = "responses"` — Codex CLI поддерживает только Responses API, поэтому значение тут единственное.
* `requires_openai_auth = false` — отключает встроенный вход через ChatGPT (он не нужен, ключ берётся из `HUBRIS_API_KEY`).
* `model` — модель из [нашего каталога](/models). Любая активная модель в формате `provider/model` подойдёт.
## Запуск
```bash
codex
```
При первом запуске Codex CLI проверит конфигурацию и сделает тестовый запрос. Если ключ корректный — увидите интерактивную сессию. Если нет — ошибка авторизации, проверьте `HUBRIS_API_KEY` и сам ключ на странице [Ключи](https://hubris.pw/keys) в дашборде.
## Выбор модели
Codex CLI хорошо работает с любой моделью, у которой стабильный Responses API. Рекомендации по задачам:
* **Быстрые правки, рутина** — `openai/gpt-4o-mini`, `anthropic/claude-haiku-4.5`.
* **Сложные изменения, рефакторинг** — `openai/gpt-4o`, `anthropic/claude-sonnet-4.5`.
* **Когда нужны рассуждения** (планирование больших изменений, поиск багов) — `openai/o1`, `openai/o3-mini`.
Сменить модель можно прямо в `config.toml` или флагом `--model`:
```bash
codex --model anthropic/claude-sonnet-4.5
```
Полный список — на странице [Каталог моделей](/models). Цены указаны там же в рублях за 1 миллион токенов.
## Тарификация
Codex CLI ничем не отличается от прямых запросов к `/v1/responses` — каждое сообщение списывается с баланса по тарифу выбранной модели. Токены рассуждения (если модель их использует) учитываются в выходных токенах. Историю запросов и затраты смотрите в дашборде на странице [Использование](https://hubris.pw/usage).
## Что дальше
* [POST /v1/responses](/docs/api/responses) — описание самого эндпоинта, который дёргает Codex CLI.
* [Аутентификация](/docs/authentication) — как создать ключ и где хранить его в проекте.
* [Каталог моделей](/models) — какие модели сейчас активны и сколько стоят.
---
# LangChain.js (/docs/frameworks/langchain-js)
> Подключить Hubris к LangChain.js через ChatOpenAI.
LangChain.js работает с Hubris через `ChatOpenAI` из пакета `@langchain/openai`.
## Установка
```bash
npm install langchain @langchain/openai @langchain/core
```
## Подключение
```ts
import { ChatOpenAI } from "@langchain/openai";
const llm = new ChatOpenAI({
model: "anthropic/claude-haiku-4.5",
apiKey: process.env.HUBRIS_API_KEY,
configuration: {
baseURL: "https://api.hubris.pw/v1",
},
});
```
**Замечание про `configuration.baseURL`.** В LangChain.js есть несколько способов задать base URL — самый надёжный для стриминга — через объект `configuration` (он передаётся в OpenAI SDK внутри). Не используйте устаревший `basePath` или `apiBaseUrl` — они ломают стриминг в некоторых версиях.
## Базовый вызов
```ts
import { HumanMessage } from "@langchain/core/messages";
const response = await llm.invoke([new HumanMessage("Привет")]);
console.log(response.content);
```
## С шаблоном промпта
```ts
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { StringOutputParser } from "@langchain/core/output_parsers";
const prompt = ChatPromptTemplate.fromMessages([
["system", "Вы — лаконичный ассистент."],
["user", "{question}"],
]);
const chain = prompt.pipe(llm).pipe(new StringOutputParser());
const result = await chain.invoke({ question: "Что такое pi?" });
console.log(result);
```
## Стриминг
```ts
const stream = await llm.stream([new HumanMessage("Расскажи историю")]);
for await (const chunk of stream) {
process.stdout.write(chunk.content as string);
}
```
## С tool calling
```ts
import { tool } from "@langchain/core/tools";
import { z } from "zod";
const weatherTool = tool(
async ({ city }) => ({ city, temp: 5, condition: "sunny" }),
{
name: "get_weather",
description: "Текущая погода в городе",
schema: z.object({ city: z.string() }),
},
);
const llmWithTools = llm.bindTools([weatherTool]);
const response = await llmWithTools.invoke([new HumanMessage("Погода в Москве?")]);
console.log(response.tool_calls);
```
## Что дальше
* [Каталог моделей](/models) — все доступные `provider/model`.
* [POST /v1/chat/completions](/docs/api/chat-completions) — полная схема параметров.
---
# LangChain (Python) (/docs/frameworks/langchain-python)
> Подключить Hubris к LangChain через ChatOpenAI.
LangChain работает с Hubris через стандартный класс `ChatOpenAI` из пакета `langchain-openai` — задаёте `base_url` на наш endpoint.
## Установка
```bash
pip install langchain langchain-openai
```
## Подключение
```python
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="anthropic/claude-haiku-4.5",
base_url="https://api.hubris.pw/v1",
api_key="sk-gw-...",
)
```
## Базовый вызов
```python
from langchain_core.messages import HumanMessage
response = llm.invoke([HumanMessage(content="Привет")])
print(response.content)
```
## С системным промптом и chain
```python
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
prompt = ChatPromptTemplate.from_messages([
("system", "Вы — лаконичный ассистент."),
("user", "{question}"),
])
chain = prompt | llm | StrOutputParser()
result = chain.invoke({"question": "Что такое pi с точностью до 5 знаков?"})
print(result)
```
## Стриминг
```python
for chunk in llm.stream([HumanMessage(content="Расскажи историю")]):
print(chunk.content, end="", flush=True)
```
## С tool calling
```python
from langchain_core.tools import tool
@tool
def get_weather(city: str) -> dict:
"""Текущая погода в городе."""
return {"city": city, "temp": 5, "condition": "sunny"}
llm_with_tools = llm.bind_tools([get_weather])
response = llm_with_tools.invoke([HumanMessage(content="Какая погода в Москве?")])
print(response.tool_calls)
```
## С агентами (LangGraph)
`ChatOpenAI` напрямую совместим с `langgraph` агентами — указываете тот же клиент в `create_react_agent`:
```python
from langgraph.prebuilt import create_react_agent
agent = create_react_agent(llm, tools=[get_weather])
result = agent.invoke({"messages": [HumanMessage(content="Какая погода в Москве?")]})
```
## Что дальше
* [Каталог моделей](/models) — все доступные `provider/model`.
* [POST /v1/chat/completions](/docs/api/chat-completions) — полная схема.
---
# OpenAI SDK для Node.js (/docs/frameworks/openai-node)
> Использовать официальный openai пакет с Hubris — поменяйте baseURL и apiKey.
Официальный пакет `openai` от OpenAI работает с Hubris как прямая замена — меняете два параметра при инициализации клиента, остальной код не трогаете.
## Установка
```bash
npm install openai
# или
pnpm add openai
# или
yarn add openai
```
## Подключение
```ts
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.hubris.pw/v1",
apiKey: process.env.HUBRIS_API_KEY,
});
```
## Базовый запрос
```ts
const response = await client.chat.completions.create({
model: "anthropic/claude-haiku-4.5",
messages: [{ role: "user", content: "Привет" }],
});
console.log(response.choices[0].message.content);
```
## Стриминг
```ts
const stream = await client.chat.completions.create({
model: "anthropic/claude-haiku-4.5",
messages: [{ role: "user", content: "Расскажи историю" }],
stream: true,
});
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta?.content;
if (delta) process.stdout.write(delta);
}
```
## Tool calling
```ts
const tools = [{
type: "function" as const,
function: {
name: "get_weather",
description: "Текущая погода в городе",
parameters: {
type: "object",
properties: { city: { type: "string" } },
required: ["city"],
},
},
}];
const response = await client.chat.completions.create({
model: "anthropic/claude-haiku-4.5",
messages: [{ role: "user", content: "Какая погода в Москве?" }],
tools,
});
console.log(response.choices[0].message.tool_calls);
```
## Что не работает
`client.embeddings.create()`, `client.images.generate()`, `client.audio.*`, `client.files.*`, `client.batches.*` — соответствующих эндпоинтов нет, появятся в ближайших обновлениях.
## Что дальше
* [Каталог моделей](/models) — все идентификаторы.
* [POST /v1/chat/completions](/docs/api/chat-completions) — полная схема.
---
# OpenAI SDK для Python (/docs/frameworks/openai-python)
> Использовать официальный openai пакет с Hubris — поменяйте base_url и api_key.
Официальный пакет `openai` от OpenAI работает с Hubris как прямая замена: меняете два параметра при инициализации клиента, остальной код не трогаете.
## Установка
```bash
pip install openai
```
Минимальная версия — 1.0+ (новый API клиента). Старый stub-стиль (`openai.api_key = ...`) не поддерживаем.
## Подключение
```python
from openai import OpenAI
client = OpenAI(
base_url="https://api.hubris.pw/v1",
api_key="sk-gw-...",
)
```
Лучше через переменную окружения:
```python
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.hubris.pw/v1",
api_key=os.environ["HUBRIS_API_KEY"],
)
```
## Базовый запрос
```python
response = client.chat.completions.create(
model="anthropic/claude-haiku-4.5",
messages=[{"role": "user", "content": "Привет"}],
)
print(response.choices[0].message.content)
```
## Стриминг
```python
stream = client.chat.completions.create(
model="anthropic/claude-haiku-4.5",
messages=[{"role": "user", "content": "Расскажи историю"}],
stream=True,
)
for chunk in stream:
delta = chunk.choices[0].delta.content
if delta:
print(delta, end="", flush=True)
```
## Tool calling
Обычный OpenAI-API:
```python
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Текущая погода в городе",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}]
response = client.chat.completions.create(
model="anthropic/claude-haiku-4.5",
messages=[{"role": "user", "content": "Какая погода в Москве?"}],
tools=tools,
)
print(response.choices[0].message.tool_calls)
```
## Async-клиент
```python
from openai import AsyncOpenAI
client = AsyncOpenAI(
base_url="https://api.hubris.pw/v1",
api_key="sk-gw-...",
)
async def main():
response = await client.chat.completions.create(
model="anthropic/claude-haiku-4.5",
messages=[{"role": "user", "content": "Привет"}],
)
print(response.choices[0].message.content)
```
## Что не работает
`client.embeddings.create()`, `client.images.generate()`, `client.audio.*`, `client.files.*`, `client.batches.*` — соответствующих эндпоинтов в Hubris сейчас нет, эти вызовы упадут с 404. Появятся в ближайших обновлениях.
## Что дальше
* [Каталог моделей](/models) — все доступные `provider/model` идентификаторы.
* [POST /v1/chat/completions](/docs/api/chat-completions) — полная схема параметров.
* [Аутентификация](/docs/authentication) — про ключи.
---
# Vercel AI SDK (/docs/frameworks/vercel-ai-sdk)
> Подключить Hubris к Vercel AI SDK через @ai-sdk/openai-compatible.
Vercel AI SDK подключается к Hubris через провайдер `@ai-sdk/openai-compatible` — он принимает любой OpenAI-совместимый endpoint.
## Установка
```bash
npm install ai @ai-sdk/openai-compatible
```
## Подключение
```ts
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
const hubris = createOpenAICompatible({
name: "hubris",
baseURL: "https://api.hubris.pw/v1",
apiKey: process.env.HUBRIS_API_KEY,
});
```
## generateText (одиночный запрос)
```ts
import { generateText } from "ai";
const { text } = await generateText({
model: hubris.chatModel("anthropic/claude-haiku-4.5"),
prompt: "Привет, как дела?",
});
console.log(text);
```
## streamText (стриминг)
```ts
import { streamText } from "ai";
const { textStream } = await streamText({
model: hubris.chatModel("anthropic/claude-haiku-4.5"),
prompt: "Расскажи короткую историю",
});
for await (const chunk of textStream) {
process.stdout.write(chunk);
}
```
## С tool calling
```ts
import { generateText, tool } from "ai";
import { z } from "zod";
const result = await generateText({
model: hubris.chatModel("anthropic/claude-haiku-4.5"),
prompt: "Какая погода в Москве?",
tools: {
getWeather: tool({
description: "Текущая погода в городе",
parameters: z.object({ city: z.string() }),
execute: async ({ city }) => ({ city, temp: 5, condition: "sunny" }),
}),
},
});
```
## Использование в Next.js Route Handler
```ts
// app/api/chat/route.ts
import { streamText } from "ai";
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
const hubris = createOpenAICompatible({
name: "hubris",
baseURL: "https://api.hubris.pw/v1",
apiKey: process.env.HUBRIS_API_KEY,
});
export async function POST(req: Request) {
const { messages } = await req.json();
const result = await streamText({
model: hubris.chatModel("anthropic/claude-haiku-4.5"),
messages,
});
return result.toDataStreamResponse();
}
```
## Что дальше
* [Каталог моделей](/models) — все идентификаторы.
* [POST /v1/chat/completions](/docs/api/chat-completions) — полная схема параметров.
---
# Аудио в чате (запись на вход, голос на выход) (/docs/features/audio)
> Модель слушает аудиозапись и рассуждает о ней — либо отвечает голосом. Content-part input_audio, modalities audio, стриминг delta.audio.
import { CodeTabs } from '@/components/docs/code-tabs';
import { EndpointBadge } from '@/components/docs/endpoint-badge';
Обычный [`/v1/chat/completions`](/docs/api/chat-completions) умеет работать со звуком в обе стороны:
* **Аудио на вход** — вы прикладываете запись к сообщению, и модель отвечает на вопросы о ней: кто говорит, каким тоном, что за шум на фоне, о чём договорились в звонке.
* **Голос на выход** — модель озвучивает свой ответ, аудио приходит в потоке вместе с текстовой расшифровкой.
Не путайте с [транскрибацией](/docs/api/transcriptions): `/v1/audio/transcriptions` решает одну задачу — превратить запись в текст, дёшево и предсказуемо. Аудио в чате — это рассуждение о записи (можно совмещать с tool calling и структурированным выводом) и разговорный сценарий. Если нужен просто текст расшифровки, берите транскрибацию — она для этого и сделана.
## Эндпоинт
## Аудио на вход
Запись передаётся как content-part типа `input_audio` внутри user-сообщения — рядом с обычным текстом:
```json
{
"role": "user",
"content": [
{ "type": "text", "text": "О чём эта запись и каким тоном говорят?" },
{
"type": "input_audio",
"input_audio": { "data": "UklGRiQAAABXQVZFZm10...", "format": "wav" }
}
]
}
```
| Поле | Тип | Описание |
| ------------------------ | --------------- | ------------------------------------------------------------------------------ |
| **`type`** | `"input_audio"` | Дискриминатор content-part. |
| **`input_audio.data`** | `string` | Аудио в base64 **без** префикса `data:audio/...;base64,` — только сами данные. |
| **`input_audio.format`** | `string` | `wav`, `mp3`, `aiff`, `aac`, `ogg`, `flac`, `m4a`, `pcm16`, `pcm24`. |
Ссылки на аудио не поддерживаются — только base64. Base64 раздувает запрос примерно на треть, поэтому длинные записи лучше резать: тело запроса ограничено 36 МБ, минута WAV — это около 2,7 МБ.
### Минимальный пример
Официальные `openai` SDK для Python и TypeScript работают без правок — достаточно поменять `base_url` / `baseURL`.
### Сколько это стоит в токенах
Аудио превращается в отдельный подвид prompt-токенов. В ответе они видны в `usage.prompt_tokens_details.audio_tokens` и входят в общий `usage.prompt_tokens`:
```json
{
"usage": {
"prompt_tokens": 70,
"completion_tokens": 38,
"total_tokens": 108,
"prompt_tokens_details": { "audio_tokens": 50 },
"cost": 9
}
}
```
### Какие модели принимают аудио
Те, у которых в карточке ([`GET /v1/models`](/docs/api/models)) поле `input_modalities` содержит `"audio"` — в [каталоге](/models) это фильтр «принимает аудио». Обратите внимание: под фильтр попадают и модели распознавания речи (Whisper, Voxtral, Nova и другие) — они предназначены для [`/v1/audio/transcriptions`](/docs/api/transcriptions), а не для чата. Для аудио в диалоге берите чат-модели: семейство Gemini, `openai/gpt-audio` и подобные.
Если модель аудио не принимает, запрос отклоняется **до** обращения к провайдеру — с кодом `400` и `code: "audio_input_not_supported"`. За такой вызов ничего не списывается.
## Голос на выход
Если диалог не нужен и требуется просто озвучить готовый текст, берите [`/v1/audio/speech`](/docs/api/speech): один запрос — один аудиофайл, дешевле и предсказуемее. Всё, что ниже, — про голосовой ответ модели внутри чата.
Чтобы модель ответила голосом, нужны три вещи одновременно:
1. `modalities: ["text", "audio"]`;
2. объект `audio` с голосом и форматом;
3. **`stream: true`** — иначе `400` с `code: "audio_output_requires_stream"`.
| Поле | Тип | Описание |
| ------------------ | --------------- | ----------------------------------------------- |
| **`modalities`** | `array` | `["text","audio"]`. |
| **`audio.voice`** | `string` | Голос озвучки, например `alloy`. Список — ниже. |
| **`audio.format`** | `string` | Формат ответа: `wav`, `mp3`, `pcm16` и т. п. |
| **`stream`** | `boolean` | Обязательно `true`. |
```bash
curl -N -s https://api.hubris.pw/v1/chat/completions \
-H "Authorization: Bearer sk-gw-..." \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-audio",
"modalities": ["text", "audio"],
"audio": {"voice": "alloy", "format": "pcm16"},
"stream": true,
"messages": [{"role": "user", "content": "Скажи «Привет!»"}]
}'
```
Звук приходит частями внутри обычных SSE-чанков, в поле `delta.audio`:
```json
{
"id": "chatcmpl-abc123",
"object": "chat.completion.chunk",
"choices": [
{
"index": 0,
"delta": {
"audio": {
"id": "audio_abc123",
"data": "GAAaABwAHgAgACIAJAAmACgA...",
"transcript": "Привет"
}
}
}
]
}
```
| Поле | Описание |
| ------------------------ | -------------------------------------------------------------------------------------------------------------- |
| `delta.audio.id` | Идентификатор аудио-фрагмента ответа. |
| `delta.audio.data` | Кусок аудио в base64, в формате из `audio.format`. Склеивайте куски по порядку и декодируйте один раз в конце. |
| `delta.audio.transcript` | Текстовая расшифровка того, что модель произносит в этом куске. |
Расшифровка удобна, чтобы показывать субтитры в момент воспроизведения и сохранять реплику в историю диалога. Общий формат SSE, `[DONE]` и сбор `usage` — на странице [Стриминг](/docs/features/streaming).
Токены озвучки приходят в `completion_tokens_details.audio_tokens` (подмножество `completion_tokens`).
### Какие бывают голоса
Для моделей семейства `openai/gpt-audio*` доступны тринадцать голосов:
`alloy`, `echo`, `fable`, `onyx`, `nova`, `shimmer`, `coral`, `verse`, `ballad`, `ash`, `sage`, `marin`, `cedar`.
Отдельного справочника голосов у провайдеров нет, и в карточке модели они не публикуются. Если голос указан неверно, ответ приходит с кодом `400`, а в тексте ошибки провайдер перечисляет все допустимые значения для этой модели — это и есть способ узнать список для любой новой модели:
```json
{
"error": {
"message": "Invalid value: 'нет-такого'. Supported values are: 'alloy', 'echo', 'fable', ...",
"type": "invalid_request_error",
"code": "invalid_value"
}
}
```
### Модели с голосовым ответом
Их сильно меньше, чем принимающих аудио: нужно `"audio"` в `output_modalities` карточки модели — например `openai/gpt-audio` и `openai/gpt-audio-mini`. Актуальный список — в [каталоге](/models). Запрос `modalities: ["text","audio"]` к модели без голосового выхода отклоняется с `400` и `code: "audio_output_not_supported"`.
Не путайте с генерацией музыки: у моделей вроде `google/lyria-3-pro-preview` на выходе тоже аудио, но это сочинённый трек по текстовому описанию, а не проговоренный ответ. Они не ведут диалог голосом и оплачиваются за клип или песню — смотрите цену на карточке модели.
### Ограничение: аудио не переиспользуется по `id`
Ссылаться в следующем запросе на прошлый ответ по `audio.id` нельзя — идентификатор живёт в пределах одного ответа. Историю мультитёрн-диалога ведите текстом: кладите в `messages` assistant-сообщение с `content` или с `audio.transcript` предыдущей реплики. Сами байты озвучки в историю отправлять не нужно — это только раздует запрос.
## HTTP-коды
| Код | `code` | Когда |
| ----------------- | ------------------------------ | -------------------------------------------------------------------------------------------- |
| `200` | — | Успешный ответ. |
| `400` | `audio_input_not_supported` | В сообщениях есть `input_audio`, а модель не принимает аудио. |
| `400` | `audio_output_not_supported` | В `modalities` есть `audio`, а модель не умеет отвечать голосом. |
| `400` | `audio_output_requires_stream` | Голосовой ответ запрошен без `stream: true`. |
| `400` | — | Тело не соответствует схеме (например, `data` с префиксом `data:` или неизвестный `format`). |
| `401` | — | Ключ отсутствует / отозван / невалиден. |
| `402` | — | Недостаточно средств на балансе. |
| `413` | `request_too_large` | Тело запроса больше 36 МБ. |
| `429` | — | Превышен дневной лимит на ключе. |
| `502 / 503 / 504` | — | Транзиентные сбои у провайдера или курса ЦБ. |
Формат тела ошибки и стратегия retry — [Ошибки](/docs/concepts/errors).
## Биллинг
Аудио-токены тарифицируются по отдельной ставке модели — обычно она выше текстовой, и на вход и на выход ставки разные. Итоговая стоимость запроса приходит в `usage.cost` в копейках: это ровно та сумма, что списывается с баланса и попадает в [журнал запросов](/logs), с уже учтённой разницей ставок.
Списание — по факту завершённого запроса, итог приходит в `usage.cost` **в копейках** (как на всех остальных эндпоинтах) и совпадает с суммой в [логах](/logs). При обрыве соединения во время голосового стрима ответ дочитывается до конца и оплачивается полностью.
## Что дальше
* [POST /v1/audio/transcriptions](/docs/api/transcriptions) — просто получить текст записи, без рассуждений модели.
* [POST /v1/audio/speech](/docs/api/speech) — обратная задача: озвучить готовый текст одним запросом.
* [Стриминг](/docs/features/streaming) — разбор SSE-чанков и сбор `usage`.
* [POST /v1/chat/completions](/docs/api/chat-completions) — полная схема параметров.
* [Изображения на вход](/docs/features/vision) — та же логика мультимодального ввода, только для картинок.
* [Цены](/docs/concepts/pricing) — как считается `usage.cost`.
---
# Обработка ошибок (/docs/features/error-handling)
> Стратегия retry, backoff, комбинация с model fallbacks и что показывать конечному пользователю.
Гид про практическую работу с ошибками Hubris API — где имеет смысл ретраить, где не имеет, как комбинировать с [Model Fallbacks](/docs/features/model-fallbacks), и как не показать сырое сообщение API конечному пользователю.
Полная таблица кодов и форматов ответа — на странице [Ошибки](/docs/concepts/errors).
## Стратегия по кодам
| HTTP | code | Ретраить? | Почему |
| ---- | --------------------------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| 400 | `invalid_request` | ❌ нет | Это баг в коде. Ретрай → та же ошибка. Логируйте и чините. |
| 401 | `invalid_api_key` | ❌ нет | Ключ невалиден. Ротация на лету не предусмотрена. |
| 402 | `insufficient_balance` | ❌ нет (но можно ждать) | Кончился баланс. Если есть авто-пополнение — после крупного топ-апа ретрай поможет. Иначе — алёрт. |
| 404 | `model_not_found` | ❌ нет | Модель архивирована или ID опечатан. Лучше fallback на актуальную ([model-selection](/docs/features/model-selection)). |
| 429 | `daily_limit_exceeded` | ❌ не имеет смысла | Лимит ключа исчерпан. До конца окна — отказ. Используйте другой ключ или ждите. |
| 502 | `upstream_error` | ✅ да | Апстрим вернул 5xx или контент-фильтр. Транзиентно. |
| 503 | `exchange_rate_unavailable` | ✅ да | Курс ЦБ временно не доступен — каталог не отдаёт цены. Обычно минуту. |
| 504 | `upstream_timeout` | ✅ да | Модель перегружена. Часто помогает другая модель из каталога. |
Главное правило: **ретраить транзиентные сбои (5xx) с backoff, остальное — нет**.
## Где ретрай встроен на стороне Hubris
Hubris уже автоматически переключается на запасную модель, если вы передали массив `models`:
```bash
curl -s https://api.hubris.pw/v1/chat/completions \
-d '{
"model": "anthropic/claude-haiku-4.5",
"models": [
"anthropic/claude-haiku-4.5",
"openai/gpt-4o-mini"
],
"messages": [{"role": "user", "content": "..."}]
}'
```
Если основная модель ответила 429 или 5xx — клиент видит успешный ответ от запасной. Подробнее: [Model Fallbacks](/docs/features/model-fallbacks).
**Это снимает большую часть транзиентных ошибок без вашего ретрая.** Перед тем как писать сложную ретрай-логику в клиенте — попробуйте `models: [...]`. На практике она закрывает 80% сценариев.
## Когда нужен клиентский ретрай поверх
Кейсы, где Hubris-fallback не спасает:
* **Нет резервной модели в массиве `models` или нужна та же самая модель.** Если основная модель уникальна (например, специфичный image-gen ID, который никто не дублирует) — клиентский ретрай.
* **503 `exchange_rate_unavailable`.** Это не ошибка модели — это про каталог. Fallback моделей не сработает. Ретрай через минуту.
* **Сетевые таймауты до Hubris.** До нас даже не дошёл запрос — fallback не запустится. Клиентский ретрай.
## Python: exponential backoff
```python
import time
import openai
from openai import OpenAI
client = OpenAI(
base_url="https://api.hubris.pw/v1",
api_key="sk-gw-...",
)
def call_with_retry(messages, max_attempts=3):
for attempt in range(max_attempts):
try:
return client.chat.completions.create(
model="anthropic/claude-haiku-4.5",
models=[
"anthropic/claude-haiku-4.5",
"openai/gpt-4o-mini",
],
messages=messages,
)
except openai.APIStatusError as e:
# Ретраим только транзиентные коды
if e.status_code in (502, 503, 504):
if attempt == max_attempts - 1:
raise
# Exponential backoff с jitter
wait = (2 ** attempt) + (0.5 * attempt)
time.sleep(wait)
continue
# 4xx — баг или конфигурация, ретрай не поможет
raise
resp = call_with_retry([{"role": "user", "content": "Hello"}])
```
Использование `models: [...]` параллельно с retry — это нормально: они работают на разных уровнях (модель vs сеть/каталог). Двойного списания не будет — биллинг по фактически ответившему запросу.
### Готовая библиотека
Если не хотите писать backoff руками — есть [`tenacity`](https://tenacity.readthedocs.io/):
```python
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
import openai
@retry(
retry=retry_if_exception_type((openai.APITimeoutError, openai.APIConnectionError)),
stop=stop_after_attempt(3),
wait=wait_exponential(multiplier=1, min=1, max=10),
)
def call_llm(messages):
return client.chat.completions.create(
model="anthropic/claude-haiku-4.5",
messages=messages,
)
```
## TypeScript: фабрика с retry
```ts
import OpenAI from 'openai';
const client = new OpenAI({
baseURL: 'https://api.hubris.pw/v1',
apiKey: 'sk-gw-...',
});
async function callWithRetry(
messages: Array<{ role: string; content: string }>,
maxAttempts = 3,
) {
for (let attempt = 0; attempt < maxAttempts; attempt++) {
try {
return await client.chat.completions.create({
model: 'anthropic/claude-haiku-4.5',
models: [
'anthropic/claude-haiku-4.5',
'openai/gpt-4o-mini',
],
messages,
} as any);
} catch (e: any) {
const transient = [502, 503, 504].includes(e?.status);
const lastAttempt = attempt === maxAttempts - 1;
if (!transient || lastAttempt) throw e;
const wait = (2 ** attempt) * 1000 + Math.random() * 500;
await new Promise((r) => setTimeout(r, wait));
}
}
throw new Error('unreachable');
}
```
## Что показывать пользователю
Сырые сообщения API не подходят для production UI:
| Внутреннее | Внешнее (пользователю) |
| -------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `401 invalid_api_key` | «Внутренняя ошибка авторизации» (никогда не показывайте «неверный ключ» — это намекает, что у вас есть рабочий) |
| `402 insufficient_balance` | «Сервис временно недоступен» — это **ваша** проблема, не пользователя |
| `404 model_not_found` | «Сервис обновляет каталог» — подставьте альтернативную модель |
| `429 daily_limit_exceeded` | «Лимит запросов на сегодня исчерпан, попробуйте завтра» |
| `502 / 503 / 504` | «Модель временно недоступна, повторим попытку» (если уже идёт retry) |
Никогда не показывайте `error.message` модели напрямую — он на английском и может содержать технические детали (имена провайдеров, ID моделей).
## Как НЕ надо
```python
# Плохо: ретрай 401 / 402 / 404
for i in range(5):
try:
return client.chat.completions.create(...)
except Exception:
time.sleep(2 ** i)
```
Слепой ретрай на любую ошибку:
* На `401` (неверный ключ) — крутит цикл с экспоненциальной задержкой, прежде чем сдаться. UI замораживается.
* На `402` (нет денег) — то же самое, плюс ваш сервис атакует наш API без шансов на успех.
* На `400` (баг в коде) — никогда не починится сама.
```python
# Плохо: показ сырого error.message
except openai.APIError as e:
return jsonify({"error": str(e)}), 500
```
Сырая ошибка попадёт в браузер пользователя. Безопаснее:
```python
except openai.APIError as e:
log.error("LLM error", extra={"status": getattr(e, "status_code", None)})
return jsonify({"error": "Сервис временно недоступен"}), 503
```
## Стриминг и ошибки
При `stream: true` ошибка ДО первого чанка возвращается как обычный JSON `{ error: {...} }` с соответствующим HTTP-кодом. После начала стрима — клиент видит обрыв соединения или преждевременный `finish_reason`. Подробности — на [странице стриминга](/docs/features/streaming) и в [концепции ошибок](/docs/concepts/errors#стриминг).
## Что важно знать
* **Hubris не списывает деньги за неуспешные запросы.** 4xx и 5xx — без биллинга. Streaming-обрыв после первого чанка — частичное списание по тому, что успело сгенерироваться.
* **Не ретраите structured-output ответы внутри одного контекста.** Если модель упала на структуре — лучше переключиться на другую модель (`models: [...]`), а не повторять с той же.
* **Лимиты по таймауту** — ваш HTTP-клиент должен иметь reasonable timeout (90–120 сек), иначе соединение зависнет на perpetually-slow модели.
## Что дальше
* [Ошибки](/docs/concepts/errors) — полная таблица кодов с примерами JSON-ответов.
* [Model Fallbacks](/docs/features/model-fallbacks) — встроенный fallback на запасную модель.
* [Стриминг ответов](/docs/features/streaming) — что происходит с ошибками во время стрима.
* [Управление ключами](/keys) — дневные лимиты на стороне сервера.
---
# Генерация изображений (/docs/features/image-generation)
> Генерация и редактирование изображений: гибридные модели через /v1/chat/completions с modalities, премиум-модели через /v1/images/generations с input_references.
Hubris генерирует изображения двумя путями — какой использовать, зависит от модели:
| Линейка моделей | Эндпоинт | Вход для редактирования |
| -------------------------------------------------------- | --------------------------------------------------------- | -------------------------------------------------------------- |
| Google Gemini \*-image (Nano Banana), OpenAI GPT-5 Image | [`POST /v1/chat/completions`](/docs/api/chat-completions) | картинка в `messages` (как во [Vision](/docs/features/vision)) |
| FLUX.2, Recraft, Seedream, Riverflow, Grok Imagine | [`POST /v1/images/generations`](/docs/api/images) | поле `input_references` |
Первый путь — «диалоговый»: модель отвечает и текстом, и картинкой, умеет пошагово дорабатывать результат в переписке. Второй — классический Images API в формате OpenAI: один запрос → готовые изображения, фиксированная цена за штуку. Полный список моделей — фильтр «Output: image» в [каталоге](/models); на карточке модели видно, как она тарифицируется: токенами (chat-путь) или за изображение (images-путь).
## Генерация через chat completions
Передайте `modalities` и модель с поддержкой image-output:
```bash
curl -s https://api.hubris.pw/v1/chat/completions \
-H "Authorization: Bearer sk-gw-..." \
-H "Content-Type: application/json" \
-d '{
"model": "google/gemini-2.5-flash-image",
"modalities": ["image", "text"],
"messages": [{"role": "user", "content": "Закат над горами в стиле Гибли"}]
}'
```
Картинка возвращается как base64 data URL в `choices[0].message.images[0].image_url.url`:
```json
{
"id": "chatcmpl-...",
"model": "google/gemini-2.5-flash-image",
"choices": [
{
"message": {
"role": "assistant",
"content": "Готово!",
"images": [
{
"type": "image_url",
"image_url": { "url": "data:image/png;base64,iVBORw0KGgo..." }
}
]
}
}
],
"usage": { "prompt_tokens": 18, "completion_tokens": 1290, "total_tokens": 1308 }
}
```
`modalities: ["image", "text"]` — модель отдаёт и текст-комментарий, и картинку. Если передать `modalities` не-image модели, вернётся ошибка провайдера.
## Генерация через /v1/images/generations
FLUX.2, Recraft, Seedream, Riverflow и Grok Imagine через chat completions недоступны (`404 model_not_found`) — они работают только через выделенный эндпоинт в формате OpenAI Images API:
```bash
curl -s https://api.hubris.pw/v1/images/generations \
-H "Authorization: Bearer sk-gw-..." \
-H "Content-Type: application/json" \
-d '{
"model": "black-forest-labs/flux.2-pro",
"prompt": "product photo of a ceramic mug on a linen tablecloth, soft window light"
}'
```
Ответ — `data[].b64_json` (base64 **без** префикса `data:`), как у OpenAI. Официальные SDK работают из коробки:
```python
import base64
from openai import OpenAI
client = OpenAI(base_url="https://api.hubris.pw/v1", api_key="sk-gw-...")
resp = client.images.generate(
model="black-forest-labs/flux.2-pro",
prompt="product photo of a ceramic mug on a linen tablecloth",
)
open("mug.png", "wb").write(base64.b64decode(resp.data[0].b64_json))
```
Совет по FLUX: пишите промпт на английском — кириллицу эта линейка норовит отрисовать как текст на самой картинке. Gemini, Seedream и Recraft русский понимают нормально.
Все поля запроса, коды ошибок и биллинг — в референсе [POST /v1/images/generations](/docs/api/images).
## Редактирование изображений
Оба пути умеют image-to-image: подаёте исходную картинку + текст, что с ней сделать. Картинка передаётся как data URL с base64 (надёжно у всех провайдеров) или как публичная https-ссылка.
### Через chat completions (Gemini, GPT-5 Image)
Исходное изображение кладётся в `content`-массив сообщения — ровно так же, как во [Vision](/docs/features/vision):
```bash
IMG=$(base64 -w0 photo.jpg)
curl -s https://api.hubris.pw/v1/chat/completions \
-H "Authorization: Bearer sk-gw-..." \
-H "Content-Type: application/json" \
-d @- < Запасные модели на случай 429/5xx/контент-фильтра. Передайте массив `models` — Hubris сам переключится при сбое.
Если основная модель временно недоступна — упёрлась в rate limit провайдера, упала с 5xx или ответ зарезали контент-фильтром — Hubris автоматически попробует следующие модели из массива `models`. Биллинг по фактически использованной модели (она возвращается в поле `model` ответа).
Поддерживается только в [`POST /v1/chat/completions`](/docs/api/chat-completions). На `/v1/responses` пока нет.
## Как работает
Передайте массив `models` рядом с обычным `model`. Первый элемент — основной; остальные пробуются по порядку при ошибке:
```bash
curl -s https://api.hubris.pw/v1/chat/completions \
-H "Authorization: Bearer sk-gw-..." \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-opus-4.7",
"models": [
"anthropic/claude-opus-4.7",
"anthropic/claude-sonnet-4.6",
"openai/gpt-4o-mini"
],
"messages": [{"role": "user", "content": "Привет"}]
}'
```
Если Opus 4.7 вернёт 429 от провайдера — сразу пойдёт Sonnet 4.6; если и тот недоступен — GPT-4o mini. Клиент получит обычный успешный ответ, как если бы fallback не было.
## Когда срабатывает fallback
* **429** — rate limit провайдера.
* **5xx** — апстрим упал.
* **Контент-фильтр** — провайдер заблокировал ответ модерацией (вы получите fallback-результат, а не блок).
* **Network timeout** между Hubris и провайдером.
## Биллинг
Списывается стоимость **только той модели, которая реально ответила**. Поле `model` в JSON-ответе — это победитель (не первый из массива). На странице [/usage](/usage) запрос будет залогирован под этой моделью.
Если в `models` несколько моделей разной цены и срабатывает fallback на самую дорогую — да, спишется по её тарифу. Поэтому имеет смысл располагать модели по возрастанию цены или ставить в конец «дешёвый, но всегда живой» вариант:
```json
{
"model": "anthropic/claude-opus-4.7",
"models": [
"anthropic/claude-opus-4.7",
"anthropic/claude-sonnet-4.6",
"anthropic/claude-haiku-4.5"
],
"messages": [{"role": "user", "content": "..."}]
}
```
## Стриминг
`stream: true` работает с fallback так же. Если основная модель упала ДО первого SSE-чанка, Hubris молча переключится на следующую, и клиент получит чанки уже от неё. Поле `model` в каждом чанке — это уже final-модель. После начала успешного стрима fallback больше не сработает (это технически невозможно: байты пошли клиенту).
**Edge-case:** если клиент дисконнектнется до того, как пришёл первый чанк с полем `model`, биллинг спишется по тарифу основной модели — мы просто не успеем узнать, какая ответила. Это безопасно (over-bill вместо undelivered), но может слегка завысить стоимость в редких сетевых сбоях.
## Ограничения
* **Максимум 10 моделей** в массиве (включая основную).
* **`/v1/responses`** — не поддерживается (используйте `/v1/chat/completions`).
* **`/v1/embeddings`** — не поддерживается (embedding-модели обычно стабильны и не требуют fallback).
* **Структурные ошибки запроса** (400 invalid\_request) не триггерят fallback — это значит ваш запрос неверен, и пробовать другую модель бессмысленно.
## Что дальше
* [POST /v1/chat/completions](/docs/api/chat-completions) — полный список параметров.
* [Ошибки](/docs/concepts/errors) — какие коды и в каких случаях возвращаются.
---
# Выбор модели программно (/docs/features/model-selection)
> Как фильтровать каталог через GET /v1/models, подбирать модель под задачу и обрабатывать переименования.
В Hubris доступны десятки моделей с разными возможностями и ценами. Чтобы не хардкодить ID, а подбирать модель программно — есть `GET /v1/models`. Этот гайд про то, как фильтровать каталог под конкретные задачи и обрабатывать случаи, когда «нужной» модели не оказалось.
## Что возвращает `/v1/models`
```bash
curl -s https://api.hubris.pw/v1/models \
-H "Authorization: Bearer sk-gw-..."
```
Ответ — OpenAI-совместимый список:
```json
{
"object": "list",
"data": [
{
"id": "anthropic/claude-haiku-4.5",
"object": "model",
"created": 1714000000,
"owned_by": "anthropic",
"display_name": "Claude Haiku 4.5",
"description": "Быстрая и недорогая модель Anthropic...",
"context_window": 200000,
"modalities": ["text"],
"input_modalities": ["text", "image"],
"output_modalities": ["text"],
"pricing": {
"unit": "token",
"input_rub_per_million": 88,
"output_rub_per_million": 440,
"currency": "RUB",
"is_free": false
}
}
// ...
]
}
```
Поля каждой модели:
| Поле | Что |
| -------------------------------- | ---------------------------------------------------------------------- |
| `id` | идентификатор для использования в `model` запроса |
| `owned_by` | провайдер модели — `anthropic`, `openai`, `google`, `deepseek`, и т.д. |
| `display_name` | человекочитаемое имя (для UI каталога) |
| `description` | короткое описание возможностей |
| `context_window` | размер контекста в токенах |
| `input_modalities` | что принимает на вход: `text`, `image` |
| `output_modalities` | что выдаёт: `text`, `image` |
| `pricing.unit` | единица тарификации: `token`, `unit` или `unknown` (см. ниже) |
| `pricing.input_rub_per_million` | цена в рублях за 1М prompt-токенов (при `unit: "token"`) |
| `pricing.output_rub_per_million` | цена в рублях за 1М completion-токенов (при `unit: "token"`) |
| `pricing.per_unit` | цены за другую единицу — изображение, мегапиксель, минуту |
| `pricing.is_free` | бесплатна ли модель |
В ответ попадают только активные модели — снятые с публикации не приходят.
### Единица тарификации
Считать стоимость по `input_rub_per_million` можно, только когда `pricing.unit`
равен `token`. Остальные значения:
| `pricing.unit` | Что значит |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `token` | цена за токены, поля `*_rub_per_million` осмысленны |
| `unit` | оплата за другую единицу (изображение, мегапиксель, минуту записи); реальные ставки — в `per_unit`, а `*_rub_per_million` равны нулю |
| `unknown` | провайдер не публикует тариф; списывается фактическая стоимость запроса |
Так, у моделей распознавания речи запрос стоит рублей за минуту записи, а не за
миллион токенов, — умножение токенной ставки на число токенов даст ошибку
в тысячи раз.
## Сценарии фильтрации
### «Хочу самую дешёвую модель с поддержкой изображений на вход»
```python
import httpx
resp = httpx.get(
"https://api.hubris.pw/v1/models",
headers={"Authorization": "Bearer sk-gw-..."},
).json()
vision_models = [
m for m in resp["data"]
if "image" in m["input_modalities"]
]
cheapest = min(
# Сравнивать по цене за токены можно только модели с токенной тарификацией.
[m for m in vision_models if m["pricing"]["unit"] == "token"],
key=lambda m: m["pricing"]["input_rub_per_million"],
)
print(cheapest["id"], cheapest["pricing"])
```
### «Нужен большой контекст — от 500k токенов»
```python
big_context = [m for m in resp["data"] if m["context_window"] >= 500_000]
```
### «Только модели определённого провайдера»
```python
anthropic_only = [m for m in resp["data"] if m["owned_by"] == "anthropic"]
```
### «Сортирую по цене prompt-токенов»
```python
by_input_price = sorted(
[m for m in resp["data"] if m["pricing"]["unit"] == "token"],
key=lambda m: m["pricing"]["input_rub_per_million"],
)
```
## TypeScript
```ts
type Model = {
id: string;
owned_by: string;
context_window: number;
input_modalities: string[];
output_modalities: string[];
pricing: {
unit: 'token' | 'unit' | 'unknown';
input_rub_per_million: number;
output_rub_per_million: number;
per_unit?: Array<{ sku: string; price_rub: number; unit: string }>;
currency: 'RUB';
is_free: boolean;
};
};
const resp = await fetch('https://api.hubris.pw/v1/models', {
headers: { Authorization: 'Bearer sk-gw-...' },
}).then((r) => r.json() as Promise<{ data: Model[] }>);
const visionModels = resp.data.filter((m) =>
m.input_modalities.includes('image'),
);
```
## Стабильность ID
ID модели — стабильный идентификатор. Мы не меняем его молча. При больших изменениях:
* **Новая мажорная версия** — публикуется как **новый ID**. Например, `anthropic/claude-haiku-4.5` отдельная сущность от `anthropic/claude-haiku-5.0`. Старая модель может ещё работать или быть архивирована (см. ниже).
* **Архивация** — модель снимается с активного каталога. Запросы с её ID возвращают `404 model_not_found`. Архивированные модели **не возвращаются** в `GET /v1/models`.
* **Мелкие обновления провайдера** (контекст ↑, скорость ↑, цена ↓) — id остаётся прежним, поля карточки обновляются.
## Что делать с `404 model_not_found`
Если в коде захардкожен ID и модель уехала из каталога:
1. Поймайте `404` с `error.code: "model_not_found"`.
2. Либо используйте [model fallbacks](/docs/features/model-fallbacks): передайте `models: ["primary-id", "backup-id"]` — Hubris сам переключится.
3. Либо подберите замену через `GET /v1/models` и обновите ID у себя.
```python
configured_model_id = "anthropic/claude-haiku-4.5" # ID, который вы используете
try:
resp = client.chat.completions.create(model=configured_model_id, messages=[...])
except openai.NotFoundError:
# модель архивирована — fallback на актуальный каталог
models = httpx.get("https://api.hubris.pw/v1/models", headers={...}).json()
new_model = pick_similar(models["data"], owned_by="anthropic")
resp = client.chat.completions.create(model=new_model["id"], messages=[...])
```
Самое надёжное решение в продакшене — `models: [...]` параметр запроса. Тогда выбор fallback'а делается атомарно внутри запроса, без отдельного round-trip к каталогу.
## Версионированные slug'и
Некоторые провайдеры в ответе возвращают модель с дат-версионированным slug'ом — например, запрос ушёл на `anthropic/claude-haiku-4.5`, а в `response.model` приходит `anthropic/claude-haiku-4.5-20251101`. Это нормально: мы биллим по канонической модели (`anthropic/claude-haiku-4.5`), а слаг просто говорит, какая именно дата-версия ответила.
В Hubris ответ на запрос всегда содержит реально использованный ID в поле `model` — удобно для логов и аналитики, чтобы видеть, какая именно версия модели работала на ваших запросах.
## Что важно знать
* **Не кешируйте каталог дольше нескольких часов.** Цены пересчитываются при обновлении курса ЦБ. Раз в час — нормальная частота обновления.
* **Caps по провайдеру меняются.** Иногда у Anthropic / OpenAI заканчиваются мощности — модель временно отвечает 502/503. На уровне каталога модель остаётся, но провайдер недоступен. [Model fallbacks](/docs/features/model-fallbacks) спасают.
* **Цену за токены считайте только при `pricing.unit === "token"`.** У моделей с оплатой за изображение, секунду видео или минуту записи эти поля равны нулю — реальные цены лежат в `pricing.per_unit`.
* **Бесплатность определяйте по `pricing.is_free`, а не по нулевым ценам за токены.** У моделей с оплатой в других единицах (за изображение, за секунду видео) цена за токены нулевая, а запрос платный. Подробности — [бесплатные модели](/docs/concepts/free-models). У бесплатных свои лимиты на стороне провайдера, не публикуемые.
## Что дальше
* [GET /v1/models](/docs/api/models) — полная схема ответа.
* [Model fallbacks](/docs/features/model-fallbacks) — `models: [...]` для авто-замены.
* [Каталог моделей](/models) — то же самое в UI.
* [Ошибки](/docs/concepts/errors) — обработка `404 model_not_found`.
---
# Privacy Mode (/docs/features/privacy)
> Автоматическое маскирование персональных данных перед отправкой к провайдеру.
## Зачем это нужно
Когда вы отправляете запрос к зарубежной модели, текст уходит за пределы РФ. По 152-ФЗ это требует обоснования трансграничной передачи персональных данных или согласия субъекта. **Privacy Mode** автоматически удаляет ПД из запроса, а в ответе восстанавливает их обратно — модель работает с обезличенным текстом, вы получаете осмысленный ответ.
Фича **бесплатна** для всех моделей каталога.
> **Что Privacy Mode не покрывает.** Маскирование работает для текстовых запросов Chat Completions. Генерация изображений и видео (в т.ч. публичный [`/v1/videos`](/docs/api/videos)) уходит провайдеру без маскирования и не под Zero Data Retention — не передавайте туда персональные данные третьих лиц.
## Что маскируется
| Тип | Примеры |
| ---------------- | ------------------------------------------------ |
| ФИО | Иван Петрович Сидоров |
| Email | [user@example.com](mailto:user@example.com) |
| Телефоны | +7 903 158-22-00, 8(495)555-12-34 |
| Банковские карты | 4276 1600 1234 5678 |
| IBAN / Счета | 40817810099910004312 |
| IP-адреса | 192.168.1.1, 2001:db8::1 |
| Адреса | Москва, Тверская 13 |
| Даты / Время | 12.04.1985, 2024-08-15T10:30 |
| URL | [https://example.com/me](https://example.com/me) |
| Национальность | русский, татарка |
| Паспорта РФ | 4506 № 123456 |
| СНИЛС | 123-456-789 01 |
| ИНН | 7707083893, 770708389312 |
| ОГРН | 1027700132195 |
| Мед. лицензии | LO-77-01-009999 |
## Как настроить
Откройте раздел **«Безопасность»** в личном кабинете (`/security`):
1. Включите тумблер **«PII-маскирование»**.
2. В блоке **«Скрываемые сущности»** выберите типы, которые хотите маскировать.
3. Настройте три опции:
* **Восстанавливать в ответе** — модель возвращает ответ с подставленными оригиналами вместо placeholders. По умолчанию включено.
* **Маскировать system prompt** — применять маскирование к сообщениям с `role: 'system'`. По умолчанию выключено.
* **Блокировать при ошибке** — если сервис маскирования временно недоступен или восстановление данных в ответе не удалось — вернуть ошибку 503/502 вместо тихого пропуска без маски. По умолчанию выключено.
4. Нажмите **«Сохранить политику»**.
## Переопределение для одного ключа
На странице **«Ключи»** (`/keys`) кликните на чип Privacy у нужного ключа и выберите один из четырёх режимов:
* **По политике организации** — следует основной политике (значение по умолчанию).
* **Принудительно выключить** — даже если организация включила.
* **Принудительно включить** — даже если организация выключила.
* **Обязательно** — включено + блокировать при ошибке.
## Переопределение в одном запросе
Передайте заголовок `X-Hubris-Privacy-Mask` или поле `privacy_mask` в теле запроса:
```bash
curl https://api.hubris.pw/v1/chat/completions \
-H "Authorization: Bearer sk-gw-..." \
-H "X-Hubris-Privacy-Mask: on" \
-d '{"model": "openai/gpt-4o", "messages": [{"role":"user","content":"Иван +79031582200"}]}'
```
Возможные значения: `on`, `off`, `required`. Приоритет (сильнее → слабее): header > body > per-key > политика организации.
## Пример: было → стало
**Запрос пользователя:**
> «Иван Петрович, тел +79031582200, запиши на 14:00»
**Уходит к провайдеру:**
> «\[PERSON\_1], тел \[PHONE\_1], запиши на \[DATE\_TIME\_1]»
**Ответ провайдера:**
> «Записал \[PERSON\_1] на \[DATE\_TIME\_1], позвоню за час по \[PHONE\_1]»
**Вы получаете:**
> «Записал Ивана Петровича на 14:00, позвоню за час по +79031582200»
## Стриминг
В режиме `stream: true` Hubris разбирает SSE-чанки на лету. Когда в потоке появляется placeholder вида `[TYPE_N]`, его текст буферизуется (не более 50 символов или 200 мс) и при появлении закрывающей скобки заменяется на оригинал.
Если placeholder не нашёлся в карте подстановок — буфер выдаётся клиенту как есть, а счётчик `X-Hubris-Restore-Mismatch` увеличивается на 1.
## Заголовки ответа
| Заголовок | Значение |
| --------------------------------- | ----------------------------------------------------- |
| `X-Hubris-Privacy-Mode` | `on`, `required`, или `failed` |
| `X-Hubris-Masked-Count` | сколько сущностей скрыто |
| `X-Hubris-Masked-Types` | пример: `person:1,phone:1,email:2` |
| `X-Hubris-Mask-Latency-Ms` | задержка анализа в миллисекундах |
| `X-Hubris-Privacy-Policy-Version` | версия применённой политики |
| `X-Hubris-Restore-Mismatch` | сколько placeholders в ответе не удалось восстановить |
| `X-Hubris-Restore-Skipped` | `1`, если в политике выключено восстановление |
## Что НЕ маскируется
* Изображения в vision-запросах (OCR появится в будущем).
* Содержимое аудио-запросов (когда появится audio-транскрипция).
* Сообщения с `role: 'system'`, если в политике отключено «Маскировать system prompt».
* Произвольная обфускация (например, `п@@@ел Иван`, «4-5-0-6 123456») — это сделано намеренно, чтобы не ломать релевантные пользовательские маркеры.
* Очень нестандартные ФИО без контекстных слов — есть редкие промахи NER.
## Защитные ограничения
* **Размер запроса**: при `privacy_mask ≠ off` суммарный prompt не может превышать 30 000 токенов — иначе HTTP 413. Это защита от перегрузки сервиса маскирования; для длинных юридических документов значение может быть увеличено в будущих версиях.
* **Лимит запросов**: 100 privacy-запросов в минуту на один API-ключ. Превышение — HTTP 429. Лимит мягкий, рассчитан на обычное использование.
## Лучше всего работает на коротких сообщениях
Маскирование рассчитано на одиночные запросы или короткие диалоги (1–3 хода). На длинных multi-turn-сессиях возможен такой эффект:
* В каждом запросе анализатор маскирует **всю историю** (включая прошлые ответы ассистента), присваивая токены вида `[PERSON_1]`, `[LOCATION_5]` и т. д.
* К пятому-десятому ходу контекст модели состоит на 30–70% из таких токенов.
* Модель учит этот паттерн в контексте и начинает **сама генерировать новые токены** (`[LOCATION_29]`, `[PERSON_47]`) для придуманных ею сущностей, которых не было в исходном запросе.
* В нашем `anonymizer map` этих токенов нет — мы не можем подставить «оригинал», потому что оригинала и не существует. Такие токены остаются в ответе как есть.
Признаки утечки токенов:
* Видимые в ответе строки вида `[ТИП_число]` (например, `[LOCATION_29]`).
* В `usage_logs.mask_metadata.restore_mismatch_count` будет ненулевое значение.
* В ответе есть response-заголовок `X-Hubris-Restore-Mismatch: N`.
Что делать:
* Для PII-чувствительных задач используйте **одиночные запросы** без длинной истории.
* Если важна жёсткая гарантия, включите **«Блокировать при ошибке»** в политике. Тогда при `restore_mismatch_count > 0` запрос вернёт HTTP 502 `privacy_mask_restore_failed`, и можно повторить запрос без истории.
* Альтернатива — детектировать утечку клиентом: regex `/\[[A-Z_]+_\d+\]/` по ответу, и при срабатывании очищать историю / начинать новую сессию.
## Технические детали
Под капотом — **Microsoft Presidio** (open-source PII detection engine от Microsoft) с моделью **GLiNER multi-PII** (мультиязычная NER, ONNX-оптимизирована), spaCy `ru_core_news_sm` для русской токенизации и собственные распознаватели СНИЛС, ИНН, паспортов и ОГРН с проверкой контрольной суммы.
Сервис маскирования живёт на том же сервере, что и API, и общается через localhost — данные не покидают периметр Hubris для целей анализа.
## Условия использования
Фича бесплатна. При значительном изменении нагрузки мы можем уточнить лимиты — с уведомлением за 30 дней.
---
# Кеширование промптов (/docs/features/prompt-caching)
> Как работает кеш промптов, почему cached_tokens бывает 0 и как добиться стабильной экономии на повторных запросах.
Кеширование промптов экономит деньги, когда вы много раз отправляете запросы с одним и тем же длинным началом — системной инструкцией, описанием инструментов, большим документом. Провайдер запоминает обработанное начало промпта, и при повторном запросе эти токены оплачиваются по сниженной ставке — обычно в разы дешевле обычного входа.
Скидка применяется автоматически: Hubris списывает стоимость по фактической цене исполнения, в которой кеш уже учтён. Отдельно ничего включать и оплачивать не нужно.
## Два вида кеширования
**Автоматическое** — у большинства современных моделей (DeepSeek, GPT, Gemini, Qwen и другие). Провайдер сам находит совпадающее начало промпта и применяет скидку. От вас требуется одно: начало промпта должно повторяться байт в байт.
**Явное** — у Anthropic-моделей (Claude). Кешируемый блок надо пометить маркером `cache_control` — [см. ниже](#явное-кеширование-anthropic).
## Как проверить, что кеш сработал
Смотрите поле `usage.prompt_tokens_details.cached_tokens` в ответе:
```json
{
"usage": {
"prompt_tokens": 351,
"completion_tokens": 16,
"prompt_tokens_details": {
"cached_tokens": 256
}
}
}
```
`cached_tokens` — сколько входных токенов прочитано из кеша по сниженной ставке. Ноль — кеш в этом запросе не сработал.
## Почему `cached_tokens` бывает 0
Это самый частый вопрос, поэтому по пунктам.
**1. Кеш живёт у конкретного провайдера инференса.** Открытые модели (DeepSeek, Qwen, Llama и т. п.) обслуживают десятки независимых дата-центров, и каждый запрос маршрутизируется на доступный в этот момент. Кеш одного провайдера невидим для другого: если первый запрос обработал один дата-центр, а повторный улетел в другой — кеша там нет. Часть провайдеров кеширование вообще не поддерживает. Какие провайдеры обслуживают модель, видно в её карточке в [каталоге](/models).
**2. Минимальная длина и блочность.** Кешируется только достаточно длинное начало промпта, причём блоками фиксированного размера (обычно кратно 64–256 токенам, у некоторых провайдеров минимум — 1024 токена). Промпт на 100–200 токенов может не закешироваться нигде.
**3. Начало промпта должно совпадать точно.** Любое изменение в начале — дата в системной инструкции, перестановка инструментов, другой порядок сообщений — сбрасывает совпадение с этой позиции. Всё переменное ставьте в конец промпта.
**4. Кеш не вечен.** Время жизни — от минут до часов в зависимости от провайдера. Редкие запросы (раз в час) в кеш обычно не попадают.
## Как добиться стабильных кеш-хитов
Главный инструмент — закрепить провайдера полем `provider` в запросе, тогда повторные запросы будут попадать в один и тот же дата-центр:
```bash
curl -s https://api.hubris.pw/v1/chat/completions \
-H "Authorization: Bearer sk-gw-..." \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek/deepseek-v4-flash",
"provider": {"order": ["deepinfra"], "allow_fallbacks": false},
"messages": [
{"role": "system", "content": "Длинная стабильная инструкция..."},
{"role": "user", "content": "Вопрос"}
]
}'
```
* `order` — список провайдеров в порядке предпочтения. Имя — в нижнем регистре, без пробелов (провайдер `DeepInfra` → `deepinfra`); список провайдеров модели есть в её карточке в [каталоге](/models).
* `allow_fallbacks: false` — не переключаться на других провайдеров. Надёжно для кеша, но если выбранный провайдер недоступен, запрос вернёт ошибку вместо ответа от другого. Без этого флага `order` задаёт приоритет, но при недоступности запрос уйдёт к следующему доступному.
Живой пример: два одинаковых запроса к `deepseek/deepseek-v4-flash` (промпт 351 токен) с закреплённым `deepinfra` — первый вернул `cached_tokens: 64`, повторный — `cached_tokens: 256`. Тот же тест без закрепления провайдера легко даёт 0: запросы разлетаются по разным дата-центрам.
И общие правила, независимо от провайдера:
* **Стабильное начало.** Системная инструкция и инструменты — в начале и без изменений, всё переменное (данные пользователя, текущая дата) — в конце.
* **Частота.** Серии запросов подряд кешируются отлично, одиночные редкие — нет.
* **Длина.** Чем длиннее общий префикс, тем больше экономия.
## Явное кеширование (Anthropic)
Claude-модели кешируют только помеченные блоки. Поставьте `cache_control` на content-блок, который хотите закешировать:
```bash
curl -s https://api.hubris.pw/v1/chat/completions \
-H "Authorization: Bearer sk-gw-..." \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-haiku-4.5",
"messages": [
{
"role": "system",
"content": [
{
"type": "text",
"text": "Очень длинная инструкция на 5000 токенов...",
"cache_control": {"type": "ephemeral"}
}
]
},
{
"role": "user",
"content": "Ваш вопрос"
}
]
}'
```
Требования Anthropic: блок от 1024 токенов, время жизни кеша \~5 минут (продлевается при каждом попадании). Чтение из кеша заметно дешевле обычного входа, запись в кеш — немного дороже (примерно +25 %), поэтому кешировать имеет смысл то, что будет переиспользовано.
## Кеш в статистике
В ответе `GET /v1/usage` и в кабинете (вкладка «Активность») видна сводка по кешу за период:
| Поле | Описание |
| -------------------- | ----------------------------------------------------------------- |
| `cache_read_tokens` | Токены, прочитанные из кеша. |
| `cache_write_tokens` | Токены, записанные в кеш (у моделей с явным кешированием). |
| `cache_hit_rate` | Доля входных токенов, пришедшихся на кеш (0..1). |
| `cache_savings_rub` | Сколько сэкономлено за период относительно полной входной ставки. |
## Что кешировать
Эффективные кандидаты:
* **Системная инструкция с правилами и примерами** (1–20 тыс. токенов) — основная экономия.
* **RAG-контекст** — одни и те же документы во многих запросах подряд.
* **История долгого диалога** — каждый следующий ход переиспользует общий префикс.
* **Длинный few-shot пример** — для классификаторов и агентов.
Не даст экономии:
* Уникальные короткие запросы без общего начала.
* Промпты меньше минимального порога кеширования.
* Редкие запросы — кеш истекает между ними.
## Что дальше
* [POST /v1/chat/completions](/docs/api/chat-completions) — полная схема запроса.
* [Каталог моделей](/models) — провайдеры и цены каждой модели, включая ставку кеш-чтения.
* [Отслеживание расходов](/docs/features/usage-tracking) — как смотреть статистику.
---
# Reasoning токены (/docs/features/reasoning)
> Контроль над reasoning-моделями (o1, R1, Claude thinking) через параметр reasoning_effort.
Серия reasoning-моделей (OpenAI o1, DeepSeek R1, Claude с включённым thinking) перед основным ответом тратит токены на «внутреннее размышление». Эти reasoning-токены не показываются в `choices[0].message.content`, но учитываются в счёте.
## Когда использовать
Reasoning-модели лучше справляются с задачами, требующими многошагового рассуждения:
* Математика, логика, доказательства.
* Анализ кода и поиск багов.
* Сложные стратегические решения с несколькими переменными.
* Юридические и научные тексты с цепочкой выводов.
Для коротких ответов (классификация, переформулирование, простые вопросы) reasoning-модели избыточны: тратят больше токенов и дают тот же результат, что и обычные.
## Управление
Передайте параметр `reasoning_effort` в запросе:
```bash
curl -s https://api.hubris.pw/v1/chat/completions \
-H "Authorization: Bearer sk-gw-..." \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-4o-mini",
"messages": [{"role": "user", "content": "Если есть число, простое и сумма цифр которого тоже простое — какое наименьшее?"}],
"reasoning_effort": "high"
}'
```
Допустимые значения:
* `"low"` — минимум reasoning, быстрее и дешевле.
* `"medium"` — баланс (default для большинства reasoning-моделей).
* `"high"` — максимум, для сложных задач.
Не все модели поддерживают `reasoning_effort` — если модель его не понимает, апстрим вернёт ошибку или просто проигнорирует.
## Биллинг
Reasoning-токены входят в `completion_tokens` и тарифицируются по обычной цене выходных токенов модели:
```json
{
"usage": {
"prompt_tokens": 50,
"completion_tokens": 1200,
"total_tokens": 1250,
"completion_tokens_details": {
"reasoning_tokens": 1100
}
}
}
```
В примере выше из 1200 completion-токенов 1100 ушли на reasoning, и только 100 — на финальный ответ. Это нормально для сложных задач: модель «обдумывает» дольше, чем пишет.
Поле `completion_tokens_details.reasoning_tokens` информативное — показывает, сколько именно ушло на размышления. Денежная стоимость такая же, как для обычных completion-токенов.
## Как читать ответ
Сам процесс рассуждения скрыт — его не видно в `message.content`. Видно только финальный ответ:
```json
{
"choices": [{
"index": 0,
"message": {
"role": "assistant",
"content": "Ответ: 23 (простое, сумма цифр 5 — простое)"
},
"finish_reason": "stop"
}]
}
```
У некоторых моделей в перспективе появится поле `reasoning` рядом с `content` — мы пробросим его как есть от провайдера, без нормализации.
## Стриминг
Стриминг работает как обычно: chunk-и приходят по мере генерации финального ответа. Reasoning-фаза происходит ДО первого chunk-а — значит, для задач с высоким `reasoning_effort` первый chunk может прийти через 10–30 секунд молчания, а не сразу.
## Что дальше
* [POST /v1/chat/completions](/docs/api/chat-completions) — полная схема параметров.
* [Цены](/docs/concepts/pricing) — формула счёта.
---
# Стриминг ответов (/docs/features/streaming)
> Когда включать `stream: true`, как корректно собирать ответ модели и показывать прогресс пользователю.
При `stream: true` ответ модели приходит по частям через Server-Sent Events: вы видите текст по мере того, как он генерируется. Это даёт ощущение «живого» интерфейса: пользователь сразу видит первые слова и не ждёт полной завершённости запроса.
Технический формат SSE-стрима (chunk-структура, `[DONE]` маркер, заголовки) — на странице [POST /v1/chat/completions: Стриминг](/docs/api/streaming). Этот гид — про use-cases и практику использования в клиенте.
## Когда включать стриминг
Подходящие сценарии:
* **Чат-интерфейсы** — пользователь видит ответ по мере появления, как в ChatGPT.
* **Длинные ответы** (reasoning-модели, агенты) — без стрима пользователь смотрит 10–30 секунд в крутилку.
* **Прогресс-индикация** — показать, что модель уже отвечает (даже если первые слова — это reasoning-токены).
Не нужен:
* **Однострочные ответы** — классификация, экстракция данных, structured output. Накладные расходы на SSE-парсинг не оправданы.
* **Batch-сценарии** — обработка тысяч запросов в parallelизме, где UI не показывается.
* **Когда нужен сразу полный JSON для парсинга** — собирать stream и парсить «накопленный буфер» работает, но проще получить готовый ответ одним запросом.
## Python (синхронный)
OpenAI SDK скрывает работу с SSE — итерация по объекту `Stream` отдаёт уже распарсенные чанки.
```python
from openai import OpenAI
client = OpenAI(
base_url="https://api.hubris.pw/v1",
api_key="sk-gw-...",
)
stream = client.chat.completions.create(
model="anthropic/claude-haiku-4.5",
messages=[{"role": "user", "content": "Расскажи о трёх океанах планеты."}],
stream=True,
)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)
# В последнем чанке Hubris отдаёт usage с итоговыми токенами:
# (доступен через stream.response.usage после завершения итерации)
```
Итерация автоматически останавливается на `[DONE]`. SDK обрабатывает чанк-буфер за вас — рисковать с ручным парсингом не нужно.
## Python (асинхронный)
```python
from openai import AsyncOpenAI
client = AsyncOpenAI(
base_url="https://api.hubris.pw/v1",
api_key="sk-gw-...",
)
async def stream_answer(prompt: str):
stream = await client.chat.completions.create(
model="anthropic/claude-haiku-4.5",
messages=[{"role": "user", "content": prompt}],
stream=True,
)
async for chunk in stream:
delta = chunk.choices[0].delta.content if chunk.choices else None
if delta:
yield delta
```
Удобно для FastAPI / aiohttp-эндпоинтов, которые транслируют ответ дальше клиенту.
## Node.js / TypeScript
```ts
import OpenAI from 'openai';
const client = new OpenAI({
baseURL: 'https://api.hubris.pw/v1',
apiKey: 'sk-gw-...',
});
const stream = await client.chat.completions.create({
model: 'anthropic/claude-haiku-4.5',
messages: [{ role: 'user', content: 'Опиши закат.' }],
stream: true,
});
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta?.content;
if (delta) process.stdout.write(delta);
}
```
`for await...of` итерирует асинхронно — каждый цикл это уже распарсенный chunk.
## Vercel AI SDK
Если вы пишете на Next.js и используете Vercel AI SDK — он работает с Hubris из коробки:
```ts
import { openai } from '@ai-sdk/openai';
import { streamText } from 'ai';
const result = await streamText({
model: openai.chat('anthropic/claude-haiku-4.5'),
messages: [{ role: 'user', content: 'Hello' }],
});
return result.toAIStreamResponse();
```
Конфигурация base URL — через переменную окружения `OPENAI_BASE_URL=https://api.hubris.pw/v1`, либо явно в `openai({ baseURL: ... })`. См. гид [Vercel AI SDK](/docs/frameworks/vercel-ai-sdk).
## Браузер (fetch + ReadableStream)
Если SDK для вас избыточен — можно стримить голыми руками:
```js
const resp = await fetch('https://api.hubris.pw/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': 'Bearer sk-gw-...',
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'anthropic/claude-haiku-4.5',
messages: [{ role: 'user', content: 'Привет' }],
stream: true,
}),
});
const reader = resp.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
// Делим буфер по \n\n — каждый блок это SSE-событие
const events = buffer.split('\n\n');
buffer = events.pop() ?? ''; // последний может быть неполным — оставляем
for (const event of events) {
if (!event.startsWith('data: ')) continue;
const payload = event.slice('data: '.length);
if (payload === '[DONE]') return;
const chunk = JSON.parse(payload);
const delta = chunk.choices[0]?.delta?.content;
if (delta) appendToUI(delta);
}
}
```
Ключевые правила:
* **Буфер обязателен.** TCP может разбить SSE-блок на середине — не делайте `JSON.parse` сразу.
* **`[DONE]` не парсить как JSON.** Это литерал-маркер.
* **Используйте `decoder.decode(value, { stream: true })`** — чтобы UTF-8 multi-byte символы не ломались на границе чанков.
## Cancellation (отмена запроса)
Стандартный `AbortController` работает:
```ts
const ac = new AbortController();
const stream = await client.chat.completions.create(
{
model: 'anthropic/claude-haiku-4.5',
messages: [{ role: 'user', content: '...' }],
stream: true,
},
{ signal: ac.signal },
);
// Через 5 секунд отменяем
setTimeout(() => ac.abort(), 5000);
try {
for await (const chunk of stream) {
// ...
}
} catch (e) {
if (e.name === 'AbortError') {
console.log('Запрос отменён пользователем');
}
}
```
**Важно:** Hubris продолжает читать ответ модели до конца, даже если клиент закрыл соединение. Это защита от «бесплатных токенов». Иными словами, при отмене вы получите полное списание стоимости — токены уже сгенерированы апстримом, мы просто не доставили их вам.
## Прогресс в UI: типичные паттерны
### Типографический эффект (как в ChatGPT)
```ts
let buffer = '';
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta?.content ?? '';
buffer += delta;
setMessage(buffer); // React state setter
}
```
### Индикатор «модель думает»
Reasoning-модели могут несколько секунд молчать перед первым content-чанком (см. [Reasoning токены](/docs/features/reasoning)). Покажите spinner, пока `delta.content` не пришёл хотя бы один раз:
```ts
let firstChunk = true;
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta?.content;
if (!delta) continue;
if (firstChunk) {
hideSpinner();
firstChunk = false;
}
appendText(delta);
}
```
### Кнопка «Stop» с откатом UI
При отмене иногда нужно показать частичный ответ + label «прервано». Сохраняйте накопленный буфер в state и при `AbortError` дополняйте его пометкой — не очищайте.
## Tool calls и стриминг
Если модель вызывает функцию, `delta.tool_calls[]` приходит частями (имя функции и аргументы могут разорваться по чанкам). Собирайте по `index`. Подробности — в гиде [Вызов инструментов](/docs/features/tool-calling#стриминг).
## Биллинг
Стриминг не меняет тарификацию: списываем по тем же prompt + completion токенам, что и в обычном (не-стрим) запросе. Hubris всегда добавляет `stream_options: { include_usage: true }` к стрим-запросам, поэтому в последнем чанке вы видите итоговые токены.
Дисконнект клиента до завершения стрима — полное списание (см. выше). Это не баг, это анти-абьюз.
## Что важно знать
* **OpenAI SDK почти всегда правильный выбор.** Ручной SSE-парсинг — только если есть конкретная причина (другой стек, экстремальная оптимизация).
* **Чанк-границы непредсказуемы.** Не пытайтесь парсить `delta.content` как JSON — это просто кусок строки.
* **Headers commit before bytes.** Если стрим уже начался — мы уже отдали `200 OK` и SSE-заголовки. Ошибки ПОСЛЕ старта стрима не возвращаются как HTTP 5xx — клиент видит обрыв или `finish_reason: "stop"` на пустом delta.
## Что дальше
* [POST /v1/chat/completions: Стриминг](/docs/api/streaming) — технический формат чанков.
* [Вызов инструментов](/docs/features/tool-calling) — стрим с tool calls.
* [Reasoning токены](/docs/features/reasoning) — почему первый чанк может прийти не сразу.
* [Vercel AI SDK](/docs/frameworks/vercel-ai-sdk) — React/Next-стрим из коробки.
---
# Структурированный вывод (/docs/features/structured-output)
> Получение ответа строго в формате JSON по заданной схеме — без парсинга текста и без шанса получить «почти-JSON».
Структурированный вывод гарантирует, что модель вернёт валидный JSON и, при необходимости, соответствующий конкретной JSON Schema. Это убирает целый класс ошибок: «модель прислала JSON с лишним текстом», «закрывающая скобка съехала», «вместо числа — строка с числом».
Поддерживается большинством современных моделей. Жёсткое следование схеме (`strict: true`) — у OpenAI gpt-4o и новее, Claude 3.5 Sonnet и новее, Gemini 2.0+. Карточка модели в [каталоге](/models) подскажет.
## Два режима
Параметр `response_format` принимает один из трёх вариантов:
| Тип | Что гарантирует |
| -------------------------------- | ----------------------------------------------------------------- |
| `{ "type": "text" }` | Свободный текст. То же, что не передавать `response_format` вовсе |
| `{ "type": "json_object" }` | Ответ — синтаксически валидный JSON. Структура произвольная |
| `{ "type": "json_schema", ... }` | Ответ — JSON, соответствующий вашей схеме |
Режим `json_object` достаточен, если вы сами в системном промпте описали ожидаемые поля. Режим `json_schema` — строже: апстрим валидирует ответ модели и при `strict: true` гарантирует точное соответствие схеме.
## json\_object — простой случай
```bash
curl -s https://api.hubris.pw/v1/chat/completions \
-H "Authorization: Bearer sk-gw-..." \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-4o-mini",
"messages": [
{"role": "system", "content": "Верни поля: name (строка), age (число)."},
{"role": "user", "content": "Меня зовут Алексей, мне 34 года."}
],
"response_format": { "type": "json_object" }
}'
```
Ответ — гарантированно валидный JSON, но конкретные поля зависят от модели:
```json
{
"choices": [{
"message": {
"role": "assistant",
"content": "{\"name\": \"Алексей\", \"age\": 34}"
}
}]
}
```
Контент по-прежнему приходит строкой — `JSON.parse(...)` обязателен.
## json\_schema — строгий режим
Передайте схему в `response_format.json_schema`. Поле `strict: true` включает жёсткую валидацию: апстрим заранее ограничивает токены, которые модель может выдать, чтобы получить именно соответствующий вашей схеме JSON.
```bash
curl -s https://api.hubris.pw/v1/chat/completions \
-H "Authorization: Bearer sk-gw-..." \
-d '{
"model": "openai/gpt-4o-mini",
"messages": [
{"role": "user", "content": "Извлеки данные из квитанции: пицца 750₽, кола 150₽, чаевые 100₽."}
],
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "receipt",
"strict": true,
"schema": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {"type": "string"},
"price_rub": {"type": "number"}
},
"required": ["name", "price_rub"],
"additionalProperties": false
}
},
"tip_rub": {"type": "number"},
"total_rub": {"type": "number"}
},
"required": ["items", "tip_rub", "total_rub"],
"additionalProperties": false
}
}
}
}'
```
Ответ:
```json
{
"choices": [{
"message": {
"content": "{\"items\":[{\"name\":\"пицца\",\"price_rub\":750},{\"name\":\"кола\",\"price_rub\":150}],\"tip_rub\":100,\"total_rub\":1000}"
},
"finish_reason": "stop"
}]
}
```
### Требования к схеме при `strict: true`
* Корневой объект — обязательно `"type": "object"`.
* Все поля, которые могут присутствовать в ответе, должны быть в `required[]`. Опциональные поля нужно делать union с `null`: `{"type": ["string", "null"]}`.
* На каждом уровне объекта обязателен `"additionalProperties": false`.
* Нельзя использовать `oneOf`, `allOf`, `not`, регулярные выражения в pattern, рекурсивные ссылки.
Если схема не проходит этим требованиям — апстрим вернёт ошибку валидации до старта генерации.
## OpenAI SDK
**Python (`pydantic` + helper `parse`):**
```python
from openai import OpenAI
from pydantic import BaseModel
client = OpenAI(
base_url="https://api.hubris.pw/v1",
api_key="sk-gw-...",
)
class ReceiptItem(BaseModel):
name: str
price_rub: float
class Receipt(BaseModel):
items: list[ReceiptItem]
tip_rub: float
total_rub: float
resp = client.chat.completions.parse(
model="openai/gpt-4o-mini",
messages=[{"role": "user", "content": "Пицца 750, кола 150, чаевые 100."}],
response_format=Receipt,
)
receipt = resp.choices[0].message.parsed
print(receipt.total_rub) # 1000.0
```
**TypeScript (`zod` + helper `parse`):**
```ts
import OpenAI from 'openai';
import { z } from 'zod';
import { zodResponseFormat } from 'openai/helpers/zod';
const client = new OpenAI({
baseURL: 'https://api.hubris.pw/v1',
apiKey: 'sk-gw-...',
});
const Receipt = z.object({
items: z.array(z.object({
name: z.string(),
price_rub: z.number(),
})),
tip_rub: z.number(),
total_rub: z.number(),
});
const resp = await client.chat.completions.parse({
model: 'openai/gpt-4o-mini',
messages: [{ role: 'user', content: 'Пицца 750, кола 150, чаевые 100.' }],
response_format: zodResponseFormat(Receipt, 'receipt'),
});
const receipt = resp.choices[0].message.parsed;
console.log(receipt?.total_rub); // 1000
```
Хелперы `parse` сами добавят правильный `response_format` и при успешном ответе вернут уже распарсенный и провалидированный объект в `.parsed`.
## Что делать с отказами
Если модель решит, что задача нерешаема (например, в квитанции совсем нет цен), она вернёт `refusal` вместо контента:
```json
{
"choices": [{
"message": {
"role": "assistant",
"content": null,
"refusal": "Не могу извлечь цены — в тексте нет числовых данных."
},
"finish_reason": "stop"
}]
}
```
В коде проверяйте оба поля — иначе при `refusal` ваш `JSON.parse(content)` упадёт на `null`.
## Стриминг
`stream: true` работает. Чанки в `delta.content` приходят кусками всё ещё валидной JSON-строки — собирайте, как обычный текст, парсите финал. Для прогрессивного парсинга (показывать ответ по мере прихода) понадобится партиальный JSON-парсер на клиенте.
## Биллинг
Структурированный вывод не имеет отдельного тарифа. Платите за обычные prompt- и completion-токены:
* Описание схемы попадает в prompt-токены каждого запроса.
* Финальный JSON-ответ — в completion-токенах.
Длинные схемы (десятки полей) могут заметно раздувать prompt-расход — учитывайте при тарификации.
## Что дальше
* [Вызов инструментов](/docs/features/tool-calling) — если вместо одного JSON-ответа нужно вызвать функцию и продолжить диалог.
* [POST /v1/chat/completions](/docs/api/chat-completions) — полная схема параметров.
* [Каталог моделей](/models) — поиск моделей с поддержкой `json_schema`.
---
# Вызов инструментов (tool calling) (/docs/features/tool-calling)
> Подключение функций к моделям — модель просит вызвать вашу функцию, вы исполняете её локально, отдаёте результат и получаете финальный ответ.
Tool calling позволяет модели запросить у вас выполнение функции (получить погоду, прочитать БД, отправить письмо), получить результат и продолжить диалог с учётом этого результата. Сама модель не исполняет код — она только описывает, что и с какими аргументами надо вызвать. Исполняет — ваш клиент.
Поддерживается большинством современных моделей: Claude (Sonnet, Opus, Haiku), GPT-4o и новее, Gemini Pro/Flash, Llama 3.1+, Mistral Large. Конкретная карточка модели в [каталоге](/models) показывает поддержку.
## Как это работает
Жизненный цикл одного «раунда» вызова инструмента:
1. Вы шлёте запрос с `messages` и `tools` (описанием доступных функций).
2. Модель решает: ответить текстом или попросить вызвать функцию. В случае второго — возвращает `finish_reason: "tool_calls"` и массив `tool_calls[]` в assistant-сообщении.
3. Вы локально исполняете эти функции, формируете ответы.
4. Шлёте новый запрос: добавляете assistant-сообщение с `tool_calls` и одно или несколько `role: "tool"` сообщений с результатами.
5. Модель формирует финальный ответ для пользователя.
При параллельных вызовах модель может попросить вызвать несколько функций за один шаг.
## Минимальный пример
Шаг 1 — запрос с описанием функции:
```bash
curl -s https://api.hubris.pw/v1/chat/completions \
-H "Authorization: Bearer sk-gw-..." \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-haiku-4.5",
"messages": [
{"role": "user", "content": "Какая погода в Москве?"}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Возвращает текущую погоду в указанном городе.",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "Название города"},
"units": {"type": "string", "enum": ["celsius", "fahrenheit"]}
},
"required": ["city"]
}
}
}
]
}'
```
Ответ модели — она просит вызвать `get_weather`:
```json
{
"id": "chatcmpl-...",
"choices": [{
"index": 0,
"message": {
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"Москва\",\"units\":\"celsius\"}"
}
}
]
},
"finish_reason": "tool_calls"
}]
}
```
Шаг 2 — исполняете функцию у себя и шлёте результат. Важно: сохраните `id` вызова из `tool_calls[].id` и проставьте его в `tool_call_id`.
```bash
curl -s https://api.hubris.pw/v1/chat/completions \
-H "Authorization: Bearer sk-gw-..." \
-d '{
"model": "anthropic/claude-haiku-4.5",
"messages": [
{"role": "user", "content": "Какая погода в Москве?"},
{
"role": "assistant",
"content": null,
"tool_calls": [{
"id": "call_abc123",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"Москва\",\"units\":\"celsius\"}"
}
}]
},
{
"role": "tool",
"tool_call_id": "call_abc123",
"content": "{\"temperature\": -3, \"conditions\": \"снег\"}"
}
],
"tools": [ /* те же tools, что и в первом запросе */ ]
}'
```
В assistant-сообщении истории поле `content` может быть строкой, `null` или массивом текстовых блоков (`[{"type": "text", "text": "..."}]`) — как у OpenAI. Пустой массив `[]` (его шлёт, например, n8n начиная с версии 2.31) тоже принимается и трактуется как `null`.
Модель ответит текстом с учётом результата:
```json
{
"choices": [{
"message": {
"role": "assistant",
"content": "В Москве сейчас −3 °C и идёт снег. Одевайтесь теплее."
},
"finish_reason": "stop"
}]
}
```
## `tool_choice` — как заставить или запретить вызовы
По умолчанию (`auto`) модель сама решает, нужен ли инструмент. Можно переопределить:
| Значение | Эффект |
| ----------------------------------------------------- | ----------------------------------------------- |
| `"auto"` (по умолчанию) | Модель сама решает: текст или вызов |
| `"none"` | Запретить вызовы, модель ответит только текстом |
| `"required"` | Заставить вызвать какой-нибудь инструмент |
| `{ "type": "function", "function": { "name": "X" } }` | Заставить вызвать именно функцию `X` |
```json
"tool_choice": { "type": "function", "function": { "name": "get_weather" } }
```
## Параллельные вызовы
Если в одном шаге модель решит вызвать несколько функций сразу (например, погоду в трёх городах) — в `tool_calls[]` придёт несколько элементов. Вы исполняете их параллельно, шлёте обратно столько же `role: "tool"` сообщений с соответствующими `tool_call_id`.
Отключить параллельные вызовы (модель будет звать функции по одной):
```json
"parallel_tool_calls": false
```
## Стриминг
При `stream: true` поля `tool_calls` приходят в `delta.tool_calls[]` по частям — имя функции и аргументы могут разбиться на несколько чанков. Собирайте по `index`:
```json
data: {"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"id":"call_abc","type":"function","function":{"name":"get_weather"}}]}}]}
data: {"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\"city\":"}}]}}]}
data: {"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"Москва\"}"}}]}}]}
data: {"choices":[{"index":0,"finish_reason":"tool_calls"}]}
```
Финальный чанк с `finish_reason: "tool_calls"` означает, что модель закончила формировать вызов и ждёт результата.
## OpenAI SDK
Официальные SDK OpenAI поддерживают tools «как есть».
**Python:**
```python
from openai import OpenAI
client = OpenAI(
base_url="https://api.hubris.pw/v1",
api_key="sk-gw-...",
)
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Возвращает текущую погоду в указанном городе.",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string"},
"units": {"type": "string", "enum": ["celsius", "fahrenheit"]},
},
"required": ["city"],
},
},
}]
resp = client.chat.completions.create(
model="anthropic/claude-haiku-4.5",
messages=[{"role": "user", "content": "Какая погода в Москве?"}],
tools=tools,
)
msg = resp.choices[0].message
if msg.tool_calls:
for call in msg.tool_calls:
args = json.loads(call.function.arguments)
result = get_weather(**args) # ваша локальная функция
# ... добавьте assistant + tool сообщения и сделайте второй вызов
```
**TypeScript:**
```ts
import OpenAI from 'openai';
const client = new OpenAI({
baseURL: 'https://api.hubris.pw/v1',
apiKey: 'sk-gw-...',
});
const resp = await client.chat.completions.create({
model: 'anthropic/claude-haiku-4.5',
messages: [{ role: 'user', content: 'Какая погода в Москве?' }],
tools: [
{
type: 'function',
function: {
name: 'get_weather',
description: 'Возвращает текущую погоду в указанном городе.',
parameters: {
type: 'object',
properties: {
city: { type: 'string' },
units: { type: 'string', enum: ['celsius', 'fahrenheit'] },
},
required: ['city'],
},
},
},
],
});
const msg = resp.choices[0].message;
if (msg.tool_calls?.length) {
for (const call of msg.tool_calls) {
const args = JSON.parse(call.function.arguments);
const result = await getWeather(args);
// ... добавьте assistant + tool сообщения и сделайте второй вызов
}
}
```
## Биллинг
Tool-calling не имеет отдельного тарифа. Списываются обычные prompt- и completion-токены за каждый шаг диалога:
* Шаг 1 (модель просит вызвать функцию) — `prompt_tokens` за описание инструментов и сообщения, `completion_tokens` за сгенерированный `tool_calls[]`.
* Шаг 2 (вы шлёте результат) — `prompt_tokens` за весь диалог (включая `role: "tool"` сообщения), `completion_tokens` за финальный ответ.
Описания инструментов считаются как часть промпта на каждом шаге, где они переданы. Если у вас 10 функций по 200 токенов каждая — это +2000 prompt-токенов за вызов.
## Что дальше
* [POST /v1/chat/completions](/docs/api/chat-completions) — полная схема параметров и ответа.
* [Структурированный вывод](/docs/features/structured-output) — если вам нужен не вызов функции, а строго типизированный JSON-ответ.
* [Поиск в интернете](/docs/features/web-search) — встроенный server-side инструмент, который мы исполняем за вас.
* [Каталог моделей](/models) — какие модели поддерживают tool-calling.
---
# Мониторинг расходов (/docs/features/usage-tracking)
> Программный учёт трат через GET /v1/usage — отслеживание расхода по ключу, бюджет-алёрты, отчёты.
Hubris отдаёт расход по API-ключу через `GET /v1/usage` — отдельные запросы и агрегированные суммы. Используется для автоматического мониторинга: алёрты по порогам, отчёты, экспорт в свою аналитику.
Endpoint-референс — на [GET /v1/usage](/docs/api/usage). Этот гид — про практические паттерны.
## Базовый запрос
```bash
curl -s -H "Authorization: Bearer $HUBRIS_API_KEY" \
https://api.hubris.pw/v1/usage
```
По умолчанию — расход за последние 24 часа по тому ключу, которым сделан запрос. Ответ:
```json
{
"object": "usage",
"period": { "from": "2026-05-21T00:00Z", "to": "2026-05-22T00:00Z" },
"scope": { "key_id": "...", "key_prefix": "sk-gw-758f...d4e3" },
"totals": {
"requests": 142,
"prompt_tokens": 18500,
"completion_tokens": 3200,
"total_tokens": 21700,
"cost_rub": 187.45,
"cost_kopecks": "18745"
},
"granularity": null,
"buckets": null
}
```
**Важно:** виден расход **только этого ключа**, а не всего аккаунта. Это намеренно — скомпрометированный ключ не должен раскрывать общий бюджет.
## Паттерн 1: ежедневный отчёт
Cron-задача, которая раз в день берёт суммарный расход за вчера и шлёт письмо/слак/телеграм:
```python
import os
import requests
from datetime import datetime, timedelta, timezone
key = os.environ["HUBRIS_API_KEY"]
yesterday = datetime.now(timezone.utc) - timedelta(days=1)
day_start = yesterday.replace(hour=0, minute=0, second=0, microsecond=0)
day_end = day_start + timedelta(days=1)
resp = requests.get(
"https://api.hubris.pw/v1/usage",
params={"from": day_start.isoformat(), "to": day_end.isoformat()},
headers={"Authorization": f"Bearer {key}"},
).json()
totals = resp["totals"]
print(f"Вчера: {totals['requests']} запросов, {totals['cost_rub']} ₽")
# Отправить в Slack, если расход за день выше нормы:
if float(totals["cost_rub"]) > 500:
notify_slack(f"⚠️ Вчера ушло {totals['cost_rub']} ₽")
```
## Паттерн 2: бюджет-алёрт после каждого запроса
Если у вас агент, который может за сессию «уйти в космос» — проверяйте расход после каждого запроса и останавливайтесь при превышении лимита:
```python
DAILY_BUDGET_RUB = 1000
def check_budget():
resp = requests.get(
"https://api.hubris.pw/v1/usage",
params={"period": "today"},
headers={"Authorization": f"Bearer {key}"},
).json()
return float(resp["totals"]["cost_rub"])
# В цикле агента
while task_not_done:
if check_budget() > DAILY_BUDGET_RUB:
raise BudgetExceeded(f"Превышен дневной лимит {DAILY_BUDGET_RUB} ₽")
response = run_step()
process(response)
```
В большинстве сценариев лучше использовать **встроенный дневной лимит ключа** (см. [/keys](/keys)) — он гарантированно прерывает на стороне сервера и возвращает `429 daily_limit_exceeded`. Бюджет в коде — мягкая защита поверх него, для случаев когда нужен ранний алёрт «потратили 80% бюджета — переключаемся на дешёвую модель».
## Паттерн 3: график по дням
Для дашборда — берите данные с почасовой/посуточной разбивкой:
```bash
curl -s -H "Authorization: Bearer $HUBRIS_API_KEY" \
"https://api.hubris.pw/v1/usage?period=30d&granularity=day"
```
В ответе появится массив `buckets`:
```json
{
"totals": { "cost_rub": 5432.10, ... },
"granularity": "day",
"buckets": [
{ "bucket": "2026-04-22T00:00:00Z", "cost_rub": 180.30, "requests": 142 },
{ "bucket": "2026-04-23T00:00:00Z", "cost_rub": 213.45, "requests": 167 },
{ "bucket": "2026-04-24T00:00:00Z", "cost_rub": 0, "requests": 0 }
]
}
```
Дни без запросов всё равно присутствуют (с `cost_rub: 0`) — удобно для линейного графика без пропусков.
## Паттерн 4: разделение по проектам
Один аккаунт — несколько ключей под разные проекты. Дашборд `/usage` агрегирует общий расход; API даёт расход **только запрашиваемого ключа**.
Чтобы получить разбивку по проектам — храните ключи (или их `key_id`) на стороне ваших систем и запрашивайте `/v1/usage` каждым ключом отдельно:
```python
PROJECT_KEYS = {
"production": os.environ["HUBRIS_KEY_PROD"],
"staging": os.environ["HUBRIS_KEY_STG"],
"research": os.environ["HUBRIS_KEY_RES"],
}
for project, key in PROJECT_KEYS.items():
resp = requests.get(
"https://api.hubris.pw/v1/usage",
params={"period": "7d"},
headers={"Authorization": f"Bearer {key}"},
).json()
print(f"{project}: {resp['totals']['cost_rub']} ₽ / {resp['totals']['requests']} req")
```
## TypeScript
```ts
type UsageResp = {
totals: {
requests: number;
prompt_tokens: number;
completion_tokens: number;
total_tokens: number;
cache_read_tokens: number;
cache_write_tokens: number;
reasoning_tokens: number;
cache_hit_rate: number;
cache_savings_kopecks: string;
cache_savings_rub: number;
cost_rub: number;
cost_kopecks: string;
};
buckets: Array<{
bucket: string;
cost_rub: number;
cost_kopecks: string;
requests: number;
}> | null;
};
async function fetchUsage(period = '7d', granularity?: 'hour' | 'day') {
const url = new URL('https://api.hubris.pw/v1/usage');
url.searchParams.set('period', period);
if (granularity) url.searchParams.set('granularity', granularity);
const resp = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.HUBRIS_API_KEY}` },
});
return (await resp.json()) as UsageResp;
}
const week = await fetchUsage('7d', 'day');
console.log(`За 7 дней: ${week.totals.cost_rub} ₽`);
for (const day of week.buckets ?? []) {
console.log(` ${day.bucket}: ${day.cost_rub} ₽`);
}
```
## Точность сумм при больших расходах
`cost_kopecks` приходит как строка — чтобы при сериализации в JavaScript не терялась точность на больших суммах. Если вам важна копейка-в-копейку точность (бухучёт, биллинг клиентов поверх Hubris) — парсите `cost_kopecks` через `BigInt`, не через `Number`:
```ts
const kopecks = BigInt(resp.totals.cost_kopecks);
const rubFormatted = (Number(kopecks) / 100).toFixed(2);
```
Поле `cost_rub` — это уже `cost_kopecks / 100`, удобно для отображения, но не для арифметики.
## Часовые пояса
Время в API — UTC. Если нужно «расход за сегодня по Москве» — конвертируйте границы суток у себя и передавайте явные `from` / `to` (ISO 8601 UTC):
```python
from datetime import datetime, timezone, timedelta
MSK = timezone(timedelta(hours=3))
now_msk = datetime.now(MSK)
day_start_msk = now_msk.replace(hour=0, minute=0, second=0, microsecond=0)
day_end_msk = day_start_msk + timedelta(days=1)
resp = requests.get(
"https://api.hubris.pw/v1/usage",
params={
"from": day_start_msk.astimezone(timezone.utc).isoformat(),
"to": day_end_msk.astimezone(timezone.utc).isoformat(),
},
headers={"Authorization": f"Bearer {key}"},
).json()
```
## Дашбордная альтернатива
Если ручной мониторинг — overkill, на странице [/usage](/usage) дашборда есть готовые фильтры по модели, ключу и статусу, графики по дням и CSV-экспорт. Многие сценарии решаются прямо в UI без написания кода.
## Что важно знать
* **Кешируйте.** Не дёргайте `/v1/usage` чаще, чем раз в минуту — для большинства сценариев достаточно раз в 5–10 минут. Избыточные запросы влияют только на ваш rate-budget.
* **Расход обновляется в течение нескольких секунд** после успешного списания. Свежий запрос → пара секунд → его стоимость в `/v1/usage`.
* **Запросы на free-модели** добавляются к `requests`, но не к `cost_rub`. Если у вас активны бесплатные модели, число запросов в дашборде будет выше, чем «сколько денег ушло».
* **Только успешные запросы** входят в totals. Запросы с ошибками (4xx/5xx) не списываются и не попадают в счётчик расхода.
## Что дальше
* [GET /v1/usage](/docs/api/usage) — полная схема параметров и ответа.
* [Управление ключами](/keys) — настроить дневной лимит на уровне ключа.
* [Биллинг](/docs/concepts/billing) — как формируется стоимость токенов.
* [/usage](/usage) — дашборд расходов с фильтрами и CSV-экспортом.
---
# Изображения на вход (Vision) (/docs/features/vision)
> Отправка изображений в модель для анализа — описание содержимого, OCR, классификация, ответы на вопросы по картинке.
Vision (мультимодальный ввод) — это когда модель получает на вход не только текст, но и изображения. Подходит для описания содержимого, OCR, классификации, ответов на вопросы по картинке, разбора скриншотов и диаграмм.
Не путайте с [генерацией изображений](/docs/features/image-generation): vision — это **картинка на вход**, image-generation — **картинка на выход**.
## Поддерживающие модели
Vision поддерживает любая модель, у которой в карточке (`GET /v1/models`) поле `input_modalities` содержит `"image"`. Это, как правило, актуальные версии Claude (Sonnet/Opus), GPT-4o, Gemini, а также часть open-source моделей (Llama Vision, Qwen-VL). Точный актуальный список — в [каталоге](/models) с фильтром «принимает изображения».
## Минимальный пример
Передайте картинку как content-part типа `image_url` внутри user-сообщения:
```bash
curl -s https://api.hubris.pw/v1/chat/completions \
-H "Authorization: Bearer sk-gw-..." \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-haiku-4.5",
"messages": [
{
"role": "user",
"content": [
{ "type": "text", "text": "Что изображено на картинке?" },
{
"type": "image_url",
"image_url": {
"url": "https://example.com/photo.jpg"
}
}
]
}
]
}'
```
Контент user-сообщения — это массив content-part'ов. Каждая часть — либо `{ "type": "text", "text": "..." }`, либо `{ "type": "image_url", "image_url": { "url": "..." } }`. Порядок свободный.
## Два способа передать картинку
### URL
```json
{
"type": "image_url",
"image_url": {
"url": "https://example.com/photo.jpg"
}
}
```
Должен быть публично-доступный HTTPS URL. Провайдер модели скачает картинку сам. Размер и формат — на усмотрение провайдера; типичный лимит 5–20 МБ, поддерживаются JPEG, PNG, WebP, GIF.
### Base64 data URL
Когда картинка не лежит в публичном интернете (генерится на лету, приходит от пользователя), кодируйте её в base64 и шлите как data URL:
```json
{
"type": "image_url",
"image_url": {
"url": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAAAAAAA..."
}
}
```
Формат — `data:image/;base64,`. Mime может быть `image/jpeg`, `image/png`, `image/webp`, `image/gif`. Base64-сам по себе делает строку на \~33 % длиннее, поэтому общий размер запроса может быть значительным — учитывайте при тарификации (см. ниже).
## Несколько изображений в одном запросе
Можно передать сколько угодно картинок в одном сообщении — добавляйте content-part'ы по одной:
```json
{
"role": "user",
"content": [
{ "type": "text", "text": "Сравни эти два скриншота интерфейса." },
{ "type": "image_url", "image_url": { "url": "https://.../before.png" } },
{ "type": "image_url", "image_url": { "url": "https://.../after.png" } }
]
}
```
Модель увидит обе картинки и сможет ссылаться на них в ответе («на первом скриншоте...»).
## Параметр `detail`
Часть моделей (gpt-4o-семейство) принимают подсказку о требуемой детализации:
```json
{
"type": "image_url",
"image_url": {
"url": "https://...",
"detail": "high"
}
}
```
| Значение | Что значит |
| ----------------------- | ------------------------------------------------------------------------------------ |
| `"low"` | модель смотрит на картинку в низком разрешении — дёшево, годится для общего описания |
| `"high"` | детальное чтение — мелкий текст, цифры, диаграммы |
| `"auto"` (по умолчанию) | модель решает сама |
Не все модели поддерживают `detail` — он пробрасывается как есть; если модель его не понимает, пара значение/поле просто игнорируется апстримом.
## OpenAI SDK
Vision работает в официальных SDK OpenAI без дополнительных манипуляций.
**Python:**
```python
from openai import OpenAI
client = OpenAI(
base_url="https://api.hubris.pw/v1",
api_key="sk-gw-...",
)
resp = client.chat.completions.create(
model="openai/gpt-4o-mini",
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": "Распознай текст на этом скриншоте."},
{
"type": "image_url",
"image_url": {
"url": "https://example.com/screenshot.png",
"detail": "high",
},
},
],
}
],
)
print(resp.choices[0].message.content)
```
**TypeScript:**
```ts
import OpenAI from 'openai';
const client = new OpenAI({
baseURL: 'https://api.hubris.pw/v1',
apiKey: 'sk-gw-...',
});
const resp = await client.chat.completions.create({
model: 'openai/gpt-4o-mini',
messages: [
{
role: 'user',
content: [
{ type: 'text', text: 'Опиши, что на фото.' },
{
type: 'image_url',
image_url: { url: 'https://example.com/photo.jpg' },
},
],
},
],
});
console.log(resp.choices[0].message.content);
```
### Base64 в Python из файла
```python
import base64
with open("photo.jpg", "rb") as f:
encoded = base64.b64encode(f.read()).decode()
data_url = f"data:image/jpeg;base64,{encoded}"
resp = client.chat.completions.create(
model="openai/gpt-4o-mini",
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": "Опиши фото."},
{"type": "image_url", "image_url": {"url": data_url}},
],
}
],
)
```
## Стриминг
`stream: true` работает с vision-запросами как с обычными — ответ модели приходит чанками, prompt с изображениями просто длиннее. Никаких особенностей.
## Биллинг
Изображение на вход тарифицируется как **дополнительные prompt-токены**. Количество токенов на одну картинку зависит от модели и от её размера/разрешения. Грубо:
* Маленькая картинка в `detail: "low"` — порядка сотни prompt-токенов.
* Стандартное разрешение в `detail: "auto"`/`"high"` — от нескольких сотен до нескольких тысяч prompt-токенов.
Точное количество видно в `usage.prompt_tokens` в ответе. Списание с баланса — стандартная формула: prompt + completion токены умножаются на цену модели из [каталога](/models).
Base64 в URL **не** добавляет токенов — это просто способ доставки байтов. Тарифицируется только распарсенная картинка, не размер JSON-запроса.
## Что важно знать
* **Большие картинки = большой prompt.** Если шлёте 2K-скриншоты, ожидайте по 1000–3000 prompt-токенов на каждую. На сессии с десятком картинок это заметные деньги.
* **Картинка в URL должна быть публично-доступной.** Авторизованные URL (`Authorization: ...` заголовок при загрузке) не подойдут — провайдер скачивает анонимно. Если картинка приватная — шлите как base64.
* **Vision и structured output совмещаются.** Можно передать картинку и попросить вернуть JSON по схеме — например, извлечь поля из чека.
* **Vision и tool calling тоже совмещаются.** Модель может смотреть на картинку и вызывать функции на основе того, что увидела.
* **IDE-агенты (Kilo Code, Roo Code, Cline) могут не отправлять картинку.** Такие расширения сами решают, поддерживает ли модель изображения, и для подключений типа «OpenAI Compatible» по умолчанию считают модель текстовой — тогда картинка не доходит до модели, даже если сама модель vision поддерживает. Лечится в настройках агента (в Kilo — поле `modalities` в `kilo.jsonc`, см. [Kilo Code](/docs/integrations/kilo-code)).
## Что дальше
* [Генерация изображений](/docs/features/image-generation) — обратная задача: получить картинку на выходе.
* [Структурированный вывод](/docs/features/structured-output) — извлечение данных из картинки в JSON-схему.
* [POST /v1/chat/completions](/docs/api/chat-completions) — полная схема параметров.
* [Каталог моделей](/models) — фильтр по поддержке Vision.
---
# Поиск в интернете (/docs/features/web-search)
> Server-side инструмент — модель сама ищет в интернете и пишет ответ с источниками. Никакого ручного исполнения у вас на стороне.
В отличие от обычных функций ([Вызов инструментов](/docs/features/tool-calling)), которые исполняете вы, веб-поиск — это **server-side инструмент**: вы добавляете его в `tools[]`, модель сама вызывает поиск, получает результаты, формирует ответ и присылает его одним финальным сообщением. Вашему клиенту ничего исполнять не нужно — никаких лишних round-trip'ов и `role: "tool"` сообщений.
Работает с большинством моделей. Под капотом наш слой маршрутизации выбирает поисковый движок — у части моделей он встроен в саму модель, у части подключается отдельно. Для вас разница только в наборе источников, которые попадут в ответ.
## Минимальный пример
Передайте в `tools[]` элемент с типом `hubris:web_search` (это наш namespace для server-side инструментов — отличает их от обычных function-tool):
```bash
curl -s https://api.hubris.pw/v1/chat/completions \
-H "Authorization: Bearer sk-gw-..." \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-4o-mini",
"messages": [
{"role": "user", "content": "Что произошло на рынке российских облигаций на прошлой неделе?"}
],
"tools": [
{ "type": "hubris:web_search" }
]
}'
```
Ответ — обычный chat-ответ, но с дополнительным полем `annotations[]`, где перечислены источники:
```json
{
"id": "chatcmpl-...",
"choices": [{
"index": 0,
"message": {
"role": "assistant",
"content": "На прошлой неделе ставка ОФЗ выросла до 16.3% на длинных бумагах...",
"annotations": [
{
"type": "url_citation",
"url_citation": {
"url": "https://www.cbr.ru/press/event/?id=12345",
"title": "ЦБ РФ — Информация о денежно-кредитной политике",
"content": "Совет директоров Банка России 17 мая 2026 года...",
"start_index": 42,
"end_index": 87
}
}
]
},
"finish_reason": "stop"
}],
"usage": {
"prompt_tokens": 4521,
"completion_tokens": 318,
"total_tokens": 4839
}
}
```
`tool_calls` остаётся пустым — поиск отрабатывается полностью на стороне нашего слоя маршрутизации, наружу не вылезает.
## Параметры поиска
Можно тонко настроить поведение:
```json
{
"type": "hubris:web_search",
"parameters": {
"max_results": 5,
"search_context_size": "medium"
}
}
```
| Параметр | Значения | Описание |
| --------------------- | ------------------------------- | -------------------------------------------------------------------------------------------- |
| `max_results` | 1–10 (по умолчанию 5) | Сколько источников использовать для составления ответа |
| `search_context_size` | `"low"` / `"medium"` / `"high"` | Сколько контекста из каждого источника подгружать в промпт. Больше — точнее ответ, но дороже |
Все параметры опциональны.
## Аннотации
`annotations[]` приходит на сообщении в формате:
```json
{
"type": "url_citation",
"url_citation": {
"url": "https://...",
"title": "Заголовок страницы",
"content": "Релевантный фрагмент текста источника",
"start_index": 42,
"end_index": 87
}
}
```
`start_index` / `end_index` — позиция (в символах) в `message.content`, к которой относится цитата. Удобно для подсветки источников рядом с текстом в UI.
## Стриминг
`stream: true` работает. Особенность: **`annotations[]` приходит полным массивом сразу в первом чанке** (внутри `delta.annotations`), ещё до самого текста. Дальше идут обычные текстовые `delta.content` чанки.
```
data: {"choices":[{"index":0,"delta":{"role":"assistant","annotations":[{"type":"url_citation","url_citation":{...}}]}}]}
data: {"choices":[{"index":0,"delta":{"content":"На прошлой "}}]}
data: {"choices":[{"index":0,"delta":{"content":"неделе ставка ОФЗ "}}]}
...
data: {"choices":[{"index":0,"finish_reason":"stop"}]}
data: [DONE]
```
Это удобно: в UI можно сразу показать «по N источникам» — а текст подгружать по мере прихода.
## OpenAI SDK
В официальных SDK OpenAI поле `tools` поддерживается, но проверки типа стандартные `function` — поэтому server-tool придётся передавать через `extra_body` (Python) или приведением типов (TypeScript).
**Python:**
```python
from openai import OpenAI
client = OpenAI(
base_url="https://api.hubris.pw/v1",
api_key="sk-gw-...",
)
resp = client.chat.completions.create(
model="openai/gpt-4o-mini",
messages=[{"role": "user", "content": "Курс доллара ЦБ на сегодня?"}],
extra_body={
"tools": [
{"type": "hubris:web_search", "parameters": {"max_results": 3}}
]
},
)
msg = resp.choices[0].message
print(msg.content)
annotations = msg.model_extra.get("annotations", [])
for ann in annotations:
cit = ann["url_citation"]
print(f" - {cit['title']}: {cit['url']}")
```
**TypeScript:**
```ts
import OpenAI from 'openai';
const client = new OpenAI({
baseURL: 'https://api.hubris.pw/v1',
apiKey: 'sk-gw-...',
});
const resp = await client.chat.completions.create({
model: 'openai/gpt-4o-mini',
messages: [{ role: 'user', content: 'Курс доллара ЦБ на сегодня?' }],
tools: [
{ type: 'hubris:web_search', parameters: { max_results: 3 } },
],
} as any);
const msg = resp.choices[0].message;
console.log(msg.content);
const annotations = (msg as any).annotations ?? [];
for (const ann of annotations) {
console.log(` - ${ann.url_citation.title}: ${ann.url_citation.url}`);
}
```
## Что важно знать
* **`prompt_tokens` будут раздуты.** Результаты поиска инжектятся в промпт — это нормально, что на коротком запросе вернутся 3–10 тысяч prompt-токенов. Ответ модели опирается на эти источники.
* **`tool_calls` пустой.** Не пытайтесь обработать веб-поиск как обычный function-call — поиск отрабатывает полностью на нашей стороне.
* **Совмещение с обычными функциями работает.** В одном `tools[]` можно передать и server-tool, и свои function-tools — модель выберет, что вызывать.
* **`/v1/responses` со стримингом — известное ограничение.** В non-stream ответе `/v1/responses` элемент в `output[]` приходит с типом `hubris:web_search`. В streaming-режиме на этом эндпоинте имя пока не нормализуется обратно — в SSE-событиях `response.output_item.added` / `response.output_item.done` префикс типа отличается от того, что вы отправили в запросе. Будет исправлено. На `/v1/chat/completions` (любой режим) и на non-stream `/v1/responses` всё корректно.
## Биллинг
Веб-поиск тарифицируется не только токенами:
* **Токены** — обычным образом (prompt + completion). С учётом раздутого prompt после инжекта результатов.
* **Плата за поиск** — фиксированная сумма за запрос (зависит от модели и `search_context_size`).
В большинстве случаев бо́льшая часть стоимости запроса — это именно плата за поиск, не токены. Итоговая сумма по запросу видна в разделе [«Расходы»](/usage).
Списание происходит после успешного ответа. Транзакция атомарна — баланс, usage\_log и движение по балансу пишутся одной БД-транзакцией.
## Что дальше
* [Вызов инструментов](/docs/features/tool-calling) — для функций, которые исполняете вы сами.
* [Структурированный вывод](/docs/features/structured-output) — заставить ответ соответствовать JSON-схеме (работает вместе с веб-поиском).
* [POST /v1/chat/completions](/docs/api/chat-completions) — полная схема.
* [Расходы](/usage) — детализация запросов и стоимости.
---
# Claude Code (VS Code) (/docs/integrations/claude-code)
> Подключение Claude Code (CLI и расширение VS Code) к Hubris через эндпоинт /v1/messages — Fable, Sonnet, Opus, Haiku, reasoning effort, кэширование, статусная строка.
[Claude Code](https://code.claude.com/docs/en/overview) — официальный агент Anthropic. Работает в терминале и как расширение VS Code, понимает контекст проекта, умеет редактировать файлы, исполнять команды, читать скриншоты, держит долгую сессию с кэшированием промптов. Поддерживает любой Anthropic-совместимый бэкенд через переменные окружения. Hubris подходит из коробки — и для CLI, и для VS Code.
## Что вы получите
* Доступ ко всему семейству Claude (Fable 5, Sonnet 5, Opus 4.8, Haiku 4.5 и предыдущие поколения) **с оплатой в рублях**.
* Не только Claude: [любая чат-модель каталога](#любые-чат-модели-каталога--gpt-gemini-deepseek-и-другие) — GPT, Gemini, DeepSeek и другие.
* Нативное `prompt caching` для длинных сессий — экономит до десятков рублей на каждом турне.
* Чтение скриншотов и других изображений (vision).
* Использование инструментов (tool use) для редактирования файлов и запуска команд.
* Опциональная statusline в TUI с балансом, расходом сессии и текущей моделью.
## Требования
* macOS / Linux / WSL / современный терминал на Windows — либо VS Code с расширением Claude Code.
* Аккаунт на [hubris.pw](https://hubris.pw/dashboard) и API-ключ формата `sk-gw-…` (создать на [/keys](https://hubris.pw/keys)).
* Положительный баланс — пополнить можно [здесь](https://hubris.pw/billing).
## Установка Claude Code
Подробная инструкция — в [официальной документации](https://code.claude.com/docs/en/quickstart). Варианты:
* **VS Code**: Extensions (`Ctrl+Shift+X`) → «Claude Code» (издатель Anthropic) → Install.
* **CLI**:
```bash
npm install -g @anthropic-ai/claude-code
```
Альтернатива — однострочный установщик:
```bash
curl -fsSL https://claude.ai/install.sh | bash
```
Убедитесь, что Claude Code запускается: `claude --version` (для CLI) или иконка Claude в сайдбаре VS Code.
## Подключение к Hubris
Рекомендуемый способ — блок `env` в файле `~/.claude/settings.json` (Windows: `C:\Users\<имя>\.claude\settings.json`). Его подхватывают **и CLI, и расширение VS Code**, поэтому конфиг настраивается один раз:
```json
{
"env": {
"ANTHROPIC_AUTH_TOKEN": "sk-gw-ВАШ_КЛЮЧ",
"ANTHROPIC_BASE_URL": "https://api.hubris.pw",
"ANTHROPIC_API_KEY": "",
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "anthropic/claude-haiku-4.5",
"ANTHROPIC_DEFAULT_SONNET_MODEL": "anthropic/claude-sonnet-5",
"ANTHROPIC_DEFAULT_OPUS_MODEL": "anthropic/claude-opus-4.8",
"ANTHROPIC_DEFAULT_OPUS_MODEL_SUPPORTED_CAPABILITIES": "effort,xhigh_effort,max_effort,thinking,adaptive_thinking,interleaved_thinking",
"ANTHROPIC_MODEL": "anthropic/claude-opus-4.8",
"ANTHROPIC_SMALL_FAST_MODEL": "anthropic/claude-haiku-4.5"
}
}
```
Пояснения к неочевидным строкам:
* `ANTHROPIC_API_KEY: ""` — предохранитель: если в системе уже лежит настоящий ключ Anthropic, пустая строка не даст Claude Code случайно отправить его на шлюз.
* `ANTHROPIC_DEFAULT_OPUS_MODEL_SUPPORTED_CAPABILITIES` — возвращает ползунок reasoning effort, см. [ниже](#reasoning-effort-ползунок-lowmediumhigh).
* Вход в аккаунт Anthropic не нужен — авторизация идёт по ключу `sk-gw-…`.
После сохранения файла перезапустите VS Code (или откройте новый терминал) и запустите агента из папки проекта. Первый же запрос уйдёт в Hubris вместо `api.anthropic.com` и спишется с вашего баланса в рублях — проверить можно на [странице Использование](https://hubris.pw/usage).
Альтернатива для CLI — обычные переменные окружения в `~/.bashrc` / `~/.zshrc` (или профиле PowerShell):
```bash
export ANTHROPIC_BASE_URL="https://api.hubris.pw"
export ANTHROPIC_AUTH_TOKEN="sk-gw-..."
```
## Выбор модели
В рабочей сессии переключайте модель командой `/model`. Полный список — в [каталоге](https://hubris.pw/models?provider=anthropic).
Какие имена принимает Hubris:
* **Канонические id с провайдером** — `anthropic/claude-opus-4.8`, `anthropic/claude-sonnet-5`, `anthropic/claude-fable-5`. Работают для всех моделей каталога — **рекомендуем именно их**.
* **Короткие Anthropic-имена** — `claude-sonnet-4-5`, `claude-opus-4-7`, `claude-haiku-4-5` (в том числе с датой или `-latest` на конце). Поддерживаются для поколений до Sonnet 4.6 / Opus 4.7 включительно.
Чтобы зафиксировать модель по умолчанию, задайте `ANTHROPIC_MODEL` в конфиге выше или передайте её при запуске CLI:
```bash
claude --model anthropic/claude-sonnet-5
```
### Какая модель когда подходит
| Модель | Когда брать |
| ---------------------------- | ----------------------------------------------------------------------------- |
| `anthropic/claude-haiku-4.5` | быстрые ответы, поиск по коду, простые правки, дёшево. |
| `anthropic/claude-sonnet-5` | основной рабочий вариант: качественные правки, рефакторинги, длинные сессии. |
| `anthropic/claude-opus-4.8` | сложные архитектурные задачи, миграции, разбор багов в больших кодовых базах. |
| `anthropic/claude-fable-5` | максимум качества для самых трудных задач; заметно дороже Opus. |
## Reasoning effort: ползунок low/medium/high
Claude Code показывает выбор глубины рассуждения (low / medium / high) только для моделей, чьи имена он узнаёт. Канонические id вида `anthropic/claude-opus-4.8` под встроенные шаблоны не подпадают, поэтому без дополнительной настройки ползунок пропадает — хотя сама модель effort полностью поддерживает.
Решение — объявить возможности модели явно через переменную `ANTHROPIC_DEFAULT_OPUS_MODEL_SUPPORTED_CAPABILITIES` (уже включена в рекомендуемый конфиг выше). После перезапуска ползунок возвращается в `/model` и в VS Code. Аналогичная переменная с суффиксом `_SUPPORTED_CAPABILITIES` есть и у `ANTHROPIC_DEFAULT_SONNET_MODEL`.
Управлять уровнем можно и без ползунка:
* команда `/effort high` прямо в сессии;
* поле `"effortLevel": "high"` в `~/.claude/settings.json` (вне блока `env`) — сохраняет выбор между сессиями;
* флаг CLI `claude --effort high`.
## Любые чат-модели каталога — GPT, Gemini, DeepSeek и другие
Claude Code через Hubris не ограничен моделями Anthropic. Эндпоинт [/v1/messages](/docs/api/messages) принимает **любую чат-модель каталога**: формат Anthropic конвертируется на стороне агрегатора, ответ приходит в привычном Claude Code виде — со стримингом, tool use и токен-статистикой. Проверено вживую на `openai/gpt-4o-mini`, `google/gemini-3.6-flash` и `deepseek/deepseek-chat`.
Просто укажите канонический id модели из [каталога](https://hubris.pw/models) в тех же переменных:
```json
{
"env": {
"ANTHROPIC_AUTH_TOKEN": "sk-gw-ВАШ_КЛЮЧ",
"ANTHROPIC_BASE_URL": "https://api.hubris.pw",
"ANTHROPIC_API_KEY": "",
"ANTHROPIC_MODEL": "openai/gpt-4o-mini",
"ANTHROPIC_DEFAULT_SONNET_MODEL": "google/gemini-3.6-flash",
"ANTHROPIC_DEFAULT_OPUS_MODEL": "deepseek/deepseek-chat",
"ANTHROPIC_SMALL_FAST_MODEL": "anthropic/claude-haiku-4.5"
}
}
```
Слоты переключателя `/model` (Haiku / Sonnet / Opus) — это просто «ярлыки»: какую модель подставить в каждый, решаете вы через `ANTHROPIC_DEFAULT_*_MODEL`. В сессии можно указать и произвольную модель напрямую: `/model google/gemini-3.6-flash`.
### Вариант конфигурации: настройки расширения VS Code
Вместо `~/.claude/settings.json` те же переменные можно задать прямо в настройках VS Code — поле `claudeCode.environmentVariables` (массив объектов `{"name", "value"}`). Важно: настройка применяется только из **User Settings** (`Ctrl+Shift+P` → «Preferences: Open User Settings (JSON)»), в workspace-файле проекта она игнорируется.
```json
{
"claudeCode.environmentVariables": [
{ "name": "ANTHROPIC_BASE_URL", "value": "https://api.hubris.pw" },
{ "name": "ANTHROPIC_AUTH_TOKEN", "value": "sk-gw-ВАШ_КЛЮЧ" },
{ "name": "ANTHROPIC_API_KEY", "value": "" },
{ "name": "ANTHROPIC_MODEL", "value": "openai/gpt-4o-mini" }
]
}
```
Не задавайте одну и ту же переменную и здесь, и в `~/.claude/settings.json` — выберите одно место, чтобы конфигурация оставалась предсказуемой.
### Ограничения
* Работают только **чат-модели** (`text`-вывод). Image-, видео- и embedding-модели каталога через Claude Code недоступны.
* Ползунок [reasoning effort](#reasoning-effort-ползунок-lowmediumhigh) показывается только для Claude-моделей — у остальных глубина рассуждений управляется самой моделью.
* Экономия на [кэшировании промптов](#кэширование-промптов) — сильная сторона именно Claude-моделей; у других провайдеров кэш работает иначе или не работает вовсе.
* Claude Code — агент, обученный в первую очередь под Claude. С другими моделями он работает, но качество агентных сценариев (инструменты, длинные сессии) может отличаться.
## Statusline с балансом
В нижней строке TUI Claude Code можно выводить текущий баланс Hubris, расход сессии и активную модель. Скрипт обновляется на каждом турне и не блокирует ответ модели.
### Установка (Linux / macOS / WSL)
```bash
curl -fsSL https://hubris.pw/scripts/claude-statusline.sh \
-o ~/.claude/hubris-statusline.sh
chmod +x ~/.claude/hubris-statusline.sh
```
Добавьте в `~/.claude/settings.json`:
```json
{
"statusLine": {
"type": "command",
"command": "~/.claude/hubris-statusline.sh"
}
}
```
### Установка (Windows / кросс-платформа на Node.js)
```powershell
Invoke-WebRequest -Uri https://hubris.pw/scripts/claude-statusline.mjs `
-OutFile $env:USERPROFILE\.claude\hubris-statusline.mjs
```
В `~/.claude/settings.json`:
```json
{
"statusLine": {
"type": "command",
"command": "node ~/.claude/hubris-statusline.mjs"
}
}
```
Скрипт читает `ANTHROPIC_AUTH_TOKEN` из среды (тот же ключ, что использует CC) и обращается к [/v1/usage](/docs/api/usage) за расходом и балансом. Запросы на статус — бесплатны (не списываются с баланса).
Пример вывода:
```
hubris │ Sonnet 5 │ session 0,42 ₽ · today 12,18 ₽ · balance 1 287 ₽
```
## Прочая конфигурация
| Переменная окружения | Что делает |
| ----------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `ANTHROPIC_BASE_URL` | URL шлюза. Для Hubris — `https://api.hubris.pw`. |
| `ANTHROPIC_AUTH_TOKEN` | API-ключ `sk-gw-…`. Используется как `Authorization: Bearer`. |
| `ANTHROPIC_MODEL` | модель по умолчанию. |
| `ANTHROPIC_SMALL_FAST_MODEL` | модель для лёгких подзадач (резюмирование, выбор имени). Оставляйте `anthropic/claude-haiku-4.5`. |
| `ANTHROPIC_DEFAULT_HAIKU_MODEL` / `..._SONNET_MODEL` / `..._OPUS_MODEL` | какие модели подставляются в стандартные пункты переключателя `/model`. |
| `ANTHROPIC_DEFAULT_OPUS_MODEL_SUPPORTED_CAPABILITIES` | явное объявление возможностей модели (effort, thinking) — нужно при канонических id, см. раздел про reasoning effort. |
| `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` | `1` — отключает фоновые запросы Claude Code к сервисам Anthropic (телеметрия, проверка обновлений). Необязательно. |
## Дневные лимиты
Если на ключе включён дневной лимит трат (можно поставить на [/keys](https://hubris.pw/keys)), Hubris вернёт `429 rate_limit_error`. Claude Code отобразит ошибку в TUI и предложит остановить сессию. Лимит обнуляется ежедневно в 00:00 UTC.
## Кэширование промптов
Claude Code сам управляет `cache_control` для длинных системных промптов и расшаренного контекста. Hubris передаёт это поле в Anthropic без изменений и **списывает по фактической стоимости** с учётом cache hit (0,1× от обычной цены) или cache write (1,25×). Подробности — в [API-референсе /v1/messages](/docs/api/messages).
## Часто задаваемые вопросы
**Можно ли использовать через настоящий ключ Anthropic параллельно?**
Да: переменные `ANTHROPIC_*` локальные. Достаточно открыть второй терминал без них — Claude Code пойдёт напрямую.
**Где увидеть детальный расход?**
Все запросы появляются на [странице Использование](https://hubris.pw/usage) с разбивкой по моделям и времени. Через API — [GET /v1/usage](/docs/api/usage).
**Поддерживается ли Privacy Mode для маскирования PII?**
В первой версии — нет. Если нужно маскирование, делайте запросы через [/v1/chat/completions](/docs/api/chat-completions) с заголовком `X-Hubris-Privacy-Mask`. Поддержка на `/v1/messages` появится позже.
**Что делать, если Hubris отвечает `404 not_found_error` на модель?**
Чаще всего это короткое имя новой модели (например `claude-opus-4-8`), которое шлюз пока не распознаёт. Используйте канонический id из [каталога](https://hubris.pw/models?provider=anthropic) — `anthropic/claude-opus-4.8` — или переключитесь командой `/model`.
**Пропал ползунок low/medium/high у Opus — как вернуть?**
Добавьте `ANTHROPIC_DEFAULT_OPUS_MODEL_SUPPORTED_CAPABILITIES` в блок `env` (см. [рекомендуемый конфиг](#подключение-к-hubris)) и перезапустите VS Code. Подробности — в разделе про [reasoning effort](#reasoning-effort-ползунок-lowmediumhigh).
## Что дальше
* [POST /v1/messages](/docs/api/messages) — полный API-референс по эндпоинту.
* [Каталог моделей](https://hubris.pw/models?provider=anthropic) — активные Claude-модели и цены в рублях.
* [Использование](https://hubris.pw/usage) — детальный расход по запросам.
* [Биллинг](https://hubris.pw/billing) — пополнение баланса через СБП.
---
# Cline (VS Code / Cursor) (/docs/integrations/cline)
> Подключение AI-ассистента Cline к Hubris — кодит, читает и пишет файлы, запускает терминал из вашего редактора.
[Cline](https://cline.bot) — расширение для VS Code и Cursor, которое превращает редактор в полноценного AI-агента: умеет читать и редактировать файлы, запускать команды в терминале, работать с git, выполнять многошаговые задачи. Подключается к любому OpenAI-совместимому провайдеру — в том числе к Hubris.
## Требования
* VS Code 1.85+ или Cursor
* Установленный аккаунт на [hubris.pw](https://hubris.pw/dashboard) и API-ключ
## Установка Cline
1. Откройте панель расширений в VS Code (`Ctrl+Shift+X`).
2. Найдите **Cline** (издатель — saoudrizwan).
3. Нажмите **Install**.
4. После установки в боковой панели появится иконка Cline.
## Подключение к Hubris
В панели Cline нажмите на селектор модели под полем ввода. Откроется список API-провайдеров.
1. Выберите **OpenAI Compatible**.
2. Заполните поля:
| Поле | Значение |
| ------------ | ------------------------------------------------------ |
| **Base URL** | `https://api.hubris.pw/v1` |
| **API Key** | ваш ключ из [личного кабинета](https://hubris.pw/keys) |
| **Model ID** | например `anthropic/claude-haiku-4.5` |
3. Сохраните настройки. Cline готов к работе — можно сразу писать задачу.
## Рекомендуемые модели
Cline — это многошаговый агент: длинный контекст, много вызовов инструментов, чтение/запись файлов. Под такой профиль подходят:
| Назначение | Модель |
| ----------------------------------------- | --------------------------------------------------- |
| Сложные многошаговые задачи, рефакторинги | Claude Opus (`anthropic/claude-opus-*` из каталога) |
| Основная рабочая лошадка для кодинга | Claude Sonnet (`anthropic/claude-sonnet-*`) |
| Быстрые правки, экономичный режим | `anthropic/claude-haiku-4.5` |
| Универсальная альтернатива | `openai/gpt-4o-mini` |
Полный актуальный список — в [каталоге моделей](/models). Подойдёт любая модель с поддержкой tool calling (фильтр в каталоге).
## Стоимость и контроль расходов
Cline — агент, а значит делает много вызовов на одну задачу: чтение файлов, изменения, проверка результата. Расход токенов на одну сессию может оказаться выше, чем ожидаешь.
Что советуем:
* **Дневной лимит на ключ** — задайте лимит расхода в [настройках ключа](/keys). При превышении ответы вернутся с `429 daily_limit_exceeded`, баланс не пострадает.
* **Раздельные ключи** — отдельный ключ для каждого проекта или клиента, чтобы видеть расходы в [«Расходах»](/usage) разнесённо.
* **Дешёвая модель для рутины** — Haiku или GPT-4o-mini для типовых правок, переключаться на Opus только когда задача реально сложная.
## Решение проблем
### Cline не подключается, ошибка 401
* Проверьте, что в поле **API Key** именно ключ Hubris (формат `sk-gw-` + hex).
* Если копировали из браузера — убедитесь, что не попали пробелы по краям.
* В случае сомнений — создайте новый ключ в [личном кабинете](/keys).
### `model not found` при попытке генерации
* Проверьте написание Model ID. Формат — `provider/model-name`, например `anthropic/claude-haiku-4.5`.
* Убедитесь, что модель есть в нашем [каталоге](/models) — Hubris поддерживает не все модели всех провайдеров.
### Cline работает, но «теряет» инструменты или зацикливается
* Это типичная история со слабыми моделями на сложных tool-сценариях. Попробуйте более сильную модель — Claude Sonnet или Opus.
* Уменьшите размер задачи: дайте Cline более узкую цель за раз.
### Сильно вырос баланс — почему?
* Каждое действие Cline (открыть файл, прочитать, изменить, проверить) — отдельный запрос с собственным `prompt_tokens` и `completion_tokens`. Длинные файлы → большой prompt.
* Откройте [«Расходы»](/usage) и отсортируйте по дате — будут видны все запросы за сессию.
## Что дальше
* [Каталог моделей](/models) — выбрать подходящую модель.
* [Управление API-ключами](/keys) — создать отдельный ключ и задать дневной лимит.
* [Расходы](/usage) — детализация всех запросов и потраченных рублей.
* [Вызов инструментов](/docs/features/tool-calling) — как работают tool calls под капотом.
---
# Dify (/docs/integrations/dify)
> Подключение Hubris к Dify — низкокодовой LLM-платформе для построения чат-приложений, агентов и workflow.
[Dify](https://dify.ai) — open-source платформа для построения LLM-приложений: чат-боты, AI-агенты, RAG-workflow. Поддерживает self-hosted и облачный (`cloud.dify.ai`) режимы. Hubris подключается как провайдер типа OpenAI-API-compatible — модели Hubris становятся доступны во всех приложениях.
## Требования
* Аккаунт на [cloud.dify.ai](https://cloud.dify.ai) **или** self-hosted Dify
* Аккаунт на [hubris.pw](https://hubris.pw/dashboard) и API-ключ
## Подключение
1. В Dify откройте **Settings** → **Model Providers**.
2. Найдите в списке **OpenAI-API-compatible** и нажмите **Add Model**.
3. Заполните поля:
| Поле | Значение |
| ---------------------- | ------------------------------------------------------------------- |
| **Model Type** | `LLM` (для chat-моделей) |
| **Model Name** | ID модели из нашего каталога, например `anthropic/claude-haiku-4.5` |
| **API Key** | ваш ключ из [личного кабинета Hubris](https://hubris.pw/keys) |
| **API endpoint URL** | `https://api.hubris.pw/v1` |
| **Completion mode** | `Chat` |
| **Model context size** | размер контекста модели (см. карточку в [каталоге](/models)) |
| **Maximum chunks** | 1 |
4. Сохраните. Модель появится в списке доступных в Dify.
Чтобы добавить ещё модели — повторите для каждой. Один ключ Hubris работает на все.
## Использование в приложениях
После подключения модель Hubris доступна везде, где Dify предлагает выбор LLM:
* **AI Chatflow** — собранный из блоков чат-агент.
* **LLM-блок в Workflow** — точка вызова модели в произвольном workflow.
* **Agent app** — приложение с инструментами и многошаговыми сценариями.
* **Knowledge / RAG** — встраивание модели в retrieval-цепочки.
В каждом блоке выбираете подключённого провайдера (`OpenAI-API-compatible`) и нужную модель.
## Рекомендуемые модели
| ID | Когда подходит |
| ---------------------------- | ---------------------------------------------------------- |
| `anthropic/claude-haiku-4.5` | быстрые чат-боты, классификация |
| `openai/gpt-4o-mini` | универсальный недорогой выбор, поддержка structured output |
| `google/gemini-2.0-flash` | мультимодальные приложения |
Для агентов и сложных reasoning-задач — Claude Sonnet/Opus из [каталога](/models). Используйте фильтр «tool calling» для агентских сценариев.
## Streaming и Function Calling
Dify работает с обоими по умолчанию — `stream: true` идёт в наш `/v1/chat/completions`, tools прокидываются без дополнительной настройки. Никаких отдельных флагов в Dify включать не надо.
## Embeddings для RAG
Для retrieval-сценариев в Dify нужен отдельный provider типа **Text Embedding**. Добавьте ещё одну запись OpenAI-API-compatible, но с **Model Type** = `Text Embedding`, указав модель-эмбеддер из нашего [каталога](/models). API endpoint и ключ — те же, что и для LLM-провайдера.
## Решение проблем
### Provider добавлен, но Dify не видит модели
* Перепроверьте API endpoint URL — должен быть `https://api.hubris.pw/v1` (с `/v1` в конце, без trailing slash).
* Убедитесь, что Model Name точно совпадает с ID из каталога Hubris.
### `Invalid API key` при тестовом запросе
* API-ключ должен начинаться с `sk-gw-` и не содержать пробелов.
### Модель работает, но контекст «обрывается» / ответы короткие
* Проверьте поле **Model context size** — оно должно соответствовать реальному контексту модели (карточка в каталоге).
* Поле **Maximum chunks** относится к чанкам контекста в RAG — оставьте `1` для обычных чат-сценариев.
### Streaming не работает в Workflow
* В настройках LLM-блока должно быть включено **Stream Mode**. Если выключено — ответ приходит одним куском в конце.
## Что дальше
* [Каталог моделей](/models) — IDs для подключения в Dify.
* [Управление API-ключами](/keys) — отдельный ключ под Dify-приложение.
* [Расходы](/usage) — детализация по запросам.
* [Структурированный вывод](/docs/features/structured-output) — стабильные JSON-ответы в workflow.
---
# Hermes Agent (/docs/integrations/hermes-agent)
> Подключение Hermes Agent от Nous Research — самообучающегося AI-агента с Telegram, памятью и веб-дашбордом — к Hubris.
[Hermes Agent](https://github.com/NousResearch/hermes-agent) — открытый AI-агент от Nous Research. Работает с файлами и терминалом, сам формирует «навыки» из опыта, хранит память между сессиями, поддерживает чат через Telegram. Подключается к любому OpenAI-совместимому провайдеру.
## Требования
* Linux / macOS / WSL2 / Android (Termux)
* Python 3.11+
* Аккаунт на [hubris.pw](https://hubris.pw/dashboard) и API-ключ
## Установка
```bash
curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash
```
Перезагрузите shell:
```bash
source ~/.bashrc # или source ~/.zshrc
```
## Подключение к Hubris
### Через мастер настройки (рекомендуется)
```bash
hermes setup
```
В меню выберите **Custom Endpoint** и заполните:
| Поле | Значение |
| ------------------ | ------------------------------------------------------ |
| **Base URL** | `https://api.hubris.pw/v1` |
| **API Key** | ваш ключ из [личного кабинета](https://hubris.pw/keys) |
| **Model** | например `anthropic/claude-haiku-4.5` |
| **Context window** | размер контекста модели (см. [каталог](/models)) |
Остальные параметры можно оставить по умолчанию.
### Через файлы конфигурации
`~/.hermes/.env`:
```bash
OPENAI_API_KEY=sk-gw-...
OPENAI_BASE_URL=https://api.hubris.pw/v1
```
`~/.hermes/config.yaml`:
```yaml
model:
default: "anthropic/claude-haiku-4.5"
provider: "custom"
base_url: "https://api.hubris.pw/v1"
```
## Запуск
```bash
hermes
```
Откроется интерактивный терминал. Можно сразу писать задачу.
## Веб-дашборд
Hermes поставляется со встроенным веб-интерфейсом для управления сессиями, памятью и навыками:
```bash
hermes dashboard
```
По умолчанию открывается на `http://localhost:9119`. **Не открывайте на публичном IP без защиты** — дашборд хранит API-ключи. Для удалённого доступа используйте SSH-туннель:
```bash
ssh -L 9119:localhost:9119 user@ваш-сервер
```
## Подключение Telegram-бота
Hermes умеет принимать команды через Telegram.
1. У [@BotFather](https://t.me/BotFather) создайте бота (`/newbot`), скопируйте токен.
2. У [@userinfobot](https://t.me/userinfobot) узнайте свой Telegram ID.
3. Добавьте в `~/.hermes/.env`:
```bash
TELEGRAM_BOT_TOKEN=ваш_токен_от_BotFather
TELEGRAM_ALLOWED_USERS=ваш_telegram_id
```
4. Запустите gateway:
```bash
hermes gateway start
```
Теперь бот отвечает в Telegram через выбранную модель Hubris.
## Рекомендуемые модели
Hermes — агент с длинной сессией и многократными вызовами инструментов. Подойдут модели с хорошим tool calling:
| ID | Когда уместна |
| ---------------------------- | ------------------------------------------ |
| `anthropic/claude-haiku-4.5` | быстрые типовые задачи |
| `openai/gpt-4o-mini` | универсальный выбор |
| `google/gemini-2.0-flash` | мультимодальные сценарии, большой контекст |
Для сложных автономных задач (рефакторинг, длинные сессии) — Claude Sonnet/Opus из [каталога](/models). Фильтр «tool calling» поможет отобрать подходящие.
## Решение проблем
### `hermes: command not found` после установки
```bash
source ~/.bashrc # или ~/.zshrc
```
Проверьте, что `~/.local/bin` в PATH: `echo $PATH`.
### `Invalid API key`
* Перепроверьте ключ в `~/.hermes/.env` — без пробелов, начинается с `sk-gw-`.
### Бот в Telegram не отвечает
* Проверьте gateway: `hermes gateway status`.
* Убедитесь, что Telegram ID правильно указан в `TELEGRAM_ALLOWED_USERS`, без пробелов и комментариев в той же строке.
### `No messaging platforms enabled` в логах
* Проверьте, что `TELEGRAM_BOT_TOKEN` указан без inline-комментариев в .env (`TOKEN=xyz # comment` ломает парсинг — комментарий выносите на отдельную строку).
## Что дальше
* [Каталог моделей](/models) — выбор по поддержке tool calling.
* [Управление API-ключами](/keys) — отдельный ключ под Hermes.
* [Расходы](/usage) — детализация сессий.
* [OpenClaw](/docs/integrations/openclaw) — альтернативный multi-messenger агент.
---
# Kilo Code (VS Code / PyCharm) (/docs/integrations/kilo-code)
> Подключение Kilo Code — AI-агента для VS Code и PyCharm — к Hubris.
[Kilo Code](https://kilocode.ai) — AI-агент для разработчиков, доступный и в VS Code, и в PyCharm. Автономно выполняет многошаговые задачи: пишет, рефакторит, отлаживает, добавляет тесты, при необходимости открывает браузер для проверки результата.
## Требования
* VS Code 1.85+ **или** PyCharm 2024.1+
* Аккаунт на [hubris.pw](https://hubris.pw/dashboard) и API-ключ
## Установка и подключение в VS Code
1. Откройте панель расширений (`Ctrl+Shift+X`), найдите **Kilo Code**, нажмите **Install**.
2. В боковой панели появится иконка Kilo — откройте её → **Settings** (шестерёнка) → вкладка **Providers**.
3. Нажмите **Custom provider** → **Connect**.
4. Заполните поля:
| Поле | Значение |
| --------------- | ------------------------------------------------------ |
| **Provider ID** | `hubris` (или любое имя — это локальный идентификатор) |
| **Base URL** | `https://api.hubris.pw/v1` |
| **API Key** | ваш ключ из [личного кабинета](https://hubris.pw/keys) |
5. Сохраните настройки. Kilo автоматически подтянет список моделей.
## Установка и подключение в PyCharm
1. Откройте **Settings** → **Plugins** → **Marketplace**, найдите **Kilo Code**, установите. Перезапустите IDE.
2. В правой панели появится иконка Kilo — откройте, нажмите **Settings**.
3. В разделе **Configuration Profile** создайте новый профиль.
4. В поле **API Provider** выберите **OpenAI Compatible**.
5. Заполните **Base URL** = `https://api.hubris.pw/v1` и **API Key**. Список моделей подтянется автоматически.
## Рекомендуемые модели
Kilo автономно делает много шагов (план → код → тест → правка), поэтому имеет смысл выбирать модели с поддержкой tool calling и нормальным reasoning.
| ID | Когда подходит |
| ---------------------------- | ------------------------------------ |
| `anthropic/claude-haiku-4.5` | быстрая работа над типовыми правками |
| `openai/gpt-4o-mini` | универсальный недорогой выбор |
| `google/gemini-2.0-flash` | мультимодальные сценарии |
Для тяжёлых рефакторингов и многошаговых задач — берите Claude Opus/Sonnet из [каталога](/models). Фильтр «tool calling» поможет отсеять модели, не поддерживающие агентский сценарий.
## Контроль расходов
* Заведите [отдельный ключ](/keys) под Kilo, поставьте суточный лимит — на случай если автономный агент уйдёт «копать» дольше ожидаемого.
* Для рутины используйте недорогую модель, на тяжёлые задачи переключайте Opus.
* Расход по сессиям виден в [«Расходах»](/usage) — фильтруйте по ключу.
## Решение проблем
### Список моделей пустой / Kilo не подтягивает каталог
* Это обычно про невалидный API-ключ или неправильный Base URL. Перепроверьте оба.
* В VS Code иногда нужно перезагрузить окно: `Ctrl+Shift+P` → **Developer: Reload Window**.
* В PyCharm — закройте и заново откройте панель Kilo.
### 401 при первом запросе
* Ключ должен быть в формате `sk-gw-` + hex, без пробелов и переводов строк.
### Модель «не поддерживает изображения» / картинка не доходит
Симптомы: при попытке прикрепить изображение Kilo пишет, что модель не поддерживает image input, **либо** картинка вроде бы отправляется, но модель отвечает что-то вроде «This model does not support image input, please provide details as text» — и так на любой модели, даже заведомо мультимодальной (`openai/gpt-4o`, Claude Sonnet, Gemini).
Причина — в самом Kilo, а не в Hubris. Для провайдеров типа **OpenAI Compatible** Kilo определяет поддержку изображений **на своей стороне**, не запрашивая возможности модели у сервера, и по умолчанию считает такие модели текстовыми. В итоге расширение либо блокирует прикрепление, либо кладёт в запрос только имя файла без самой картинки — модель получает текст вроде `233.png`, но не пиксели, и закономерно отвечает, что изображения не видит.
Решение — явно объявить модальности модели в файле `kilo.jsonc` (лежит в корне проекта или в `.kilo/kilo.jsonc`):
```jsonc
{
"provider": {
"hubris": {
"models": {
"openai/gpt-4o": {
"name": "openai/gpt-4o",
"modalities": {
"input": ["text", "image"],
"output": ["text"]
}
}
}
}
}
}
```
* `hubris` — тот же **Provider ID**, что вы задали при подключении.
* Ключ модели (`openai/gpt-4o`) должен точно совпадать с тем, что выбран в Kilo.
* В `input` перечислите реально поддерживаемые модальности — для работы с картинками обязателен `"image"`. Поддерживает ли модель изображения, видно в [каталоге](/models) (фильтр «принимает изображения»).
* Пропишите так каждую модель, которой будете отправлять картинки.
После правки перезагрузите окно: `Ctrl+Shift+P` → **Developer: Reload Window**.
Со стороны Hubris vision работает в полном объёме: `/v1/chat/completions` принимает изображения и публичным URL, и base64 data URL — подробности в [Изображения на вход](/docs/features/vision). Ограничение здесь чисто на стороне Kilo.
### Kilo не справляется с задачей / зацикливается
* Слабая модель плохо ведёт себя как агент. Попробуйте более сильную из каталога с явной поддержкой tool calling.
* Уменьшите размер задачи: дайте Kilo более узкую цель.
## Что дальше
* [Каталог моделей](/models) — выбор по поддержке tool calling.
* [Управление API-ключами](/keys) — отдельный ключ под Kilo с дневным лимитом.
* [Расходы](/usage) — детализация сессий.
* [Cline](/docs/integrations/cline), [Roo Code](/docs/integrations/roo-code) — альтернативные VS Code агенты.
---
# Make (/docs/integrations/make)
> Подключение Hubris к Make (бывший Integromat) — приложение с модулями «Создать текстовый ответ» и «Сгенерировать изображение», готовый сценарий для блога на WordPress и универсальный путь через HTTP-модуль.
[Make](https://www.make.com) (бывший Integromat) — визуальный конструктор автоматизаций: сценарий собирается из модулей мышкой, без кода. Hubris подключается двумя способами:
* **Готовое приложение Hubris** — два модуля, «Создать текстовый ответ» и «Сгенерировать изображение». Список моделей подтягивается прямо из вашего аккаунта, изображение приходит готовым файлом. Это основной путь.
* **HTTP-модуль** — универсальный запасной вариант: работает с любым нашим эндпоинтом, включая те, которых нет в приложении (озвучка, транскрибация, эмбеддинги, видео).
Приложение для Make собрала студия [web-steps.ru](https://web-steps.ru/) — не команда Hubris. Мы отвечаем за API и за то, что описано ниже; вопросы по устройству самого приложения, доработки и поддержка — на стороне автора.
## Требования
* Аккаунт [Make](https://www.make.com)
* Аккаунт на [hubris.pw](https://hubris.pw/dashboard), пополненный баланс и API-ключ формата `sk-gw-...`
Приложение ставится на аккаунт любого тарифа. Учтите только: Make относит сторонние приложения к платным категориям, и на бесплатном тарифе запуск сценария может упереться в сообщение вида «premium app» — это ограничение Make, а не наше. Тогда пригодится HTTP-модуль (см. ниже) — он штатный и такого ограничения не имеет.
## Установка приложения
Приложение не опубликовано в общем каталоге Make — оно ставится по ссылке-приглашению.
1. Войдите в свой аккаунт Make.
2. Откройте ссылку: **[Установить приложение Hubris](https://us2.make.com/app/invite/34a849c2d356583d3a4663d59a5c5b13)**
3. Подтвердите установку. Приложение появится в вашей организации — дальше оно ищется в списке модулей по слову `Hubris`.
Ссылка ведёт на зону `us2`. Если ваш аккаунт живёт в другой зоне (`eu1`, `eu2`, `us1` — видно в адресной строке Make), сначала войдите в Make, а потом откройте ссылку: установка идёт в ту организацию, которая выбрана в вашей сессии.
## Создание подключения
Подключение (Connection) создаётся один раз и потом переиспользуется во всех модулях сценария.
1. Добавьте в сценарий любой модуль Hubris.
2. В поле **Connection** нажмите **Add**.
3. Заполните:
| Поле | Значение |
| ------------------- | ---------------------------------------------------------------------------------- |
| **Connection name** | любое понятное имя, например `Hubris` |
| **API Key** | ваш ключ формата `sk-gw-...` — создать в [личном кабинете](https://hubris.pw/keys) |
4. Сохраните. Make сразу проверит ключ запросом к нашему каталогу моделей — если подключение создалось, ключ рабочий.
Ключ живёт в хранилище подключений Make и в самом сценарии не показывается. Разумно завести под Make отдельный ключ с [дневным лимитом](/keys) — тогда ошибка в сценарии (например, бесконечный цикл) не съест весь баланс.
## Модуль «Создать текстовый ответ»
Отправляет запрос к языковой модели и возвращает текст. Подходит для генерации статей и писем, кратких пересказов, классификации обращений, извлечения данных из текста.
### Параметры
| Поле | Что задаёт |
| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Провайдер** | Семейство моделей: ChatGPT, Claude, Gemini, DeepSeek, Llama, Mistral, Qwen, Grok, Kimi, GLM. Это фильтр для следующего поля. |
| **Model** | Конкретная модель. Список подгружается из вашего аккаунта Hubris при открытии модуля — то есть всегда актуальный. |
| **Prompt** | Основной текст запроса. Поле многострочное; чтобы подставлять данные предыдущих шагов, включите режим сопоставления (иконка **Map**) и вставляйте переменные вида `{{1.text}}`. |
| **Prompt (2-е сообщение)** | Второе сообщение с ролью `system`. Спрятано под **Show advanced settings**. Удобно, когда одна часть промпта постоянная (правила, тон, формат), а вторая приходит из сценария. Если поле заполнено, первое сообщение тоже отправляется как `system`. |
| **Role** | Роль первого сообщения: `user`, `system` или `assistant`. По умолчанию `user`. |
| **Max Tokens** | Потолок длины ответа в токенах. По умолчанию 6000. |
| **Temperature** | Разброс ответов, от 0 до 2. Ниже — стабильнее и суше, выше — свободнее. По умолчанию 1. |
| **Top P** | Альтернативный способ ограничить разброс, от 0 до 1. По умолчанию 1. Обычно трогают либо Temperature, либо Top P. |
| **Parse JSON Response** | Требует от модели ответ строго в JSON и разбирает его в поле `Result`. Подробности ниже. |
| **Response Format** | Селектор `Text` / `JSON Object`. Фактически формат задаёт галка **Parse JSON Response**, этот селектор на запрос не влияет — ориентируйтесь на галку. |
Переменной подставляются только **Prompt** и **Prompt (2-е сообщение)**. Всё остальное — включая **Провайдер** и **Model** — задаётся руками в панели модуля. Если модель нужно переключать по ходу сценария (например, дешёвая для простых заявок, сильная для сложных), поставьте перед модулями **Router** с двумя ветками или используйте HTTP-модуль.
### Что возвращает
| Поле | Значение |
| ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Text Response** | Ответ модели текстом — то, что нужно в 90 % сценариев. |
| **Result** | Разобранный JSON (только при включённом **Parse JSON Response**). |
| **Model** | Идентификатор модели, которая реально ответила. |
| **ID** | Идентификатор ответа модели (`chatcmpl-...`) — пригодится в переписке с поддержкой. |
| **Finish Reason** | Почему модель остановилась: `stop` — договорила сама, `length` — упёрлась в Max Tokens. |
| **Usage → Prompt / Completion / Total Tokens** | Сколько токенов ушло на запрос и ответ. |
| **Usage → Cost** | Стоимость запроса **в копейках**. `1250` — это 12,50 ₽. Удобно складывать за прогон и писать в таблицу. Изредка поле приходит пустым (модель не отдала стоимость) — списание при этом всё равно происходит, точную сумму смотрите в [логах](/logs). |
### Ответ в формате JSON
Когда нужен не связный текст, а структура (заголовок, описание, теги), включите **Parse JSON Response**. Эта галка — единственный переключатель формата: с ней модуль требует от модели строгий JSON и кладёт разобранный ответ в поле `Result`; без неё запрос уходит за обычным текстом, даже если селектор **Response Format** стоит в `JSON Object`.
Оговорка про `Result`: в списке для сопоставления у него показаны заранее заданные ключи — `filename`, `title`, `alt`, `caption`, `info`. Их зашил автор приложения под свой сценарий. Если структура другая, поля не появятся в подсказках Make, но данные приходят целиком — обращайтесь к ним выражением вида `{{2.result.my_field}}`. Когда полей много и хочется видеть их в сопоставлении, добавьте после модуля штатный **JSON → Parse JSON** и разберите им `Text Response`.
В самом промпте всегда описывайте нужную структуру словами и приводите пример — модель ориентируется на него. Строгий JSON поддерживают не все модели: если выбранная не понимает такой режим, запрос вернётся ошибкой — тогда выключите галку и опишите формат словами либо возьмите другую модель из [каталога](/models). Подробнее — в разделе [Структурированный вывод](/docs/features/structured-output).
## Модуль «Сгенерировать изображение»
Рисует картинку по текстовому описанию и отдаёт её **готовым файлом** — дальше его можно сразу положить в Google Drive, загрузить в WordPress, отправить в Telegram или приложить к письму, без промежуточных модулей.
### Параметры
| Поле | Что задаёт |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Провайдер** | Семейство: Gemini Image, GPT Image, FLUX, Recraft, Seedream, Riverflow, Grok Imagine, MiniMax, Qwen Image, Wan. У MiniMax и Wan в каталоге сейчас нет моделей, рисующих картинки, — список моделей там будет пустым. |
| **Model** | Конкретная модель, список подгружается из вашего аккаунта. |
| **Prompt** | Описание картинки. Поддерживает сопоставление — можно собирать из данных предыдущих шагов. |
| **Quality** | `Medium` — размер 1K, `High` — 2K. Влияет на стоимость. |
| **Aspect Ratio** | `1:1`, `16:9`, `9:16`, `4:3`, `3:4`, `2:3`, `3:2`. Соотношения `2:3` и `3:2` отправляются как ближайшие поддерживаемые — `3:4` и `4:3`. |
Quality и Aspect Ratio рассчитаны на линейку Gemini Image — там они работают предсказуемо. У остальных линеек размер и пропорции задаёт сама модель: FLUX, Recraft, Seedream и прочие эти поля игнорируют, у GPT Image соотношение сторон тоже может не примениться. Когда пропорции критичны, проверьте результат на одном прогоне.
### Что возвращает
| Поле | Значение |
| ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Data** | Само изображение — двоичный файл. Именно его принимают модули Google Drive, Dropbox, WordPress, Telegram, Email. |
| **File Name** | Имя файла — `post-image` с расширением. Одинаковое на каждом прогоне: если складываете картинки в одну папку, переименуйте файл следующим модулем (например, по теме поста). |
| **MIME Type** | Тип содержимого. У большинства линеек это всегда `image/png`. |
| **Text Comment** | Текстовый комментарий модели к картинке, если она его дала. |
| **Model** | Модель, которая рисовала. |
| **Endpoint Used** | Каким путём ушёл запрос: `chat` или `images`. Полезно при разборе ошибок. |
| **First Image URL** / **Image URLs** | Ссылка на изображение (у части моделей это `data:`-строка, а не веб-адрес). |
Модели Gemini Image и GPT Image приложение вызывает через `/v1/chat/completions`, остальные — через `/v1/images/generations`. Это видно в поле **Endpoint Used** и объясняет, почему набор влияющих настроек у линеек разный. Подробнее про оба пути — в разделе [Генерация изображений](/docs/features/image-generation).
## Готовый сценарий: блог на WordPress
Чтобы не собирать конвейер с нуля, возьмите готовый: **[скачать wordpress-seo.blueprint.json](/make/wordpress-seo.blueprint.json)**. Сценарий берёт тему из Google Таблицы, пишет статью, рисует обложку, заливает её в медиатеку сайта и создаёт запись в WordPress.
Сценарий и приложение для Make сделала студия [web-steps.ru](https://web-steps.ru/).
Цепочка из девяти модулей:
1. **Google Sheets → Get a Cell** — очередная тема из таблицы.
2. **Hubris → Создать текстовый ответ** — статья по теме: заголовки, списки, блок вопросов, финальный призыв со ссылкой.
3. **Hubris → Создать текстовый ответ** — промпт для обложки по той же теме.
4. **Hubris → Сгенерировать изображение** — сама обложка.
5. **Hubris → Создать текстовый ответ** — имя файла, заголовок, alt-текст, подпись и описание картинки (ответ в JSON).
6. **WordPress → Create a Media Item** — обложка попадает в медиатеку с заполненными alt и подписью.
7. **Markdown → Markdown to HTML** — статья переводится из Markdown в HTML.
8. **WordPress → Create a Post** — запись создаётся **черновиком**, обложка привязывается к ней.
9. **Google Sheets → Delete a Row** — отработанная тема вычёркивается из таблицы.
### Как импортировать
1. В Make: **Create a new scenario** → меню `···` в правом нижнем углу → **Import Blueprint** → выберите скачанный файл.
2. Откройте каждый модуль и подставьте свои подключения: Hubris, Google Sheets, WordPress.
3. В обоих модулях Google Sheets выберите свою таблицу — в файле она намеренно не задана — и проверьте лист: первый модуль настроен на лист с именем `Лист1`, последний — на первый лист по счёту. Формат таблицы простой: темы идут списком в столбце `A`, сценарий читает ячейку `A1`, а в конце удаляет первую строку — следующая тема поднимается наверх сама.
4. Во втором модуле (статья) раскройте **Show advanced settings** и в поле **Prompt (2-е сообщение)** замените `https://example.com` и анкор на свои — иначе в конце статьи окажется ссылка-заглушка.
5. В модуле **Create a Post** проверьте автора и рубрику: в файле стоят первый автор и первая рубрика сайта. На чужом WordPress это почти наверняка не то, что нужно.
6. Запустите один прогон и посмотрите черновик в WordPress. Когда результат устроит, поменяйте в модуле **Create a Post** статус с `draft` на `publish`.
Модели в файле проставлены рабочие, но их можно менять: для более выразительного текста — модель посильнее из [каталога](/models), для картинок — любая image-модель.
Стоимость каждого прогона видна в поле **Usage → Cost** и в [логах](/logs) — там же можно отфильтровать запросы по ключу, если под Make заведён отдельный.
## Универсальный путь: HTTP-модуль
Приложение закрывает текст и картинки. Всё остальное — озвучку, транскрибацию, эмбеддинги, видео, вызов инструментов, картинку на входе — можно взять штатным модулем **HTTP → Make a request**:
```
URL: https://api.hubris.pw/v1/chat/completions
Method: POST
Headers:
Authorization: Bearer sk-gw-...
Content-Type: application/json
Body type: Raw → JSON (application/json)
Request content:
{
"model": "anthropic/claude-haiku-4.5",
"messages": [
{"role": "user", "content": "{{1.text}}"}
]
}
```
Включите **Parse response**, чтобы Make сам разобрал JSON — тогда ответ доступен как `{{2.choices[1].message.content}}` (в Make нумерация элементов массива начинается с единицы). Таймаут в настройках модуля стоит поднять до 300 секунд: длинные тексты и рассуждающие модели отвечают не мгновенно.
Полный список эндпоинтов — в [справочнике API](/docs/api/overview).
## Рекомендуемые модели
Сценарии Make — это обычно поток однотипных запросов, где важнее цена и скорость, чем предельное качество рассуждений.
| Модель | Когда уместна |
| ------------------------------- | ------------------------------------------------- |
| `anthropic/claude-haiku-4.5` | классификация, короткие ответы, извлечение данных |
| `openai/gpt-4o-mini` | универсальный недорогой выбор |
| `anthropic/claude-sonnet-4.5` | статьи, письма, тексты, где виден стиль |
| `google/gemini-2.5-flash-image` | быстрые иллюстрации и обложки |
| `black-forest-labs/flux.2-pro` | фотореалистичные изображения |
Полный каталог с ценами и фильтрами — на странице [Модели](/models).
## Решение проблем
### Список моделей пустой или крутится вечно
Приложение запрашивает модели по вашему ключу. Пустой список означает, что запрос не прошёл: проверьте подключение (пересоздайте его с ключом заново) и то, что ключ не отозван в [личном кабинете](/keys). Ещё вариант — у выбранного провайдера просто нет моделей нужного типа: например, у Recraft нет текстовых.
### «Неверный API-ключ Hubris» (401)
Ключ не подошёл. Он должен начинаться с `sk-gw-` и не содержать пробелов и переносов строк — при копировании из письма или мессенджера легко прихватить лишнее.
### «Недостаточно баланса» (402)
Баланс кончился. Пополнить — в [личном кабинете](/billing), минимум 300 ₽. Полезно включить [автопополнение](/billing), чтобы ночной сценарий не встал на середине.
### «Модель не найдена» (404)
Модель убрали из каталога или переименовали — такое случается, когда производитель снимает версию с публикации. Откройте поле **Model** и выберите модель заново.
### «Превышен дневной лимит API-ключа» (429)
Обычно это суточный лимит расхода, заданный для ключа: поднять или снять — в [настройках ключа](/keys), лимит считается по скользящим 24 часам. Тот же текст приложение показывает и когда частоту запросов ограничил сам производитель модели (частая история на бесплатных моделях) — если лимита на ключе нет или он далеко не выбран, просто повторите запуск позже или возьмите другую модель.
### Сценарий падает по таймауту
Ограничение на один запрос в модулях приложения — 300 секунд, этого хватает почти всегда. Если используете HTTP-модуль, проверьте его собственное поле **Timeout**: по умолчанию там куда меньше. И помните про ограничение Make на длительность всего сценария на вашем тарифе.
### Ответ обрывается на полуслове
**Finish Reason** = `length` означает, что упёрлись в **Max Tokens**. Поднимите значение — для длинной статьи 6000 может не хватить.
## Что дальше
* [Каталог моделей](/models) — какие модели доступны и сколько стоят.
* [Управление ключами](/keys) — отдельный ключ под Make с дневным лимитом.
* [Расходы](/usage) — во сколько обошлись прогоны сценариев.
* [Структурированный вывод](/docs/features/structured-output) — как получать стабильный JSON.
* [Генерация изображений](/docs/features/image-generation) — параметры моделей и оба пути вызова.
---
# MCP-сервер (/docs/integrations/mcp)
> Подключите Hubris к AI-агентам через Model Context Protocol — без хардкода API.
> **Beta.** Фича стабильна, но набор инструментов и формат ответов могут уточняться по мере фидбэка.
Model Context Protocol — открытый стандарт от Anthropic. Через MCP агенты (Claude Desktop, Claude Code, Cline, Cursor) получают доступ к внешним сервисам единообразным способом: каталог моделей, баланс, отправка запросов в LLM — всё через одно подключение.
Hubris-MCP даёт агенту:
* **Каталог моделей** с ценой в рублях, фильтрами по capability, цене и длине контекста.
* **Баланс аккаунта** — чтобы агент знал, сколько ему доступно.
* **Запросы к LLM** через `chat_complete` (полный паритет с `/v1/chat/completions`).
* **Готовый prompt** `compare-models` для сравнения моделей под конкретную задачу.
## Что такое MCP
MCP позволяет AI-агентам вызывать инструменты и читать ресурсы внешних сервисов без необходимости хранить API-ключ напрямую в промпте или прописывать кастомные интеграции. Агент узнаёт о доступных инструментах автоматически при подключении к серверу.
Hubris реализует MCP через **Streamable HTTP transport** — единственный URL без дополнительной инфраструктуры.
## Требования
* Аккаунт на [hubris.pw](https://hubris.pw/dashboard) и API-ключ формата `sk-gw-…` (создать на [/keys](https://hubris.pw/keys)).
* Положительный баланс — пополнить можно [здесь](https://hubris.pw/billing).
* MCP-клиент: Claude Desktop, Claude Code, Cline, Cursor, любой другой совместимый.
* Для Claude Desktop дополнительно — установленный Node.js (нужен `npx` для запуска шима `mcp-remote`).
## Подключение
### Claude Desktop
> Claude Desktop пока не умеет напрямую подключаться к удалённым HTTP-MCP-серверам — он работает только со stdio-серверами. Для подключения к Hubris нужен локальный шим [`mcp-remote`](https://github.com/geelen/mcp-remote): он стартует как stdio-процесс и передаёт все вызовы на `https://api.hubris.pw/mcp`.
> **Не используйте окно «Add custom connector (BETA)»** в claude.ai / Claude Desktop. Эта форма рассчитана на сервера с OAuth 2.1 (у Hubris пока Bearer-авторизация), и для произвольных HTTP-MCP-серверов известно падает с ошибкой `Couldn't reach the MCP server. … ofid_…` ещё до отправки запроса нам — это [баг на стороне Anthropic](https://github.com/anthropics/claude-ai-mcp/issues). Подключайтесь только через JSON-конфиг ниже.
Откройте файл конфигурации:
* **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
* **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
* **Linux:** `~/.config/Claude/claude_desktop_config.json`
Добавьте секцию `mcpServers`:
```json
{
"mcpServers": {
"hubris": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://api.hubris.pw/mcp",
"--transport",
"http-only",
"--header",
"Authorization:${AUTH_TOKEN}"
],
"env": {
"AUTH_TOKEN": "Bearer sk-gw-..."
}
}
}
}
```
Подставьте свой ключ в `AUTH_TOKEN` (формат: `Bearer sk-gw-…`). Заголовок в `--header` передаётся как `Authorization:${AUTH_TOKEN}` без пробела вокруг двоеточия — это рекомендация `mcp-remote` из-за особенностей экранирования в некоторых клиентах.
Перезапустите Claude Desktop. В чате появится индикатор подключённого MCP-сервера и список доступных инструментов.
### Claude Code
```bash
claude mcp add --transport http hubris https://api.hubris.pw/mcp \
--header "Authorization: Bearer sk-gw-..."
```
Проверьте, что сервер добавлен:
```bash
claude mcp list
```
### Cline / Cursor / другие клиенты
Любой MCP-клиент с поддержкой Streamable HTTP transport подключается тем же URL и Bearer-ключом. Параметры те же:
| Поле | Значение |
| --------------------- | ------------------------------- |
| Transport | Streamable HTTP |
| URL | `https://api.hubris.pw/mcp` |
| Заголовок авторизации | `Authorization: Bearer sk-gw-…` |
Подробности — в документации вашего клиента.
## Инструменты
| Инструмент | Что делает |
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `models_list` | Полный каталог активных моделей с курсорной пагинацией. |
| `models_search` | Поиск по capability (vision / reasoning / tools / web\_search / audio\_in / audio\_out / image\_gen / transcription), цене, длине контекста. |
| `models_get_pricing` | Тариф конкретной модели в рублях. |
| `balance_get` | Текущий баланс аккаунта. |
| `chat_complete` | LLM-запрос к выбранной модели. Полный паритет с `/v1/chat/completions`. |
## Ресурсы
| URI | Содержимое |
| -------------------------- | -------------------------------------- |
| `hubris://catalog/models` | Каталог моделей одним JSON-документом. |
| `hubris://docs/quickstart` | Главная страница быстрого старта. |
## Безопасность
Bearer-ключ открывает доступ к балансу аккаунта. Любой, кто получит ключ, сможет отправлять запросы от вашего имени через MCP. Рекомендуем:
* Создать **отдельный ключ** для MCP в [/keys](https://hubris.pw/keys) — это упростит отзыв без затрагивания других интеграций.
* При компрометации — немедленно отозвать ключ в дашборде.
* Не публиковать конфиг MCP с ключом в открытых репозиториях.
OAuth-авторизация (без копирования ключа в конфиг) — на дорожной карте.
## Что дальше
* [Каталог моделей](https://hubris.pw/models) — все активные модели и цены в рублях.
* [POST /v1/chat/completions](/docs/api/chat-completions) — API-референс LLM-запросов.
* [/keys](https://hubris.pw/keys) — управление API-ключами.
* [Биллинг](https://hubris.pw/billing) — пополнение баланса через СБП.
---
# n8n (/docs/integrations/n8n)
> Подключение Hubris к n8n — низкокодовая платформа автоматизации, в которой можно собирать workflow с участием LLM.
[n8n](https://n8n.io) — низкокодовая платформа автоматизации с тысячью готовых интеграций и поддержкой собственных нод. В workflow можно дёргать LLM — обрабатывать письма, классифицировать обращения, переводить, суммаризировать. Hubris подключается как обычный OpenAI-совместимый провайдер.
## Требования
* n8n self-hosted или n8n Cloud
* Аккаунт на [hubris.pw](https://hubris.pw/dashboard) и API-ключ
## Создание Credentials
В n8n настройка апстрима живёт не в самой ноде, а в общих Credentials — один раз создал, потом используешь во всех нодах.
1. Откройте раздел **Credentials** (боковое меню слева).
2. Нажмите **Add Credential** → найдите **OpenAI API**.
3. Заполните поля:
| Поле | Значение |
| ------------------- | ---------------------------------------------------------------------------------------- |
| **API Key** | ваш ключ Hubris (формат `sk-gw-...`) — взять в [личном кабинете](https://hubris.pw/keys) |
| **Base URL** | `https://api.hubris.pw/v1` |
| **Organization ID** | оставьте пустым |
4. Дайте credential осмысленное имя, например `Hubris`. Сохраните.
## Использование в workflow
Дальше credential `Hubris` доступен во всех нодах, которые умеют работать с OpenAI-совместимым API.
### OpenAI Chat Model (для AI Agent / Chains)
В AI-нодах (LangChain-агенты, цепочки):
1. Добавьте ноду **OpenAI Chat Model**.
2. В поле **Credential to connect with** выберите свой `Hubris`.
3. В поле **Model** введите идентификатор модели из нашего [каталога](/models) — например `anthropic/claude-haiku-4.5`.
Эта нода соединяется с другими AI-нодами (Tool, Memory, Output Parser) — n8n собирает агент из ваших шагов.
### OpenAI (Message a model)
Если нужен простой запрос без агентной обвязки:
1. Добавьте ноду **OpenAI** → действие **Message a model**.
2. **Credential** → `Hubris`.
3. **Model** — введите ID вручную (n8n обычно подгружает список из апстрима; у нас это работает, но проверьте, что нода показывает наши модели).
4. Соберите промпт: можно через **Messages** (multi-turn) или **Simple** (один user-prompt).
### HTTP Request (на всякий случай)
Если хочется полного контроля над запросом или прокинуть кастомный параметр:
```
Method: POST
URL: https://api.hubris.pw/v1/chat/completions
Authentication: Generic Credential Type → Header Auth
Name: Authorization
Value: Bearer sk-gw-...
Body (JSON):
{
"model": "anthropic/claude-haiku-4.5",
"messages": [
{"role": "user", "content": "{{ $json.prompt }}"}
]
}
```
Удобно когда нужен `response_format: { type: "json_schema" }` или другие параметры, которых нет в UI ноды OpenAI.
## Рекомендуемые модели
n8n-workflow обычно — серия однотипных запросов: классифицировать, перевести, суммаризировать. Здесь главное — цена и скорость, не максимальное качество reasoning.
| Модель | Когда уместна |
| ---------------------------- | ---------------------------------------------------------- |
| `anthropic/claude-haiku-4.5` | быстрая классификация, простое извлечение данных |
| `openai/gpt-4o-mini` | универсальный недорогой выбор, поддержка structured output |
| `google/gemini-2.0-flash` | мультимодальные сценарии (картинки + текст) |
Полный список с фильтрами по цене и возможностям — в [каталоге](/models).
## Тяжёлые / агентные сценарии
Если workflow строит долгого AI-агента с многошаговыми инструментами — типа «найди в письме компанию → сходи в CRM → собери ответ» — модели с поддержкой tool calling показывают себя гораздо лучше. Возьмите Claude Sonnet/Opus или GPT-4o из каталога, и не забудьте включить **tool calling** в настройках ноды.
См. также наш гид по [вызову инструментов](/docs/features/tool-calling) — там разобран жизненный цикл tool-сессии.
## Решение проблем
### Список моделей в OpenAI-ноде пустой
* Это значит, нода не смогла дёрнуть `GET /v1/models` с вашим credential. Проверьте Base URL (`https://api.hubris.pw/v1`, обязательно с `/v1` в конце) и валидность ключа.
* Можно временно ввести Model ID вручную в режиме **Manual** — работает даже без подгрузки списка.
### 401 при первом запросе
* API-ключ не прошёл. Перепроверьте, что начинается с `sk-gw-` и не содержит пробелов.
### 402 / `insufficient_balance`
* Закончился баланс. Пополните в [личном кабинете](/billing).
### 429 / `daily_limit_exceeded`
* Сработал суточный лимит ключа. Можно увеличить в [настройках ключа](/keys) или дождаться сброса (24 часа от первого запроса дня).
### Workflow «зависает» на AI-ноде
* LLM-запросы могут идти до нескольких минут (длинный контекст, reasoning-модели). В настройках ноды поднимите **Timeout** (по умолчанию 5 минут).
## Что дальше
* [Каталог моделей](/models) — какие именно ID использовать в нодах.
* [Управление API-ключами](/keys) — отдельный ключ под n8n с лимитом.
* [Расходы](/usage) — посмотреть, во сколько обошёлся прогон workflow.
* [Структурированный вывод](/docs/features/structured-output) — стабильные JSON-ответы для дальнейших шагов workflow.
---
# OAuth 2.0 + PKCE (/docs/integrations/oauth)
> Стандартный OAuth flow для подключения сторонних приложений и MCP-клиентов (Claude Desktop, Cursor, Cline) без копирования root-ключа.
Hubris реализует **OAuth 2.0 Authorization Code + PKCE** (RFC 6749 + 7636 + 9700 BCP) для авторизации сторонних приложений. Это даёт два преимущества:
1. **Sandboxed-доступ**: токен выдаётся под выбранные scopes и опциональный дневной лимит. Если приложение попросит чат — оно не сможет читать ваш баланс.
2. **MCP-connector совместимость**: Claude Desktop, Claude.ai, Cursor и любой MCP-клиент с native «Add custom connector» UI проходят discovery + DCR + авторизацию автоматически, без `mcp-remote` шима.
## Discovery (RFC 8414 + RFC 9728)
Клиент начинает с одного из:
```http
GET https://api.hubris.pw/.well-known/oauth-protected-resource
GET https://hubris.pw/.well-known/oauth-authorization-server
```
Второй — основной, содержит все endpoints. Пример ответа:
```json
{
"issuer": "https://hubris.pw",
"authorization_endpoint": "https://hubris.pw/oauth/authorize",
"token_endpoint": "https://hubris.pw/oauth/token",
"registration_endpoint": "https://hubris.pw/oauth/clients",
"revocation_endpoint": "https://hubris.pw/oauth/revoke",
"scopes_supported": ["chat:write", "models:read", "balance:read"],
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code", "refresh_token"],
"code_challenge_methods_supported": ["S256"],
"token_endpoint_auth_methods_supported": ["none"]
}
```
`token_endpoint_auth_methods_supported: ["none"]` означает что мы поддерживаем только public clients (PKCE-only, без client\_secret).
## Scopes
| Scope | Доступ |
| -------------- | ------------------------------------------------------------------------------ |
| `chat:write` | `/v1/chat/completions`, `/v1/responses`, `/v1/messages`, `/mcp` chat\_complete |
| `models:read` | `/v1/models`, `/mcp` models\_list+search+get\_pricing |
| `balance:read` | чтение баланса (`/api/internal/me`, `/mcp` balance\_get) |
Приложение запрашивает подмножество в `?scope=...` (space-separated). Юзер видит и подтверждает на consent-странице.
## Токены
* **Access token**: формат `hbr-at-<32 hex>`. TTL **1 час**. Идёт как `Authorization: Bearer ...` на `/v1/*` и `/mcp`.
* **Refresh token**: формат `hbr-rt-<48 hex>`. TTL **90 дней**. Принимается **только** на `POST /oauth/token`.
* **Rotation**: каждый refresh выпускает новый refresh + новый access. Старый refresh имеет 30-секундное grace-окно (защита от race-conditions при параллельных запросах). После окна — обнаружение reuse и revoke всей семьи токенов.
## Поток (Python пример)
```python
import requests, hashlib, base64, secrets
# 1. Регистрация client (one-time per app)
reg = requests.post('https://hubris.pw/oauth/clients', json={
'client_name': 'My App',
'redirect_uris': ['http://localhost:8765/callback'],
'scope': 'chat:write models:read',
})
client_id = reg.json()['client_id']
# 2. PKCE
verifier = secrets.token_urlsafe(32)
challenge = base64.urlsafe_b64encode(
hashlib.sha256(verifier.encode()).digest()
).rstrip(b'=').decode()
# 3. Браузерный flow — открыть в браузере
auth_url = (
'https://hubris.pw/oauth/authorize'
f'?client_id={client_id}'
'&redirect_uri=http://localhost:8765/callback'
'&response_type=code'
f'&code_challenge={challenge}&code_challenge_method=S256'
'&scope=chat:write+models:read'
'&state=somestate'
)
# Юзер видит consent, кликает «Разрешить» → редирект на callback?code=...&state=...
# 4. Обмен кода на токены
tok = requests.post('https://hubris.pw/oauth/token', data={
'grant_type': 'authorization_code',
'code': received_code,
'redirect_uri': 'http://localhost:8765/callback',
'client_id': client_id,
'code_verifier': verifier,
}).json()
access_token = tok['access_token']
# 5. Используем
resp = requests.post(
'https://api.hubris.pw/v1/chat/completions',
headers={'Authorization': f'Bearer {access_token}'},
json={
'model': 'openai/gpt-4o-mini',
'messages': [{'role': 'user', 'content': 'привет'}],
},
)
```
## Дневной лимит
Приложение может передать `daily_limit_kopecks=50000` в query к `/oauth/authorize`. Юзер видит запрошенную сумму на consent-странице и может **снизить** перед подтверждением. Если приложение не передало — default **500 ₽/день**.
После выдачи токена лимит работает идентично api\_keys daily-limit'у — 429 при превышении с метаданными в `/usage`.
## Безопасность
Hubris следует RFC 9700 BCP:
* **PKCE S256 обязательно** — `plain` метод не поддерживается.
* **Refresh rotation + reuse detection** — обнаруженный replay старого refresh после grace-окна revoke'ит всю семью токенов.
* **redirect\_uri exact-match** после нормализации (lowercase scheme/host, case-sensitive path) — без prefix-wildcards.
* **Reserved-word check** на client\_name (`hubris`, `official`, `admin`, `support` запрещены как substring) — anti-phishing baseline.
* **DCR rate-limit**: 5 регистраций/IP/час + 100/день глобально.
* **Все токены — sha256-хэши** в БД. Plain text никогда не хранится.
## MCP integration
Совместимость с native «Add custom connector» UI в Claude Desktop / Claude.ai:
1. В клиенте указываешь URL: `https://api.hubris.pw/mcp`
2. Клиент сам идёт по discovery → DCR → /authorize (открывает браузер)
3. Юзер видит consent: «Claude Desktop запрашивает доступ chat:write, models:read, balance:read»
4. После «Разрешить» — обратно в клиент, токены выпущены, инструменты подключены
Legacy `sk-gw-` ключи продолжают работать на `/mcp` без изменений (back-compat).
## Phase B (deferred)
* `/profile/connected-apps` UI — список авторизованных приложений + revoke
* Per-scope deny (сейчас all-or-nothing на consent-странице)
* Step-up auth (re-OTP при выдаче sensitive scopes если auth >7д назад)
* Domain verification для verified-badge при DCR
* `embeddings:write`, `usage:read` scopes
* OAuth для /v1/embeddings + /v1/usage routes
## Ссылки
* RFC 6749 — OAuth 2.0
* RFC 7009 — Token Revocation
* RFC 7591 — Dynamic Client Registration
* RFC 7636 — PKCE
* RFC 8414 — Authorization Server Metadata
* RFC 9700 — OAuth 2.0 Security Best Current Practice
* RFC 9728 — Protected Resource Metadata
* MCP Authorization: [https://modelcontextprotocol.io/specification/draft/basic/authorization](https://modelcontextprotocol.io/specification/draft/basic/authorization)
---
# OpenClaw (/docs/integrations/openclaw)
> Подключение Hubris к OpenClaw — open-source AI-ассистенту для Telegram, WhatsApp, Discord и Slack.
[OpenClaw](https://openclaw-ai.com) — open-source платформа для AI-ассистента в мессенджерах. Поднимает локальный gateway, маршрутизирует сообщения из Telegram, WhatsApp, Discord, Slack к LLM-провайдеру. Hubris подключается как custom provider через OpenAI-совместимый API.
## Требования
* Установленный OpenClaw (см. [инструкцию по установке](https://openclaw-ai.com/en/install))
* Node.js 22+
* Аккаунт на [hubris.pw](https://hubris.pw/dashboard) и API-ключ
## Подключение к Hubris
Конфиг OpenClaw — `~/.openclaw/openclaw.json` (JSON5, поддерживает комментарии и trailing commas).
Откройте файл:
```bash
nano ~/.openclaw/openclaw.json
```
или через встроенный инструмент:
```bash
openclaw configure
```
В секции `models.providers` добавьте провайдер `hubris`:
```json5
{
models: {
mode: "merge",
providers: {
hubris: {
baseUrl: "https://api.hubris.pw/v1",
apiKey: "${HUBRIS_API_KEY}",
api: "openai-completions",
models: [
{ id: "anthropic/claude-haiku-4.5", name: "Claude Haiku" },
{ id: "openai/gpt-4o-mini", name: "GPT-4o mini" },
{ id: "google/gemini-2.0-flash", name: "Gemini Flash" },
],
},
},
},
}
```
Поле `api: "openai-completions"` обязательно — без него OpenClaw не определит тип API.
Ключ положите в `~/.openclaw/.env`:
```
HUBRIS_API_KEY=sk-gw-...
```
## Модель по умолчанию
В секции `agents.defaults` укажите основную модель и опционально fallback'и:
```json5
{
agents: {
defaults: {
model: {
primary: "hubris/anthropic/claude-haiku-4.5",
fallbacks: ["hubris/openai/gpt-4o-mini"],
},
models: {
"hubris/anthropic/claude-haiku-4.5": { alias: "Claude" },
"hubris/openai/gpt-4o-mini": { alias: "GPT" },
"hubris/google/gemini-2.0-flash": { alias: "Gemini" },
},
},
},
}
```
Формат ссылки: `/`, где `` — имя из секции `providers` (у нас `hubris`).
## Применение конфига
Если включён hot-reload (по умолчанию) — изменения подхватятся автоматически. Если нет:
```bash
openclaw gateway restart
```
## Переключение моделей в чате
Прямо в любом канале:
```
/model hubris/openai/gpt-4o-mini
```
Или через CLI:
```bash
openclaw models set hubris/google/gemini-2.0-flash
```
Список доступных:
```bash
openclaw models list
```
## Подключение каналов
В `channels` секции укажите параметры мессенджеров. Пример для Telegram:
```json5
{
channels: {
telegram: {
dmPolicy: "allowlist",
botToken: "<токен бота от @BotFather>",
allowFrom: ["<ваш Telegram ID>"],
groupPolicy: "allowlist",
},
},
plugins: {
entries: {
telegram: { enabled: true },
},
},
}
```
Аналогично для WhatsApp, Discord, Slack — детали в [документации OpenClaw](https://openclaw-ai.com/en/docs).
## Рекомендуемые модели
OpenClaw как чат-агент в мессенджерах хорошо работает на быстрых моделях. Для агентного сценария с инструментами — более крупная модель из каталога.
| ID | Когда уместна |
| ---------------------------- | --------------------------------------------- |
| `anthropic/claude-haiku-4.5` | быстрые типовые ответы |
| `openai/gpt-4o-mini` | универсальный недорогой выбор |
| `google/gemini-2.0-flash` | мультимодальные сценарии (изображения в чате) |
Полный список — в [каталоге моделей](/models).
## Решение проблем
### `'No API provider registered for api: undefined'`
В секции провайдера должно быть поле `api: "openai-completions"` — без него OpenClaw не понимает тип API.
### `Invalid API key` (401)
* Проверьте формат ключа (`sk-gw-` + hex).
* Если используете `${HUBRIS_API_KEY}`, убедитесь, что переменная действительно установлена в `~/.openclaw/.env`.
### Модель не находится
* Проверьте, что ID в `models[]` точно совпадает с одним из [каталога](/models).
* Проверьте, что та же модель есть в `agents.defaults.models` — это allowlist.
* Формат: `hubris/`.
### Gateway не запускается после правки конфига
OpenClaw строго валидирует конфиг:
```bash
openclaw doctor
openclaw doctor --fix
```
### Медленные ответы
* Переключитесь на более быструю модель — Haiku или Flash.
* В настройках канала включите `streamMode: "partial"` для стриминговых ответов.
## Что дальше
* [Каталог моделей](/models) — список ID для конфига.
* [Управление API-ключами](/keys) — отдельный ключ под OpenClaw.
* [Расходы](/usage) — детализация сессий.
* [Hermes Agent](/docs/integrations/hermes-agent) — альтернативный агент с Telegram.
---
# OpenCode (/docs/integrations/opencode)
> Подключение терминального AI-агента OpenCode (sst.dev) к Hubris — Plan Mode, file-context, image drag-and-drop прямо в TUI.
[OpenCode](https://opencode.ai) — open-source AI-агент, работающий прямо в терминале как полноценный TUI. Понимает контекст проекта, помогает писать и править код, держит историю команд. Подключается к любому OpenAI-совместимому провайдеру через `opencode.json` в корне проекта.
## Требования
* macOS / Linux / WSL / Windows + современный терминал
* `curl` и `jq` в PATH
* Аккаунт на [hubris.pw](https://hubris.pw/dashboard) и API-ключ
## Установка
Самый простой способ — curl-инсталлятор:
```bash
curl -fsSL https://opencode.ai/install | bash
```
Альтернативы: `npm i -g opencode-ai`, Homebrew, Chocolatey, Docker-образ. Полный список — в [официальной документации OpenCode](https://opencode.ai/docs/install).
## Подключение к Hubris
OpenCode читает конфиг из `opencode.json` в корне проекта (или `~/.config/opencode/opencode.json` глобально).
Создайте файл со следующим содержимым:
```json
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"hubris": {
"npm": "@ai-sdk/openai-compatible",
"options": {
"baseURL": "https://api.hubris.pw/v1",
"apiKey": "{env:HUBRIS_API_KEY}"
},
"models": {
"anthropic/claude-haiku-4.5": { "name": "Claude Haiku" },
"openai/gpt-4o-mini": { "name": "GPT-4o mini" },
"google/gemini-2.0-flash": { "name": "Gemini Flash" }
}
}
}
}
```
Ключ кладите в переменную окружения (безопаснее, чем в файл):
```bash
export HUBRIS_API_KEY="sk-gw-..."
```
Запустите агента из папки проекта:
```bash
cd /path/to/your/project
opencode
```
## Команды и режимы
| Команда | Что делает |
| ------------------------- | ----------------------------------------------------- |
| `Tab` | переключение Plan Mode ↔ Execute Mode |
| `@filename` | подключить файл в контекст текущего запроса |
| drag-and-drop изображения | передать картинку как вход для мультимодальной модели |
| `/init` | сгенерировать стартовый план проекта |
| `/models` | переключить активную модель |
| `/undo` / `/redo` | откатить или повторить последнее изменение |
| `/share` | поделиться сессией через ссылку |
## Рекомендуемые модели
| ID | Когда подходит |
| ---------------------------- | ---------------------------------------------------- |
| `anthropic/claude-haiku-4.5` | быстрая работа над типовыми задачами |
| `openai/gpt-4o-mini` | универсальный выбор, недорого |
| `google/gemini-2.0-flash` | мультимодальные сценарии (drag-and-drop изображений) |
Для сложных рефакторингов и архитектурных задач — Claude Sonnet или Opus из [каталога](/models). С тяжёлыми моделями стоит работать в Plan Mode, чтобы проверить план перед исполнением.
## Контроль расходов
* Plan Mode (через `Tab`) — модель только описывает, что собирается делать, не исполняет. Хороший способ оценить сложность задачи и стоимость до запуска.
* Отдельный API-ключ для OpenCode с дневным лимитом в [настройках](/keys).
* Расход за сессию виден в [«Расходах»](/usage).
## Решение проблем
### `Invalid API key` при первом запуске
* Перепроверьте, что переменная `HUBRIS_API_KEY` экспортирована в текущей shell-сессии: `echo $HUBRIS_API_KEY`.
* Ключ должен начинаться с `sk-gw-`.
### Модели не показываются в `/models`
* Проверьте, что секция `models` в `opencode.json` содержит ID, существующие в нашем [каталоге](/models).
* Перезапустите `opencode` после правки конфига.
### Запрос «висит» / нет ответа
* Тяжёлые модели на сложных задачах могут думать минуты. Если зависло дольше — `Ctrl+C` и попробуйте более быструю модель.
## Что дальше
* [Каталог моделей](/models) — список доступных IDs.
* [Управление API-ключами](/keys) — отдельный ключ под OpenCode.
* [Расходы](/usage) — детализация сессий.
* [Cline](/docs/integrations/cline), [Roo Code](/docs/integrations/roo-code) — агенты для VS Code.
---
# Qwen Code CLI (/docs/integrations/qwen-code)
> Подключение Qwen Code — терминального AI-агента от Alibaba (форк gemini-cli) — к Hubris.
[Qwen Code](https://github.com/QwenLM/qwen-code) — терминальный AI-агент от Alibaba, форк `gemini-cli` от Google. Несмотря на название, поддерживает любые OpenAI-совместимые провайдеры — не только модели семейства Qwen. Минималистичный TUI, удобен для CLI-сценариев и автоматизации.
## Требования
* macOS / Linux / WSL / Windows
* Node.js 20+
* Аккаунт на [hubris.pw](https://hubris.pw/dashboard) и API-ключ
## Установка
```bash
npm install -g @qwen-code/qwen-code
```
После установки команда `qwen` должна быть доступна в PATH:
```bash
qwen --version
```
## Подключение к Hubris
Конфиг живёт в `~/.qwen/settings.json`. Создайте файл или дополните существующий:
```json
{
"selectedAuthType": "openai",
"openai": {
"baseUrl": "https://api.hubris.pw/v1",
"apiKey": "sk-gw-...",
"models": [
{
"id": "anthropic/claude-haiku-4.5",
"displayName": "Claude Haiku",
"contextWindowSize": 200000
},
{
"id": "openai/gpt-4o-mini",
"displayName": "GPT-4o mini",
"contextWindowSize": 128000
},
{
"id": "google/gemini-2.0-flash",
"displayName": "Gemini Flash",
"contextWindowSize": 1000000
}
]
}
}
```
`contextWindowSize` для каждой модели подсмотрите в [каталоге](/models) — это размер контекстного окна, на основе которого Qwen Code решает, когда обрезать историю диалога.
Альтернатива — переменные окружения:
```bash
export OPENAI_BASE_URL="https://api.hubris.pw/v1"
export OPENAI_API_KEY="sk-gw-..."
```
Запустите агента в папке проекта:
```bash
cd /path/to/your/project
qwen
```
## Переключение моделей
В рантайме:
```
/model anthropic/claude-haiku-4.5
```
Или при запуске:
```bash
qwen --model openai/gpt-4o-mini
```
## Рекомендуемые модели
| ID | Когда подходит |
| ---------------------------- | ------------------------------------------ |
| `anthropic/claude-haiku-4.5` | быстрая работа |
| `openai/gpt-4o-mini` | универсальный выбор |
| `google/gemini-2.0-flash` | мультимодальные сценарии, большой контекст |
Для сложных задач — Claude Sonnet/Opus или Qwen Coder из [каталога](/models).
## Решение проблем
### `qwen: command not found` после установки
* Проверьте, что глобальный bin-каталог npm в PATH: `npm bin -g` покажет, где. Добавьте этот путь в `$PATH`.
### `Invalid API key`
* Ключ в `settings.json` должен быть в кавычках, начинаться с `sk-gw-`.
* Если используете env vars, перезапустите терминал.
### Список моделей пустой / Qwen Code не видит модели
* Проверьте, что в `settings.json` секция `models` непустая и ID моделей корректные.
* При работе через env vars Qwen Code попытается дёрнуть `GET /v1/models` — убедитесь, что Base URL правильный.
### Длинный контекст вылетает с ошибкой
* Поле `contextWindowSize` в конфиге должно соответствовать реальному контексту модели. Загляните в карточку модели в [каталоге](/models).
## Что дальше
* [Каталог моделей](/models) — IDs и contextWindowSize для всех моделей.
* [Управление API-ключами](/keys) — отдельный ключ под Qwen Code.
* [Расходы](/usage) — детализация сессий.
* [OpenCode](/docs/integrations/opencode) — альтернативный терминальный агент.
---
# Roo Code (/docs/integrations/roo-code)
> Подключение Roo Code — AI-агента для VS Code с режимами Architect / Code / Ask / Debug — к Hubris.
[Roo Code](https://roo-code.com) — расширение для VS Code (форк Cline), которое умеет работать в нескольких режимах: Architect для проектирования, Code для написания и правок, Ask для вопросов, Debug для отладки. Каждому режиму можно назначить свою модель — например, более мощную для Architect и более дешёвую для рутинных правок в Code.
## Требования
* VS Code 1.85+
* Аккаунт на [hubris.pw](https://hubris.pw/dashboard) и API-ключ
## Установка Roo Code
1. Откройте панель расширений в VS Code (`Ctrl+Shift+X`).
2. Найдите **Roo Code** (издатель — RooVeterinaryInc).
3. Нажмите **Install**.
4. После установки в боковой панели появится иконка Roo Code.
## Подключение к Hubris
1. Откройте параметры расширения через иконку шестерёнки в боковой панели Roo Code.
2. Перейдите на вкладку **Providers**.
3. В выпадающем меню провайдера выберите **OpenAI Compatible**.
4. Заполните поля:
| Поле | Значение |
| ------------ | ------------------------------------------------------ |
| **Base URL** | `https://api.hubris.pw/v1` |
| **API Key** | ваш ключ из [личного кабинета](https://hubris.pw/keys) |
| **Model** | например `anthropic/claude-haiku-4.5` |
5. Сохраните настройки (кнопка **Save** в правом верхнем углу).
## Разные модели для разных режимов
Главная фишка Roo Code — разные модели на каждый режим. Это удобно: тяжёлая модель только там, где она реально нужна.
В тех же настройках провайдера у Roo Code есть отдельные слоты для каждого режима. Типовая раскладка:
| Режим | Что делает | Какая модель уместна |
| ------------- | ------------------------------------------------- | ------------------------------------------- |
| **Architect** | проектирует архитектуру, разбивает задачу на шаги | мощная — Claude Opus / Sonnet |
| **Code** | пишет и правит код по чёткому плану | средняя или быстрая — Claude Sonnet, GPT-4o |
| **Ask** | отвечает на вопросы о коде | быстрая и дешёвая — Haiku, GPT-4o-mini |
| **Debug** | анализирует логи, ищет причины ошибок | средняя — Claude Sonnet |
Полный актуальный список моделей — в [каталоге](/models). В коде режим называется `apiModelId`, конкретное значение зависит от модели в нашем каталоге.
## Что обычно ставят
| ID | Когда уместна |
| ---------------------------- | -------------------------------- |
| `anthropic/claude-haiku-4.5` | быстрая работа, недорого |
| `openai/gpt-4o-mini` | универсальный недорогой выбор |
| `google/gemini-2.0-flash` | мультимодальные сценарии, быстро |
Для тяжёлых режимов (Architect, сложный Code) подойдёт более крупная Claude или GPT — посмотрите [каталог](/models) с фильтром по поддержке tool calling.
## Решение проблем
### `Failed to fetch models` или 401 при первом подключении
* Перепроверьте ключ: должен начинаться с `sk-gw-`, без пробелов.
* В Roo Code иногда нужно полностью перезагрузить окно VS Code (`Ctrl+Shift+P` → **Developer: Reload Window**) после первой настройки.
### Модель отвечает «не вижу инструментов» или зацикливается
* Не все модели одинаково хорошо справляются с tool-агентами Roo Code. Попробуйте модель, у которой в [каталоге](/models) явно указана поддержка tool calling.
* Уменьшите контекст: закройте лишние открытые файлы — Roo передаёт активные вкладки в каждый запрос.
### Сессия Architect вдруг очень дорогая
* Architect-режим часто пишет длинные планы — это много `completion_tokens`. Используйте модель попроще для Code и Ask, а Architect зовите только когда правда нужен.
### Дневной лимит ключа сработал в середине задачи
* В [настройках ключа](/keys) можно увеличить лимит. Альтернатива — заведите отдельный ключ под Roo Code и для каждого проекта.
## Что дальше
* [Каталог моделей](/models) — подобрать модели под каждый из четырёх режимов.
* [Управление API-ключами](/keys) — отдельный ключ под Roo Code с лимитом.
* [Расходы](/usage) — детализация запросов.
* [Cline](/docs/integrations/cline) — родитель Roo Code, простой одиночный режим.
---
# Кабинет для AI-агентов (/docs/integrations/webmcp)
> WebMCP — браузерный агент вызывает инструменты кабинета Hubris напрямую, без скрейпинга интерфейса.
> **В разработке.** Стандарт WebMCP ещё формируется, доступность в браузерах ограничена. Набор инструментов может расшириться по мере взросления стандарта.
[WebMCP](https://github.com/webmachinelearning/webmcp) — браузерный стандарт в разработке (инкубируется в рамках W3C). Идея простая: страница сама объявляет структурные инструменты через `document.modelContext`, и агент в браузере пользователя вызывает их напрямую, вместо того чтобы читать интерфейс и угадывать, куда кликнуть. На Hubris это работает на публичных страницах и в кабинете одновременно — набор инструментов подстраивается под то, вошли вы или нет.
Нативная поддержка есть пока не во всех браузерах. В Chrome стандарт доступен через origin trial (версии 149–156); также WebMCP умеют агентные браузеры и браузерные расширения, реализующие этот стандарт. Если ваш браузер или агент WebMCP не поддерживает, кабинет работает как обычно — ничего не ломается.
## Инструменты
Гостю (без входа в кабинет) видны 5 публичных инструментов — они работают на любой странице Hubris. После входа добавляются ещё 13: чтение данных кабинета, действия и тестовый прогон модели. Итого в кабинете доступно 18 инструментов.
| Инструмент | Что делает | Где доступен |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ |
| `models_search` | Ищет модели в каталоге по названию, провайдеру или описанию задачи своими словами; отдаёт цены в рублях за 1М токенов. | Гость |
| `pricing_get` | Возвращает подробные цены и характеристики одной модели по её идентификатору. | Гость |
| `cost_estimate` | Оценивает стоимость использования модели в рублях по ожидаемому числу запросов и токенов — ориентир по текущим ценам, не оферта. | Гость |
| `docs_search` | Ищет по документации Hubris, возвращает до 10 подходящих страниц. | Гость |
| `status_get` | Текущий статус платформы и провайдеров моделей, включая активные (нерешённые) инциденты. | Гость |
| `balance_get` | Баланс кабинета (основной и бонусный) и приблизительный прогноз, на сколько дней и запросов хватит при текущем расходе. | Кабинет |
| `usage_stats` | Статистика расхода и запросов за период: по дням, по моделям, по API-ключам или общие итоги за 7 дней. | Кабинет |
| `logs_query` | Последние записи журнала запросов: время, модель, статус, стоимость, токены — без адресов вызывающих приложений и других чувствительных данных. | Кабинет |
| `payment_history` | Лента денежных операций: пополнения, возвраты, корректировки, партнёрские выплаты. | Кабинет |
| `usage_export` | Возвращает ссылку на CSV-файл с историей расхода за период. | Кабинет |
| `referral_info` | Состояние партнёрской программы: ссылка, число приглашённых, сумма начислений. Доступно после первой оплаты. | Кабинет |
| `notification_prefs_get` | Текущие настройки уведомлений: продуктовые, о достижениях, маркетинговые. | Кабинет |
| `topup_start` | Начинает пополнение баланса на сумму в рублях: создаёт QR-код СБП или платёжную ссылку для карты. Баланс пополняется только после подтверждения платежа в приложении банка. | Кабинет |
| `invoice_create` | Создаёт счёт на оплату для юридического лица на сумму в рублях (нужны заранее заполненные реквизиты организации). | Кабинет |
| `documents_get` | Список счетов юридического лица со ссылкой на PDF и, если документ уже сформирован, — на закрывающий УПД. | Кабинет |
| `notification_prefs_set` | Меняет настройки email-уведомлений кабинета. | Кабинет |
| `feedback_submit` | Публикует идею на доску идей Hubris — заголовок и, опционально, описание. Лимит — не больше трёх идей в сутки. | Кабинет |
| `chat_test` | Тестовый прогон промпта по текстовой модели каталога (генерация изображений — в студии на сайте). Списывается с баланса по обычным ценам. | Кабинет |
Управление API-ключами агенту намеренно недоступно ни в одном из наборов — ни просмотр, ни выпуск, ни отзыв. Это осознанное ограничение: ключ открывает доступ к балансу за пределами текущей браузерной сессии, и агент не должен уметь его получить.
## Лимиты `chat_test`
`chat_test` — это примерка модели, а не рабочий канал. У неё два серверных ограничения, которые нельзя обойти со стороны агента:
* **до 2000 токенов ответа** — сервер обрезает ответ до этого значения независимо от того, что попросил агент;
* **до 20 прогонов в час** — на пользователя, счётчик скользит от первого прогона в окне; после исчерпания сервер отвечает `429` с `Retry-After`.
Ограничения защищают от случайного зацикливания агента, а не от расходов — токены `chat_test` списываются с баланса по тем же ценам, что и обычный запрос. Инструмент работает только с текстовыми моделями: попытка вызвать модель с генерацией изображений получает отказ — сама генерация доступна в студии на сайте, не через кабинет для агентов.
При обрезке ответа `chat_test` называет причину: если сработал серверный кап (2000 токенов), в подсказке — ссылка на подключение MCP-сервера, путь к полноценной работе без лимитов; если ответ обрезал собственный `max_tokens` агента (передан меньше 2000), подсказка называет именно это значение и предлагает его поднять — подключать MCP-сервер для этого не нужно.
## Полноценная работа — через MCP-сервер
WebMCP в кабинете создан для быстрой проверки и точечных действий прямо в браузере, а не для продуктивной работы с моделями. Если агенту нужны запросы без ограничения токенов и без часового лимита, следующий шаг — подключить [MCP-сервер Hubris](/docs/integrations/mcp): тот же каталог моделей и `chat_complete`, но по API-ключу и без капов, применимых к тестовому прогону.
## Приватность
Агент действует под вашей активной сессией в браузере — теми же правами, что и вы сами в открытой вкладке кабинета. Отдельной агентской идентичности или токена WebMCP не создаёт: закрыли вкладку или вышли из аккаунта — доступ у агента пропал вместе с сессией.
Из этого следует два ограничения, введённых сознательно:
* **Управление API-ключами недоступно** — см. выше. Ключ живёт дольше браузерной сессии и не должен быть доступен через страницу.
* **Генерация изображений — только в студии на сайте**, не через инструменты кабинета для агентов.
## Что дальше
* [MCP-сервер](/docs/integrations/mcp) — подключение по API-ключу, без лимитов тестового прогона.
* [Каталог моделей](https://hubris.pw/models) — все активные модели и цены в рублях.
* [Биллинг](https://hubris.pw/billing) — пополнение баланса.
* [Кабинет](https://hubris.pw/dashboard) — то же самое можно сделать руками, без агента.