# llmbridge — полная справка API

Базовый URL: https://sav.ecom24.ru/v1. Ключ `lb-...` передаётся в заголовке `Authorization: Bearer <ключ>`. Индекс разделов: https://sav.ecom24.ru/llms.txt

<!-- page: authentication -->
## Авторизация

Каждый запрос к внешнему API должен содержать ваш ключ LLMBridge в заголовке `Authorization`. Ключ начинается с `lb-` и создаётся в разделе «Ключи API» портала.

```bash
Authorization: Bearer lb-xxxxxxxxxxxxxxxxxxx
```

Как получить ключ:

1. Войдите в портал LLMBridge под своей учётной записью.
2. Откройте раздел «Ключи API» в боковом меню.
3. Нажмите «Создать ключ» — ключ будет показан один раз, целиком.
4. Сохраните его в надёжном месте: повторно ключ не показывается.

**Потеряли ключ — создайте новый.** Восстановить существующий невозможно: сервис хранит только его хэш и маску. Старый ключ отключается в том же разделе, чтобы им нельзя было воспользоваться.

Отсутствующий, неизвестный или отключённый ключ — ответ `401` с телом `{"error": {"type": "authentication_error", "code": "invalid_api_key"}}`.

<!-- page: chat-completions -->
## Chat Completions

Генерация ответа модели — `POST /v1/chat/completions`. Формат запроса и ответа совместим с отраслевым стандартом Chat Completions, поэтому подходят привычные SDK и инструменты.

Тело запроса:

```json
{
  "model": "MODEL_ID",
  "messages": [
    { "role": "system", "content": "You are a helpful assistant." },
    { "role": "user", "content": "Hello!" }
  ],
  "temperature": 0.7,
  "max_tokens": 1024,
  "stream": false
}
```

Поле `model` — идентификатор модели из таблицы «Доступные модели» внизу этой страницы. Список тех же моделей отдаёт `GET /v1/models`.

Коды ошибок и что с ними делать:

| HTTP | code | Что произошло |
| --- | --- | --- |
| 400 | invalid\_json, missing\_field, unsupported\_parameter, unsupported\_modality | Ошибка в теле запроса — повтор без исправления не поможет. |
| 401 | invalid\_api\_key | Ключ отсутствует, неизвестен или отключён. |
| 403 | key\_disabled, key\_expired, company\_suspended, access\_not\_configured | Доступ закрыт — обратитесь к администратору компании. |
| 402 | insufficient\_balance, budget\_exceeded, company\_budget\_exceeded | Исчерпан баланс или бюджет ключа/компании. |
| 404 | model\_not\_found | Модели нет в каталоге или она недоступна вашему ключу. |
| 413 | request\_too\_large | Тело запроса слишком большое. |
| 429 | rate\_limit\_exceeded, token\_limit\_exceeded | Слишком много запросов или исчерпан лимит токенов. |
| 502 / 503 / 504 | upstream\_error, service\_unavailable, upstream\_timeout | Временный сбой — запрос можно повторить. |

Повторять запрос имеет смысл только на `429`, `502`, `503` и `504`. При `429` дождитесь времени из заголовка `Retry-After`, дальше — экспоненциальная пауза (1 с, 2 с, 4 с …) с ограничением числа попыток. Ошибки `4xx` из-за тела запроса, ключа или баланса повторять бессмысленно.

#### Каталог моделей — `GET /v1/models`

Список моделей, доступных вашему ключу: включённые в каталоге и разрешённые ключу (если у ключа задан список моделей — только они). Тот же список показан в таблице «Доступные модели» внизу этой страницы. Формат совместим с OpenAI, поэтому подходит `client.models.list()` официального SDK.

```bash
curl https://llmbridge.ru/v1/models \
  -H "Authorization: Bearer lb-..."
```

```json
{
  "object": "list",
  "data": [
    { "id": "chat-model",   "object": "model", "created": 1725000000, "owned_by": "llmbridge", "modalities": ["text", "image-in", "file"] },
    { "id": "embed-model",  "object": "model", "created": 1725000000, "owned_by": "llmbridge", "modalities": ["embedding"] },
    { "id": "speech-model", "object": "model", "created": 1725000000, "owned_by": "llmbridge", "modalities": ["tts"] }
  ]
}
```

Одна модель — `GET /v1/models/{id}`: возвращает тот же объект. Если модели нет, она выключена или недоступна вашему ключу — `404 model_not_found`.

```bash
curl https://llmbridge.ru/v1/models/chat-model \
  -H "Authorization: Bearer lb-..."
```

| Поле | Значение |
| --- | --- |
| `id` | идентификатор модели — его передают в поле `model` всех методов |
| `object` | всегда `model` |
| `created` | время появления модели в каталоге, unix-секунды |
| `owned_by` | всегда `llmbridge` |
| `modalities` | что умеет модель и на каком адресе её вызывать (см. ниже). Расширение формата OpenAI: SDK поле сохраняет, но не типизирует |

**Модальности.** У каждой модели ровно одна базовая модальность — `text`, `tts` или `embedding`; она определяет адрес вызова. Остальные значения — дополнительные возможности текстовой модели.

| Значение | Что означает | Где вызывать |
| --- | --- | --- |
| `text` | текстовая (чат) модель | `/v1/chat/completions`, `/v1/messages`, `/v1/responses` |
| `image-in` | принимает изображения во входе | те же адреса, блоки `image_url` |
| `file` | принимает файлы (PDF) во входе | те же адреса, блоки `file` |
| `audio-in`, `video-in` | принимает аудио / видео во входе | те же адреса |
| `image-out`, `audio-out` | умеет отдавать изображение / аудио в ответе | те же адреса |
| `tts` | синтез речи; всегда без `image-out`/`audio-out` | `/v1/audio/speech` |
| `embedding` | эмбеддер; других значений у него нет | `/v1/embeddings` |

