
## Add a key

`POST /v1/search/credentials`

Saves a BYOK key for the chosen provider for the current user. After it is added, calls to [`POST /v1/search`](/docs/search/run) and [`POST /v1/research`](/docs/search/research) with that provider do not charge Ꝟ — you are billed directly by the provider.

Before saving, the server makes a trial call to the provider with the supplied key. If the provider rejects the key — `400 INVALID_CREDENTIAL` is returned and the record is not created. Only after a successful check is the key encrypted and saved. After saving, the original value cannot be returned through the API — the output contains only the mask `<first 5>********<last 4>`.

## Request fields (body)

| Field | Type | Req. | Default | Description |
|------|-----|:-----:|-----------|---------|
| `provider` | string | yes | — | One of the BYOK providers: `tavily`, `brave`, `exa`, `you-com`, `linkup`, `perplexity`, `jina`, `z-ai`. `bitrix-search` is not accepted through v1 |
| `apiKey` | string | yes | — | A token from the chosen provider. Tavily — format `tvly-…`, Brave — a subscription token from the Brave dashboard, the rest — a token from the corresponding provider's dashboard |
| `name` | string | yes | — | A custom label for the key, shown in the list. From 1 to 64 characters |
| `isDefault` | boolean | no | `false` | Make it default for the provider. Requests to `POST /v1/search` and `POST /v1/research` without the `provider` field will go through this key |

## Examples

### curl — personal key

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

### curl — OAuth app

```bash
curl -X POST https://vibecode.bitrix24.com/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": "My Tavily",
    "isDefault": true
  }'
```

### JavaScript — personal key

```javascript
const res = await fetch('https://vibecode.bitrix24.com/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: 'My Tavily',
    isDefault: true,
  }),
})

const cred = await res.json()
console.log('Key created:', cred.id)
```

### JavaScript — OAuth app

```javascript
const res = await fetch('https://vibecode.bitrix24.com/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: 'My Tavily',
    isDefault: true,
  }),
})

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

## Response fields

| Field | Type | Description |
|------|-----|---------|
| `id` | string | Identifier of the created key. Passed to [`DELETE /v1/search/credentials/:id`](/docs/search/credentials/delete) and [`POST /v1/search/credentials/:id/test`](/docs/search/credentials/test) |
| `provider` | string | Provider identifier: one of the eight BYOK providers |
| `scope` | string | Visibility level. When created through v1 — always `USER` |
| `name` | string | Label from the request |
| `maskedKey` | string | Mask `<first 5>********<last 4>` |
| `isDefault` | boolean | Whether the key became default for the provider |
| `createdAt` | string | Creation date in ISO 8601 format |

## Response example

201 — key created:

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

## Error response example

400 — the provider rejected the key during the pre-check:

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

400 — an attempt to add `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'"
  }
}
```

Other situations are listed in the error table below.

## Errors

| HTTP | Code | Description |
|------|-----|---------|
| 400 | `INVALID_CREDENTIAL` | The provider rejected the key during the pre-check. The record is not saved. The message contains the reason — for example, `Upstream HTTP 401` for a revoked Tavily key, `Upstream HTTP 422` for an invalid Brave subscription token |
| 400 | `INVALID_REQUEST` | `apiKey` not specified, `name` empty or longer than 64 characters, `provider` not from the list of eight BYOK providers |
| 401 | `MISSING_API_KEY` | The `X-Api-Key` header is missing |
| 401 | `INVALID_API_KEY` | Invalid API key |
| 401 | `UNAUTHORIZED` | The request goes through a `vibe_app_…` key without `Authorization: Bearer <session_token>` |
| 403 | `SCOPE_DENIED` | The key lacks the `vibe:search` scope |
| 404 | `PROVIDER_NOT_FOUND` | A provider with this identifier does not exist or is disabled |
| 429 | `RATE_LIMITED` | The overall request limit is exceeded |
| 500 | `INTERNAL_ERROR` | Internal server error |

The full list of common API errors — [Errors](/docs/errors).

## Known specifics

**One default key per provider.** When a new key is created with `isDefault: true`, all other USER keys of the same provider automatically stop being default. The previous key stays in the system — it can be used through an explicit `provider` in [`POST /v1/search`](/docs/search/run) or deleted.

**Several keys of one provider without a default.** When a user has several USER keys of one provider, none is marked `isDefault: true`, and only `provider` is specified in [`POST /v1/search`](/docs/search/run) — the most recent one by `createdAt` is taken. For predictable behavior, mark the desired key as default.

**The key is verified with the provider before saving.** The server makes a trial call to the chosen provider with the supplied `apiKey`. The record is created only on a positive result; in that case the `lastVerifiedAt` field is immediately filled with the current date. This closes the class of errors "a key chosen as default that the provider has already revoked" and removes the need for a separate [`POST /v1/search/credentials/:id/test`](/docs/search/credentials/test) step right after creation. The test endpoint is needed later — to re-verify an already-saved key after rotation at the provider.

**Network errors before reaching the provider are masked as `INVALID_CREDENTIAL`.** If the verification request did not reach the provider (DNS, a network timeout between Vibecode and the provider), the user sees the same `400 INVALID_CREDENTIAL` with a message about the reason. This is a safe default — not to save a key whose status is not confirmed. Retry after a short time.

## See also

- [Your own keys (BYOK)](/docs/search/credentials) — subsection overview
- [List your keys](/docs/search/credentials/list) — `GET /v1/search/credentials`
- [Delete a key](/docs/search/credentials/delete) — `DELETE /v1/search/credentials/:id`
- [Test a key](/docs/search/credentials/test) — `POST /v1/search/credentials/:id/test`
- [Search (POST /v1/search)](/docs/search/run) — BYOK key selection cascade
- [Providers](/docs/search/providers) — which engines accept BYOK
- [Errors](/docs/errors) — API error code reference
