
## Добавить ключ

`POST /v1/search/credentials`

Сохраняет BYOK-ключ выбранного провайдера для текущего пользователя. После добавления вызовы [`POST /v1/search`](/docs/search/run) и [`POST /v1/research`](/docs/search/research) с этим провайдером не списывают Ꝟ — оплата идёт напрямую с вашего аккаунта у поставщика.

Перед сохранением сервер делает пробный вызов к провайдеру с присланным ключом. Если провайдер отклоняет ключ — возвращается `400 INVALID_CREDENTIAL`, запись не создаётся. Только после успешной проверки ключ шифруется и сохраняется. После сохранения исходное значение через API не вернуть — в выдаче только маска `<первые 5>********<последние 4>`.

## Поля запроса (body)

| Поле | Тип | Обяз. | По умолч. | Описание |
|------|-----|:-----:|-----------|---------|
| `provider` | string | да | — | Один из BYOK-провайдеров: `tavily`, `brave`, `exa`, `you-com`, `linkup`, `perplexity`, `jina`, `z-ai`. `bitrix-search` через v1 не принимается |
| `apiKey` | string | да | — | Токен у выбранного провайдера. Tavily — формат `tvly-…`, Brave — токен подписки из кабинета Brave, остальные — токен из кабинета соответствующего поставщика |
| `name` | string | да | — | Произвольная подпись ключа для отображения в списке. От 1 до 64 символов |
| `isDefault` | boolean | нет | `false` | Сделать дефолтным для провайдера. Запросы к `POST /v1/search` и `POST /v1/research` без поля `provider` пойдут через этот ключ |

## Примеры

### curl — личный ключ

```bash
curl -X POST https://vibecode.bitrix24.tech/v1/search/credentials \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "tavily",
    "apiKey": "tvly-abc123def456",
    "name": "Мой Tavily",
    "isDefault": true
  }'
```

### curl — OAuth-приложение

```bash
curl -X POST https://vibecode.bitrix24.tech/v1/search/credentials \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "tavily",
    "apiKey": "tvly-abc123def456",
    "name": "Мой Tavily",
    "isDefault": true
  }'
```

### JavaScript — личный ключ

```javascript
const res = await fetch('https://vibecode.bitrix24.tech/v1/search/credentials', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_API_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    provider: 'tavily',
    apiKey: 'tvly-abc123def456',
    name: 'Мой Tavily',
    isDefault: true,
  }),
})

const cred = await res.json()
console.log('Создан ключ:', cred.id)
```

### JavaScript — OAuth-приложение

```javascript
const res = await fetch('https://vibecode.bitrix24.tech/v1/search/credentials', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_APP_KEY',
    'Authorization': 'Bearer USER_SESSION_TOKEN',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    provider: 'tavily',
    apiKey: 'tvly-abc123def456',
    name: 'Мой Tavily',
    isDefault: true,
  }),
})

const cred = await res.json()
```

## Поля ответа

| Поле | Тип | Описание |
|------|-----|---------|
| `id` | string | Идентификатор созданного ключа. Передаётся в [`DELETE /v1/search/credentials/:id`](/docs/search/credentials/delete) и [`POST /v1/search/credentials/:id/test`](/docs/search/credentials/test) |
| `provider` | string | Идентификатор провайдера: один из восьми BYOK-провайдеров |
| `scope` | string | Уровень видимости. При создании через v1 — всегда `USER` |
| `name` | string | Подпись из запроса |
| `maskedKey` | string | Маска `<первые 5>********<последние 4>` |
| `isDefault` | boolean | Стал ли ключ дефолтным для провайдера |
| `createdAt` | string | Дата создания в формате ISO 8601 |

## Пример ответа

201 — ключ создан:

```json
{
  "id": "cmol3pnk5001fo70zefbwb6dl",
  "provider": "tavily",
  "scope": "USER",
  "name": "Мой Tavily",
  "maskedKey": "tvly-********f456",
  "isDefault": true,
  "createdAt": "2026-04-30T06:26:51.653Z"
}
```