Модель на «чужом» адресе отклоняется до обращения к ней и не тарифицируется: эмбеддер в `/v1/chat/completions`, `/v1/messages` и `/v1/responses`, а также текстовая или tts-модель в `/v1/embeddings` — `400 unsupported_modality`; модель без `tts` в `/v1/audio/speech` — `404 model_not_found`. Вложение, которое модель не принимает (например, изображение для модели без `image-in`), не является ошибкой: оно заменяется текстовой пометкой с именем файла.

### Озвучка текста (/v1/audio/speech)

Синтез речи по тексту — `POST /v1/audio/speech`. Запрос и ответ совместимы с отраслевым стандартом Speech API: в теле передаётся текст, в ответе — готовый аудиофайл выбранного формата.

Тело запроса:

| Параметр | Обязателен | Описание |
| --- | --- | --- |
| `model` | да | Идентификатор модели с модальностью озвучки из таблицы «Доступные модели» ниже или `GET /v1/models`. |
| `input` | да | Текст для озвучки, не более 4096 символов (символы, не байты). |
| `voice` | нет | Голос (по умолчанию `alloy`). |
| `response_format` | нет | Формат аудио (по умолчанию `mp3`). |

Пример:

```bash
curl https://llmbridge.ru/v1/audio/speech \
  -H "Authorization: Bearer lb-..." \
  -H "Content-Type: application/json" \
  -d '{
    "model": "MODEL_ID",
    "input": "Привет! Это пример озвучки текста.",
    "voice": "alloy",
    "response_format": "mp3"
  }' \
  --output speech.mp3
```

**Тарификация** — по символам входного текста (`input`): стоимость считается по объёму переданного текста, без дополнительных токенов ответа.

### База знаний через API

Если у вас или вашей компании загружены документы в раздел «База знаний», ответы моделей можно дополнять фрагментами из них двумя способами: полем `kb_search` в чат-запросе или отдельным поисковым эндпоинтом.

#### Поле kb\_search (/v1/chat/completions)

Необязательное булево поле верхнего уровня тела запроса `POST /v1/chat/completions` — расширение вне стандартной схемы, доступно у любой модели:

```json
{
  "model": "MODEL_ID",
  "messages": [{ "role": "user", "content": "Сколько дней отпуска мне положено?" }],
  "kb_search": true
}
```

При `kb_search: true` перед генерацией по последнему сообщению пользователя ищутся релевантные фрагменты вашей личной базы и базы вашей компании; найденное добавляется в контекст модели служебным сообщением — ответ приходит в том же формате `chat.completion`, без дополнительных полей. Отсутствие или `false` — поведение прежнее, поиск не выполняется. Если база пуста или поиск временно недоступен, запрос не падает — модель просто отвечает без опоры на базу знаний.

#### POST /v1/kb/search

Прямой поиск по базе знаний без генерации ответа моделью — удобно для собственных RAG-сценариев поверх API.

Тело запроса:

| Параметр | Обязателен | Описание |
| --- | --- | --- |
| `query` | да | Поисковый запрос (непустая строка). |
| `limit` | нет | Число фрагментов в ответе, 1–20 (по умолчанию — умеренное число самых релевантных). |

```bash
curl https://llmbridge.ru/v1/kb/search \
  -H "Authorization: Bearer lb-..." \
  -H "Content-Type: application/json" \
  -d '{
    "query": "Сколько дней отпуска мне положено?",
    "limit": 5
  }'
```

Ответ:

```json
{
  "items": [
    {
      "content": "Отпуск — 28 календарных дней в году.",
      "documentName": "Политика отпусков.docx",
      "collection": "personal",
      "score": 0.87
    }
  ]
}
```

`collection` — источник фрагмента: `personal` (ваша личная база) или `company` (база вашей компании).

Коды ошибок:

| HTTP | code | Что произошло |
| --- | --- | --- |
| 400 | invalid\_json, invalid\_request | Ошибка в теле запроса (в т.ч. пустой `query`) — повтор без исправления не поможет. |
| 401 | invalid\_api\_key | Ключ отсутствует, неизвестен или отключён. |
| 503 | service\_unavailable | Поиск временно недоступен — запрос можно повторить. |

Тарификации у поиска нет: и `kb_search`, и `POST /v1/kb/search` не создают отдельных строк в журнале использования.

#### Использование с SDK

Подходит официальный SDK OpenAI — меняется только базовый адрес и ключ, остальной код не отличается от работы с OpenAI напрямую.

```python
from openai import OpenAI

client = OpenAI(
    base_url="https://llmbridge.ru/v1",
    api_key="lb-...",
)

response = client.chat.completions.create(
    model="MODEL_ID",
    messages=[{"role": "user", "content": "Привет!"}],
)
print(response.choices[0].message.content)
```

```javascript
import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://llmbridge.ru/v1",
  apiKey: "lb-...",
});

const response = await client.chat.completions.create({
  model: "MODEL_ID",
  messages: [{ role: "user", content: "Привет!" }],
});
console.log(response.choices[0].message.content);
```

Нужен Anthropic-совместимый API (например, для Claude Code или SDK `anthropic`)? См. страницу «Подключение Claude Code».

#### Остатки лимитов — `GET /v1/limits`

Бесплатный запрос: не тарифицируется, не пишется в журнал использования и не расходует лимит запросов в минуту. Показывает, сколько осталось у ключа и можно ли сейчас отправлять запросы.

```bash
curl https://llmbridge.ru/v1/limits \
  -H "Authorization: Bearer lb-..."
```

```json
{
  "object": "limits",
  "as_of": "2026-08-30T09:15:00Z",
  "currency": "RUB",
  "can_request": true,
  "block_reason": null,
  "key": {
    "id": "0f3c...",
    "name": "prod-bot",
    "expires_at": null,
    "budget": {
      "period": "month",
      "limit": "1000.00",
      "spent": "123.45",
      "reserved": "1.20",
      "remaining": "875.35",
      "resets_at": "2026-09-01T00:00:00+03:00"
    },
    "rate_limit": {
      "requests_per_minute": 120,
      "requests_remaining": 117,
      "resets_at": "2026-08-30T12:16:00+03:00",
      "concurrent_streams": 10,
      "streams_in_use": 1
    }
  },
  "user_tokens": {
    "limit": 1000000,
    "spent": 12345,
    "remaining": 987655,
    "resets_at": "2026-09-01T00:00:00+03:00"
  },
  "company": {
    "monthly_budget": {
      "limit": "50000.00",
      "spent": "12000.00",
      "reserved": "35.10",
      "remaining": "37964.90",
      "resets_at": "2026-09-01T00:00:00+03:00"
    },
    "wallet": {
      "balance": "1500.000000",
      "reserved": "35.10",
      "available": "1464.90",
      "blocked": false
    }
  }
}
```