## Пример ответа при ошибке

400 — провайдер отклонил ключ при предварительной проверке:

```json
{
  "error": {
    "code": "INVALID_CREDENTIAL",
    "message": "Upstream HTTP 401"
  }
}
```

400 — попытка добавить `bitrix-search`:

```json
{
  "error": {
    "code": "INVALID_REQUEST",
    "message": "provider: Invalid enum value. Expected 'tavily' | 'brave' | 'exa' | 'you-com' | 'linkup' | 'perplexity' | 'jina' | 'z-ai', received 'bitrix-search'"
  }
}
```

Остальные ситуации перечислены в таблице ошибок ниже.

## Ошибки

| HTTP | Код | Описание |
|------|-----|---------|
| 400 | `INVALID_CREDENTIAL` | Провайдер отклонил ключ при предварительной проверке. Запись не сохранена. Сообщение содержит причину — например, `Upstream HTTP 401` для отозванного ключа Tavily, `Upstream HTTP 422` для неверного токена подписки Brave |
| 400 | `INVALID_REQUEST` | Не указан `apiKey`, `name` пустой или длиннее 64 символов, `provider` не из списка восьми BYOK-провайдеров |
| 401 | `MISSING_API_KEY` | Отсутствует заголовок `X-Api-Key` |
| 401 | `INVALID_API_KEY` | Неверный API-ключ |
| 401 | `UNAUTHORIZED` | Запрос идёт через ключ `vibe_app_…` без `Authorization: Bearer <session_token>` |
| 403 | `SCOPE_DENIED` | Ключу не хватает скоупа `vibe:search` |
| 404 | `PROVIDER_NOT_FOUND` | Провайдер с таким идентификатором отсутствует или отключён |
| 429 | `RATE_LIMITED` | Превышен общий лимит запросов |
| 500 | `INTERNAL_ERROR` | Внутренняя ошибка сервера |

Полный список общих ошибок API — [Ошибки](/docs/errors).

## Известные особенности

**Дефолтный ключ — один на провайдер.** При создании нового ключа с `isDefault: true` все остальные USER-ключи того же провайдера автоматически перестают быть дефолтными. Прежний ключ остаётся в системе — его можно использовать через явный `provider` в [`POST /v1/search`](/docs/search/run) или удалить.

**Несколько ключей одного провайдера без дефолта.** Когда у пользователя несколько USER-ключей одного провайдера, ни один не помечен `isDefault: true`, и в [`POST /v1/search`](/docs/search/run) указан только `provider` — берётся самый поздний по `createdAt`. Для предсказуемого поведения отметьте нужный ключ как дефолтный.

**Ключ проверяется у провайдера до сохранения.** Сервер делает пробный вызов выбранного провайдера с переданным `apiKey`. Запись создаётся только при положительном результате; в этом случае поле `lastVerifiedAt` сразу заполняется текущей датой. Это закрывает класс ошибок «дефолтным выбран ключ, который провайдер уже отозвал» и устраняет необходимость отдельного шага [`POST /v1/search/credentials/:id/test`](/docs/search/credentials/test) сразу после создания. Тестовый эндпоинт нужен позже — для перепроверки уже сохранённого ключа после ротации у поставщика.

**Сетевые ошибки до провайдера маскируются под `INVALID_CREDENTIAL`.** Если запрос проверки не дошёл до провайдера (DNS, тайм-аут сети между Вайбкод и поставщиком), пользователь увидит ту же `400 INVALID_CREDENTIAL` с сообщением о причине. Это безопасный дефолт — не сохранять ключ, статус которого не подтверждён. Повторите попытку через короткое время.

## Смотрите также

- [Свои ключи (BYOK)](/docs/search/credentials)
- [Список своих ключей](/docs/search/credentials/list)
- [Удалить ключ](/docs/search/credentials/delete)
- [Проверить ключ](/docs/search/credentials/test)
- [Поиск (POST /v1/search)](/docs/search/run)
- [Провайдеры](/docs/search/providers)
- [Ошибки](/docs/errors)