| Поле | Значение |
| --- | --- |
| `can_request` | `true`, если сейчас не исчерпан ни один лимит; иначе `false` и `block_reason` — первый из `rate_limit_exceeded`, `budget_exceeded`, `token_limit_exceeded`, `company_budget_exceeded`, `insufficient_balance` |
| `key.budget` | бюджет ключа в ₽ (decimal-строки: `limit` — как задан администратором, `spent`/`reserved`/`remaining` — 2 знака); `null`, если бюджет не задан. `remaining = limit − spent − reserved`, не ниже 0. `period`: `month` — сбрасывается в начале месяца (`resets_at`), `total` — не сбрасывается (`resets_at: null`) |
| `key.rate_limit` | запросов в минуту и остаток в текущей минуте; `concurrent_streams` / `streams_in_use` — лимит и занятость одновременных потоковых ответов (на `can_request` не влияют) |
| `user_tokens` | месячный лимит токенов владельца ключа; `null`, если лимит не задан |
| `company` | месячный бюджет и баланс компании. Секция видна только если администратор компании включил для ключа «Показывать лимиты компании в API»; иначе `null` — но `can_request` всё равно учитывает лимиты компании |
| `company.wallet` | только для компаний с предоплатой; `balance` — как в кошельке (до 6 знаков), `available = balance − reserved` (2 знака, не ниже 0) |

**Важно.** `can_request: true` — не гарантия: перед каждым запросом резервируется худшая стоимость конкретной модели (`max_tokens × цена вывода + размер промпта × цена ввода`), и она может не уложиться в `remaining` — тогда придёт `402 insufficient_budget`. Сравнивайте `remaining` с ожидаемой стоимостью запроса. Бесплатные модели денежные лимиты не блокируют — на них действует только лимит запросов в минуту.

Ошибки — те же, что у остальных методов: `401 invalid_api_key`, `403 company_suspended`, `503 billing_unavailable` (повторите позже).

#### Эмбеддинги — `POST /v1/embeddings`

Векторное представление текста для поиска, кластеризации и RAG. Подходит официальный SDK OpenAI: тот же адрес, то же тело, те же поля ответа — меняется только базовый адрес и ключ. Модели-эмбеддеры отмечены в каталоге бейджем «Эмбеддинги»; в чате и в `/v1/chat/completions` они недоступны.

```bash
curl https://llmbridge.ru/v1/embeddings \
  -H "Authorization: Bearer lb-..." \
  -H "Content-Type: application/json" \
  -d '{
    "model": "embed-model",
    "input": ["Первый текст", "Второй текст"]
  }'
```

```python
from openai import OpenAI

client = OpenAI(base_url="https://llmbridge.ru/v1", api_key="lb-...")
resp = client.embeddings.create(model="embed-model", input=["Первый текст", "Второй текст"])
vectors = [item.embedding for item in resp.data]
```

```json
{
  "object": "list",
  "data": [
    { "object": "embedding", "index": 0, "embedding": [0.0123, -0.0456, ...] },
    { "object": "embedding", "index": 1, "embedding": [0.0789, 0.0012, ...] }
  ],
  "model": "embed-model",
  "usage": { "prompt_tokens": 12, "total_tokens": 12 }
}
```

| Поле запроса | Значение |
| --- | --- |
| `model` | идентификатор модели-эмбеддера из каталога (обязательно) |
| `input` | строка или массив строк (обязательно, не пусто). Ограничения: не более 2048 элементов, не более 32 768 символов в элементе и 1 048 576 символов суммарно; превышение — `400 invalid_request` |
| `encoding_format` | `float` (по умолчанию) или `base64` — массив float32 little-endian в base64; другие значения — `400 unsupported_parameter` |
| `dimensions` | размерность вектора (положительное целое); передаётся модели как есть, если модель её не поддерживает — `400 invalid_request` |
| `stream`, `user` | принимаются и игнорируются |

**Тарификация.** Эмбеддинги тарифицируются только по входным токенам (`prompt_tokens`); токенов вывода у них нет, в журнале использования `tokens_out = 0`. В прайсе у моделей-эмбеддеров указана только цена ввода. Лимиты ключа, компании и пользователя действуют так же, как у остальных методов.

| Ошибка | Когда |
| --- | --- |
| `400 invalid_json` / `missing_field` / `invalid_request` | тело не JSON; нет `model`; `input` не строка и не массив строк, пуст или превышает ограничения; `dimensions ≤ 0` |
| `400 unsupported_modality` | модель не эмбеддер (чат-модель на этом адресе) |
| `400 unsupported_parameter` | `encoding_format` вне `float`/`base64` |
| `404 model_not_found` | модели нет, она выключена или недоступна ключу |
| `402 budget_exceeded` / `insufficient_balance`, `429 rate_limit_exceeded` | лимиты — те же, что у остальных методов |
| `502 upstream_error`, `504 upstream_timeout` | сбой или таймаут на стороне модели — повторите запрос |
| `503 service_unavailable` | метод временно недоступен — повторите запрос позже |

<!-- page: pii-guard -->
## Защита персональных данных

Защита персональных данных (PII-guard) находит в запросе персональные данные и заменяет их плейсхолдерами вида `[PERSON_1]`, `[PHONE_2]` до отправки модели. Модель видит только плейсхолдеры; в ответе они автоматически заменяются обратно на исходные значения. Сами данные не покидают сервис.

#### Что распознаётся

- `[PERSON_N]` — фамилия, имя, отчество (кириллица);
- `[PHONE_N]` — телефоны в российских форматах, а также международные номера вида `+…`, записанные без разделителей;
- `[EMAIL_N]` — адреса электронной почты;
- `[PASSPORT_N]` — серия и номер паспорта РФ;
- `[SNILS_N]` — СНИЛС;
- `[INN_N]` — ИНН (10 или 12 цифр);
- `[OGRN_N]` — ОГРН и ОГРНИП;
- `[CARD_N]` — номера банковских карт.

Одинаковые значения получают один и тот же плейсхолдер во всём запросе, включая историю сообщений. Модель получает указание не менять плейсхолдеры; если она всё же исказила плейсхолдер, он останется в ответе как есть — так видно, что обратная подстановка не сработала.

#### Режимы

Режим для компании задаёт администратор платформы:

- **Выключена** — защита не применяется.
- **По выбору** — в веб-чате защита включается тумблером «Защита ПДн» в строке настроек сообщения; для API-ключей — флагом «Защита ПДн» у ключа в разделе «Ключи API» (доступен администратору компании).
- **Всегда** — защита применяется ко всем запросам компании и отключить её нельзя.

Под ответом в чате появляется бейдж «ПДн: N» — сколько значений было замаскировано. В журнале использования такие запросы отмечены тем же бейджем, а в CSV-выгрузке есть колонка «PII».

#### Ручной вызов из API

Если вы храните переписку у себя или работаете с моделью в обход чата, маскирование доступно как отдельные методы. Они не тарифицируются и не попадают в журнал использования; нужен любой действующий ключ `lb-...`.

**POST /v1/pii/anonymize**. Тело — либо `text`, либо `messages` (массив `{"role", "content"}`, `content` — строка). Ответ содержит замаскированный текст и список сущностей, который понадобится для обратной замены.

```
curl https://llmbridge.ru/v1/pii/anonymize \
  -H "Authorization: Bearer lb-ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"text": "Позвоните Иванову Ивану Ивановичу: +7 999 000-00-00, ivan@example.com"}'
```

```
{
  "text": "Позвоните [PERSON_1]: [PHONE_1], [EMAIL_1]",
  "entities": [
    {"placeholder": "[PERSON_1]", "type": "PERSON", "value": "Иванову Ивану Ивановичу"},
    {"placeholder": "[PHONE_1]",  "type": "PHONE",  "value": "+7 999 000-00-00"},
    {"placeholder": "[EMAIL_1]",  "type": "EMAIL",  "value": "ivan@example.com"}
  ]
}
```

**POST /v1/pii/deanonymize**. Принимает `text` или `messages` и список `entities` из ответа anonymize; возвращает текст с восстановленными значениями. Незнакомые плейсхолдеры остаются без изменений.

```
curl https://llmbridge.ru/v1/pii/deanonymize \
  -H "Authorization: Bearer lb-ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"text": "Написал [PERSON_1] на [EMAIL_1]",
       "entities": [
         {"placeholder": "[PERSON_1]", "type": "PERSON", "value": "Иванову Ивану Ивановичу"},
         {"placeholder": "[EMAIL_1]",  "type": "EMAIL",  "value": "ivan@example.com"}
       ]}'
```

**Пример на Python** — маскирование, запрос к модели, восстановление. Вместо `MODEL_ID` подставьте идентификатор нужной модели из каталога (`GET /v1/models` или таблица «Доступные модели» на странице Chat Completions):

```
import requests

BASE = "https://llmbridge.ru"
HEADERS = {"Authorization": "Bearer lb-ВАШ_КЛЮЧ"}

masked = requests.post(f"{BASE}/v1/pii/anonymize", headers=HEADERS, json={
    "messages": [{"role": "user",
                  "content": "Мой ИНН 5001007329, перезвоните на +7 999 000-00-00"}],
}).json()

answer = requests.post(f"{BASE}/v1/chat/completions", headers=HEADERS, json={
    "model": "MODEL_ID",
    "messages": masked["messages"],
}).json()

restored = requests.post(f"{BASE}/v1/pii/deanonymize", headers=HEADERS, json={
    "text": answer["choices"][0]["message"]["content"],
    "entities": masked["entities"],
}).json()
print(restored["text"])
```

#### Границы детекции

- Защита применяется к текстовым запросам к моделям — `/v1/chat/completions`, `/v1/messages`, `/v1/responses`, `/v1/embeddings` и веб-чату. **К синтезу речи (TTS) она не применяется** — текст уходит в синтез как есть.
- Детектор ФИО — словарный, best-effort. Пропускаются: очень короткие имена в косвенных падежах (три буквы: «Яны», «Еве»); сочетания несловарного имени с отчеством редких форм. Одиночное слово с заглавной буквы не маскируется никогда — нужно сочетание минимум двух слов подряд. Из-за этого возможна и обратная ошибка: пара, в которой словарное имя-омоним (Вера, Роман, Лев) стоит рядом с подходящим по форме словом, будет замаскирована целиком.
- Детектор карт проверяет контрольную сумму Луна и работает по группам цифр. Не распознаётся номер карты, к которому цифры примыкают вплотную без разделителя (`Оплата 15004111…`). Иногда маскируется чуть более широкий фрагмент, чем сам номер, — после восстановления текст возвращается побайтово исходным, для клиента разницы нет.
- Распознавание ФИО рассчитано на кириллицу: имена латиницей, редкие и просклонённые формы могут быть пропущены.
- Анализируется только текст: вложения, изображения и голосовой ввод не проверяются.
- Защита снижает риск утечки ПДн, но **не является гарантией** и не заменяет ответственность клиента за то, что он отправляет.

#### Ограничения API

- Методы `/v1/pii/*`: не более 120 запросов в минуту на ключ (429 `rate_limit_exceeded`), тело — до 256 КиБ; `text` и `messages` одновременно, либо ни одного, либо нестроковый `content` — 400 `invalid_request`; неверный ключ — 401 `invalid_api_key`.

#### English

PII guard replaces personal data (names, phones, e-mails, Russian passport, SNILS, INN, OGRN and card numbers) with placeholders such as `[PERSON_1]` before the request reaches the model, and restores the original values in the answer. The platform administrator sets the company mode: **off**, **user's choice** (a toggle in the chat composer and a per-key flag on the API keys page) or **always**. Standalone endpoints `POST /v1/pii/anonymize` and `POST /v1/pii/deanonymize` accept `text` or `messages`, are free of charge, are not logged to usage, and are limited to 120 requests per minute per key with a 256 KiB body. Protection applies to text model requests (chat and embeddings), not TTS. Detection is dictionary-based and best-effort for Cyrillic names; attachments and audio are not analysed. Treat the guard as an additional safeguard that reduces risk but is not a guarantee and does not replace your responsibility for what you send.

<!-- page: examples -->
## Примеры кода

Базовый URL: `https://llmbridge.ru/v1`. Подставьте свой ключ вместо `lb-...` и идентификатор нужной модели вместо `MODEL_ID`.

#### Python

```python
from openai import OpenAI

client = OpenAI(
    base_url="https://llmbridge.ru/v1",
    api_key="lb-...",
)

resp = client.chat.completions.create(
    model="MODEL_ID",
    messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "Привет! Расскажи о себе."},
    ],
    temperature=0.7,
)

print(resp.choices[0].message.content)
```

#### Node.js

```javascript
import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://llmbridge.ru/v1",
  apiKey: "lb-...",
});

const resp = await client.chat.completions.create({
  model: "MODEL_ID",
  messages: [{ role: "user", content: "Hello!" }],
});

console.log(resp.choices[0].message.content);
```

#### cURL

```bash
curl https://llmbridge.ru/v1/chat/completions \
  -H "Authorization: Bearer lb-..." \
  -H "Content-Type: application/json" \
  -d '{
    "model": "MODEL_ID",
    "messages": [{"role": "user", "content": "Привет!"}],
    "temperature": 0.7
  }'
```

Каталог моделей тем же ключом: `curl -H "Authorization: Bearer lb-..." https://llmbridge.ru/v1/models`.

#### Для ИИ-агентов

Машиночитаемая версия этой документации доступна без авторизации по адресам `/llms.txt` (индекс разделов с краткими описаниями) и `/llms-full.txt` (полный текст всех разделов одним markdown-файлом). Подключите её ассистенту, который пишет интеграцию: в Cursor — добавьте URL в источники `@Docs`, в Claude Code — укажите ссылку на `/llms-full.txt` в `CLAUDE.md` проекта.

```bash
curl https://llmbridge.ru/llms-full.txt
```

#### Статус платформы

Текущее состояние сервисов, история доступности за 90 дней, инциденты и плановые работы — на публичной странице [/status](/status) (доступна без входа). Для систем мониторинга есть JSON-алиас без авторизации, ответ кэшируется на 10 секунд:

```bash
curl https://llmbridge.ru/api/status
```

```json
{
  "status": "degraded",
  "updatedAt": "2026-09-05T10:00:00Z",
  "components": [
    { "id": "web", "name": { "ru": "Веб-приложение", "en": "Web application" }, "status": "operational" },
    { "id": "api", "name": { "ru": "API /v1", "en": "API /v1" }, "status": "operational" },
    { "id": "chat", "name": { "ru": "Чат и realtime", "en": "Chat & realtime" }, "status": "operational" },
    { "id": "models", "name": { "ru": "Модели ИИ", "en": "AI models" }, "status": "degraded" },
    { "id": "media", "name": { "ru": "Файлы и медиа", "en": "Files & media" }, "status": "operational" },
    { "id": "billing", "name": { "ru": "Биллинг и лимиты", "en": "Billing & limits" }, "status": "operational" }
  ],
  "incidents": [],
  "maintenance": [],
  "history": { "days": 90, "from": "2026-06-08", "components": { "web": [ { "day": "2026-06-08", "uptimePct": 100, "status": "operational" } ] } },
  "sla": { "targetPct": 99.5, "scope": ["web", "api", "chat"] },
  "monthly": { "month": "2026-09", "uptimePct": { "web": 100, "api": 99.98, "chat": 100, "models": 99.5, "media": 100, "billing": 100 } }
}
```

Пример сокращён: в `history.components` показан один компонент за одни сутки, в настоящем ответе там все компоненты и до 90 суток у каждого. Состав `components`, `history.components` и `monthly.uptimePct` совпадает; при включённой базе знаний к нему добавляется `kb`. Списки `incidents` и `maintenance` в примере пусты, потому что открытых записей нет.

Значения `status`: `operational` — работает, `degraded` — замедление, `partial_outage` — частичный сбой, `major_outage` — сбой, `maintenance` — плановые работы, `unknown` — данных ещё нет. Компонент `models` отражает доступность внешних моделей и в SLA не входит. Целевая доступность платформы — 99,5 % в календарный месяц по компонентам `web`, `api`, `chat`; условия — в SLA-приложении к договору.

<!-- page: streaming -->
## Стриминг (SSE)

Потоковая выдача включается полем `stream: true`: ответ приходит частями по Server-Sent Events, каждая часть — приращение текста в `choices[0].delta.content`. Для интерфейсов чата это рекомендуемый режим.

#### Python

```python
stream = client.chat.completions.create(
    model="MODEL_ID",
    messages=[{"role": "user", "content": "Расскажи анекдот"}],
    stream=True,
    stream_options={"include_usage": True},
)

for chunk in stream:
    if chunk.choices and chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="", flush=True)
```

#### cURL

Ключ `-N` отключает буферизацию, иначе события покажутся разом в конце.

```bash
curl -N https://llmbridge.ru/v1/chat/completions \
  -H "Authorization: Bearer lb-..." \
  -H "Content-Type: application/json" \
  -d '{
    "model": "MODEL_ID",
    "messages": [{"role": "user", "content": "Привет!"}],
    "stream": true
  }'
```

Поток закрывается строкой `data: [DONE]`. Если запрошено `stream_options.include_usage`, перед ней приходит завершающий фрагмент с итоговым числом токенов.

<!-- page: claude-code -->
## Подключение Claude Code и Codex

Claude Code и любой клиент Anthropic SDK подключаются к LLMBridge через переменные окружения. Ключ `lb-...` создаётся в разделе «Ключи API» портала.

#### Настройка клиента

```bash
export ANTHROPIC_BASE_URL="https://llmbridge.ru"
export ANTHROPIC_AUTH_TOKEN="lb-..."
export ANTHROPIC_MODEL="MODEL_ID"
export ANTHROPIC_DEFAULT_HAIKU_MODEL="MODEL_ID_HAIKU"
```

`ANTHROPIC_BASE_URL` — адрес вашего LLMBridge (клиент сам допишет `/v1/messages`). `ANTHROPIC_AUTH_TOKEN` — ключ из личного кабинета. `ANTHROPIC_MODEL` — идентификатор основной модели из каталога (см. таблицу «Доступные модели» на странице Chat Completions или `GET /v1/models`). `ANTHROPIC_DEFAULT_HAIKU_MODEL` — более лёгкая модель для фоновых задач CLI (тот же каталог).

Основной эндпоинт — `POST /v1/messages` (формат Anthropic Messages API). Подсчёт контекста без списания: `POST /v1/messages/count_tokens` — локальная оценка входа, ответ `{"input_tokens": N}`.

**Модели.** Доступны все включённые модели каталога LLMBridge: подставьте их `public_id` в `ANTHROPIC_MODEL`. Список — в портале и через `GET /v1/models` тем же ключом.

#### Совместимость моделей и типовые вопросы

**Какие модели работают в Claude Code?** Любые включённые модели каталога LLMBridge — не только семейство Claude. Подставьте `public_id` модели в `ANTHROPIC_MODEL` или флаг `--model`. Единственное требование — поддержка инструментов (tool calling): Claude Code всегда передаёт модели описания своих инструментов, и модель без такой поддержки ответит ошибкой `400 tools is not supported for this model`. Такие модели остаются доступны через `POST /v1/chat/completions` и веб-чат, но с Claude Code несовместимы.

**Предупреждение о незнакомой модели** («…is not a model this version of Claude Code recognizes» или, в новых версиях клиента, «…isn't described by this version's model catalog»; в неинтерактивном режиме `claude -p` и через SDK — строка `[claude-code:unrecognized_model] {…}` в stderr). Безвредно: клиент не знает размер контекстного окна незнакомой ему модели, считает его равным 200k токенов и по нему запускает авто-сжатие истории. Реальное окно модели (колонка «Контекст» в таблице моделей) укажите переменной `CLAUDE_CODE_MAX_CONTEXT_TOKENS`.

**Ошибка `overloaded_error` («Service temporarily unavailable»).** Модель временно недоступна или перегружена. Повторите запрос позже или выберите другую модель каталога.

**Codex CLI.** Поддерживается через Responses API (`POST /v1/responses`) — настройка и ограничения в разделе «Codex CLI» ниже.

#### Claude Code: файл настроек и модели-алиасы

Переменные можно не экспортировать в оболочке, а записать в блок `env` файла настроек Claude Code — пользовательского `~/.claude/settings.json` или проектного `.claude/settings.json`. Ключ храните только в пользовательском файле или в переменной окружения: проектный файл попадает в git.

```json
{
  "env": {
    "ANTHROPIC_BASE_URL": "https://llmbridge.ru",
    "ANTHROPIC_AUTH_TOKEN": "lb-...",
    "ANTHROPIC_MODEL": "MODEL_ID",
    "ANTHROPIC_DEFAULT_OPUS_MODEL": "MODEL_ID",
    "ANTHROPIC_DEFAULT_SONNET_MODEL": "MODEL_ID",
    "ANTHROPIC_DEFAULT_HAIKU_MODEL": "MODEL_ID_HAIKU",
    "CLAUDE_CODE_SUBAGENT_MODEL": "MODEL_ID",
    "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1"
  }
}
```

| Переменная | Назначение |
| --- | --- |
| `ANTHROPIC_AUTH_TOKEN` или `ANTHROPIC_API_KEY` | ключ `lb-...`. Первая передаёт его заголовком `Authorization: Bearer`, вторая — `x-api-key`; LLMBridge принимает оба, задавайте одну из них |
| `ANTHROPIC_MODEL` | модель сессии; то же, что флаг `--model` |
| `ANTHROPIC_DEFAULT_OPUS_MODEL`, `ANTHROPIC_DEFAULT_SONNET_MODEL`, `ANTHROPIC_DEFAULT_HAIKU_MODEL` | какие модели каталога стоят за алиасами `opus`, `sonnet`, `haiku` (команда `/model`, настройки саб-агентов). `haiku` используется и для фоновых запросов клиента — выберите недорогую модель |
| `CLAUDE_CODE_SUBAGENT_MODEL` | модель по умолчанию для саб-агентов, которым не задана своя |
| `CLAUDE_CODE_MAX_CONTEXT_TOKENS` | реальное контекстное окно модели (колонка «Контекст» в таблице моделей); без него клиент считает окно равным 200k и по нему запускает авто-сжатие истории |
| `CLAUDE_CODE_MAX_OUTPUT_TOKENS` | лимит длины ответа: клиент запрашивает `max_tokens` 32000; если модель отклоняет такой лимит (ошибка 400 с упоминанием `max_tokens`), уменьшите |
| `ANTHROPIC_CUSTOM_MODEL_OPTION` | добавить любую модель каталога отдельным пунктом в список `/model`, не трогая алиасы |
| `CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1` | клиент запросит `GET /v1/models` и добавит в список `/model` модели каталога, в идентификаторе которых есть «claude»; остальные модели указывайте явно |
| `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1` | отключает служебные запросы клиента к сторонним серверам (телеметрия, проверка обновлений, отчёты об ошибках) |
| `API_TIMEOUT_MS` | таймаут одного запроса, по умолчанию 10 минут |

**Что уходит модели.** Каждый запрос Claude Code — это вся история сессии, системная инструкция, описания двух десятков инструментов, расширенное мышление (`thinking`) и кэширование промпта (`cache_control`). Для моделей семейства Claude LLMBridge передаёт запрос как есть: мышление, кэш промпта и служебные beta-заголовки работают штатно. Для остальных моделей запрос переводится в универсальный формат: `thinking` становится режимом рассуждений фиксированной глубины (только у моделей с тегом «Рассуждения» в таблице моделей, иначе отбрасывается), параметры `context_management` и `output_config` не передаются, инструменты и изображения работают.

**Защита ПДн и Claude Code.** Если на ключе включена защита ПДн, запрос всегда переводится в универсальный формат — маскирование требует разбора текста, — в том числе для моделей Claude: расширенное мышление заменяется режимом рассуждений, служебные beta-возможности не передаются. Остальное — в разделе «Общее для агентов» ниже.

#### Codex CLI

Codex работает с LLMBridge через Responses API (`POST /v1/responses`). Формат запросов сверен с Codex CLI 0.153–0.154. Конфигурация — `~/.codex/config.toml`:

```toml
model_provider = "llmbridge"
model = "MODEL_ID"
# Контекстное окно модели (колонка «Контекст»): Codex не знает его для сторонних моделей
model_context_window = 128000
# Только для моделей с тегом «Рассуждения» — иначе шлюз вернёт 400 unsupported_parameter
# model_reasoning_effort = "medium"

[model_providers.llmbridge]
name = "LLMBridge"
base_url = "https://llmbridge.ru/v1"
env_key = "LLMBRIDGE_API_KEY"
wire_api = "responses"
```

```bash
export LLMBRIDGE_API_KEY="lb-..."
codex                                    # интерактивно
codex exec --skip-git-repo-check "..."   # без интерактива
codex -m OTHER_MODEL_ID "..."            # другая модель на один запуск
```

**Отдельный профиль.** Чтобы не менять основной конфиг, положите те же строки в файл `~/.codex/llmbridge.config.toml` и запускайте `codex --profile llmbridge`: профиль накладывается поверх базового `config.toml`.

**Что поддерживается.** Потоковый ответ, function-инструменты Codex (команды, чтение и правка файлов), параллельные вызовы инструментов, `reasoning.effort`. Codex сам передаёт `"store": false` и полную историю сессии в каждом запросе. Ограничения LLMBridge: встроенные инструменты формата Responses, которые Codex добавляет к запросу (`web_search`, пространства имён), шлюз отбрасывает без ошибки — веб-поиск в Codex через LLMBridge не работает; изображения (`codex -i`, результат `view_image`) отклоняются с 400 — вход только текстовый; `previous_response_id` и фоновые запросы не поддерживаются (Codex их не использует).

**Служебный трафик клиентов.** Запросы к моделям Codex отправляет только в LLMBridge, но при каждом запуске дополнительно обращается к серверам своего разработчика и к GitHub (проверка обновлений, флаги функций) — учитывайте это в закрытом контуре. Claude Code с `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1` обращается только к LLMBridge.

#### Общее для агентов: расход, лимиты, ПДн, база знаний

- **Расход.** Агент отправляет в каждом запросе всю историю сессии, системную инструкцию и описания инструментов (десятки тысяч токенов), поэтому стоимость хода растёт с длиной сессии. Чаще начинайте новую сессию или сжимайте историю средствами клиента (в Claude Code — `/compact`); расход по ключу виден в разделе «Аналитика» портала.
- **Лимиты и ошибки.** Остаток бюджета и лимиты ключа — `GET /v1/limits` (бесплатно). При исчерпании бюджета шлюз отвечает 402, при превышении частоты — 429, при приостановке компании — 403; Claude Code показывает их как `API Error: 402 …`, Codex — как `ERROR: {"error": …}`. 429 и 5xx оба клиента повторяют сами. Параллельные саб-агенты Claude Code открывают несколько потоков одновременно — учитывайте лимит одновременных потоков ключа (`concurrent_streams` в `GET /v1/limits`).
- **Защита ПДн.** Если на ключе включена защита, маскируется всё, что агент отправляет модели: текст сообщений, содержимое файлов и вывод команд в результатах инструментов, аргументы вызовов; в ответе модели, включая аргументы вызовов инструментов, плейсхолдеры заменяются обратно. См. страницу «Защита ПДн».
- **База знаний.** К `/v1/messages` и `/v1/responses` база знаний компании автоматически не подключается. Для поиска по документам компании из агента вызывайте `POST /v1/kb/search` (см. «Chat Completions») — например, через собственный MCP-сервер или скрипт.
- **Выбор модели.** Нужна модель с поддержкой инструментов, иначе `400 tools is not supported for this model`; для рассуждений — тег «Рассуждения» в таблице моделей.

#### English

Point Claude Code (or any Anthropic SDK client) at LLMBridge with:

```bash
export ANTHROPIC_BASE_URL="https://llmbridge.ru"
export ANTHROPIC_AUTH_TOKEN="lb-..."
export ANTHROPIC_MODEL="MODEL_ID"
export ANTHROPIC_DEFAULT_HAIKU_MODEL="MODEL_ID_HAIKU"
```

The client appends `/v1/messages` to the base URL. Use your portal API key as `ANTHROPIC_AUTH_TOKEN`. Set `ANTHROPIC_MODEL` and `ANTHROPIC_DEFAULT_HAIKU_MODEL` to `public_id` values from the LLMBridge model catalog (`GET /v1/models`). Free context sizing: `POST /v1/messages/count_tokens` returns `{"input_tokens": N}`.

#### Использование с SDK напрямую (без Claude Code)

Тот же `/v1/messages` подходит для официального SDK Anthropic — достаточно указать базовый адрес и ключ LLMBridge.

```python
from anthropic import Anthropic

client = Anthropic(
    base_url="https://llmbridge.ru",
    api_key="lb-...",
)

message = client.messages.create(
    model="MODEL_ID",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Привет!"}],
)
print(message.content[0].text)
```

```javascript
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic({
  baseURL: "https://llmbridge.ru",
  apiKey: "lb-...",
});

const message = await client.messages.create({
  model: "MODEL_ID",
  max_tokens: 1024,
  messages: [{ role: "user", content: "Привет!" }],
});
console.log(message.content[0].text);
```

Нужен обычный OpenAI-совместимый API? См. страницу «Chat Completions».

##### Model compatibility notes

Any enabled catalog model works in Claude Code — not only the Claude family; put its `public_id` into `ANTHROPIC_MODEL` or `--model`. The one requirement is tool-calling support: Claude Code always sends its tool definitions, and a model without tool support returns `400 tools is not supported for this model` (such models remain available via `POST /v1/chat/completions` and the web chat). The client warning about an unrecognized model ID (“…is not a model this version of Claude Code recognizes” or, in newer clients, “…isn't described by this version's model catalog”; in non-interactive `claude -p`/SDK mode it is printed to stderr as `[claude-code:unrecognized_model] {…}`) is harmless — the context window is simply unknown to the client and assumed to be 200k, which drives auto-compaction; set `CLAUDE_CODE_MAX_CONTEXT_TOKENS` to the real value if needed. An `overloaded_error` means the model is temporarily unavailable — retry later or pick another catalog model. Codex CLI is supported via the Responses API (`POST /v1/responses`); setup and limits are in “Codex CLI” below.

##### Claude Code: settings file and model aliases

Instead of exporting variables, put them into the `env` block of `~/.claude/settings.json` (user) or `.claude/settings.json` (project; keep the key out of it — the file is committed to git):

```json
{
  "env": {
    "ANTHROPIC_BASE_URL": "https://llmbridge.ru",
    "ANTHROPIC_AUTH_TOKEN": "lb-...",
    "ANTHROPIC_MODEL": "MODEL_ID",
    "ANTHROPIC_DEFAULT_OPUS_MODEL": "MODEL_ID",
    "ANTHROPIC_DEFAULT_SONNET_MODEL": "MODEL_ID",
    "ANTHROPIC_DEFAULT_HAIKU_MODEL": "MODEL_ID_HAIKU",
    "CLAUDE_CODE_SUBAGENT_MODEL": "MODEL_ID",
    "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1"
  }
}
```

`ANTHROPIC_AUTH_TOKEN` (`Authorization: Bearer`) and `ANTHROPIC_API_KEY` (`x-api-key`) are both accepted — set one. `ANTHROPIC_DEFAULT_OPUS/SONNET/HAIKU_MODEL` map the `opus`, `sonnet`, `haiku` aliases to catalog models (`haiku` also serves background requests — pick a cheap one); `CLAUDE_CODE_SUBAGENT_MODEL` is the default for subagents; `CLAUDE_CODE_MAX_CONTEXT_TOKENS` sets the real context window (otherwise 200k is assumed for auto-compaction); `CLAUDE_CODE_MAX_OUTPUT_TOKENS` lowers the requested `max_tokens` (32000 by default) if a model rejects it; `ANTHROPIC_CUSTOM_MODEL_OPTION` adds any catalog model to the `/model` picker; `CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1` lists catalog models whose id contains “claude” via `GET /v1/models`; `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1` turns off telemetry, update checks and error reports; `API_TIMEOUT_MS` is the per-request timeout (10 min).

For Claude-family models LLMBridge forwards the request untouched (extended thinking, prompt caching and beta headers work as is). For other models the request is translated: `thinking` becomes a fixed-depth reasoning mode (only for models tagged “Reasoning”, dropped otherwise), `context_management` and `output_config` are not forwarded; tools and images work. With PII guard enabled on the key the request is always translated, Claude models included.

##### Codex CLI

Codex talks to LLMBridge over the Responses API (`POST /v1/responses`); request format verified against Codex CLI 0.153–0.154. `~/.codex/config.toml`:

```toml
model_provider = "llmbridge"
model = "MODEL_ID"
# context window of the model (“Context” column) — Codex does not know it for custom models
model_context_window = 128000
# only for models tagged “Reasoning”, otherwise 400 unsupported_parameter
# model_reasoning_effort = "medium"

[model_providers.llmbridge]
name = "LLMBridge"
base_url = "https://llmbridge.ru/v1"
env_key = "LLMBRIDGE_API_KEY"
wire_api = "responses"
```

```bash
export LLMBRIDGE_API_KEY="lb-..."
codex                                    # interactive
codex exec --skip-git-repo-check "..."   # non-interactive
codex -m OTHER_MODEL_ID "..."            # another model for one run
```

To keep your main config intact, put the same lines into `~/.codex/llmbridge.config.toml` and run `codex --profile llmbridge` (the profile is layered over the base `config.toml`). Supported: streaming, Codex function tools, parallel tool calls, `reasoning.effort`; Codex sends `"store": false` and the full history on every turn. Limits: built-in Responses tools that Codex attaches (`web_search`, namespaces) are dropped silently — web search does not work through LLMBridge; images (`codex -i`, `view_image` output) are rejected with 400 (text-only input); `previous_response_id` and background requests are unsupported (Codex does not use them).

**Client traffic.** Codex sends model requests only to LLMBridge, but on every launch it also contacts its vendor's servers and GitHub (update check, feature flags) — keep this in mind on restricted networks. Claude Code with `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1` talks to LLMBridge only.

##### Notes for agents

- **Cost.** Every turn resends the whole session plus the system prompt and tool definitions (tens of thousands of tokens), so cost per turn grows with session length; start new sessions more often or compact (`/compact` in Claude Code). Usage per key is shown in the portal’s “Analytics”.
- **Limits and errors.** `GET /v1/limits` (free) shows remaining budget and key limits. Budget exhausted — 402, rate limit — 429, company suspended — 403; Claude Code shows them as `API Error: 402 …`, Codex as `ERROR: {"error": …}`. Both clients retry 429 and 5xx on their own. Parallel Claude Code subagents open several streams at once — mind the key’s `concurrent_streams` limit.
- **PII guard.** With PII guard enabled on the key, everything the agent sends (messages, file contents and command output in tool results, tool-call arguments) is masked; placeholders in the model’s reply, tool-call arguments included, are restored.
- **Knowledge base.** The company knowledge base is not attached to `/v1/messages` or `/v1/responses`; call `POST /v1/kb/search` (see “Chat Completions”) from your own tool or MCP server.
- **Model choice.** The model must support tools (otherwise `400 tools is not supported for this model`); reasoning needs the “Reasoning” tag.
