## Create embeddings

`POST /v1/embeddings`

Converts text into a vector representation. Vectors are used for semantic search, clustering, duplicate detection, and finding similar CRM cards. The request and response format is compatible with the OpenAI API. Streaming is not supported.

Embeddings are supported only by models whose `capabilities.embeddings` field equals `true` in [`GET /v1/models`](/docs/ai/models/list).

## Request fields (body)

| Field | Type | Required | Default | Description |
|------|-----|:-----:|-----------|---------|
| `model` | string | yes | — | Identifier of a model that supports embeddings. List: [`GET /v1/models`](/docs/ai/models/list) |
| `input` | string \| string[] | yes | — | Text to vectorize: a single string or an array of 1 to 64 strings. One vector is returned per string. An empty string, an empty array, and an array longer than 64 strings are rejected with `400` |
| `encoding_format` | string | no | `float` | Format of the vector values: `float` or `base64` |
| `dimensions` | integer | no | — | Desired vector dimensionality. For `bitrix/embeddings`, an integer from 32 to 4096: the vector is truncated to that many leading values and re-normalized to unit length. A value outside the range is rejected with `400 invalid_request`. Other models receive the parameter unchanged and handle it according to their own rules |

## Examples

### curl — personal key

```bash
curl -X POST https://vibecode.bitrix24.com/v1/embeddings \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "bitrix/embeddings",
    "input": "We want a CRM for 50 users"
  }'
```

### curl — OAuth application

```bash
curl -X POST https://vibecode.bitrix24.com/v1/embeddings \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "bitrix/embeddings",
    "input": "We want a CRM for 50 users"
  }'
```

### JavaScript — personal key

```javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/embeddings', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_API_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    model: 'bitrix/embeddings',
    input: ['First text', 'Second text'],
  }),
})

const result = await res.json()
console.log(result.data.length)        // 2 — one vector per string
console.log(result.data[0].embedding)  // [0.0203, 0.0034, ...]
```

### JavaScript — OAuth application

```javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/embeddings', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_APP_KEY',
    'Authorization': 'Bearer USER_SESSION_TOKEN',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    model: 'bitrix/embeddings',
    input: ['First text', 'Second text'],
  }),
})

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

## Response fields

The response comes in raw OpenAI format, without the `success` and `data` wrapper.

| Field | Type | Description |
|------|-----|---------|
| `object` | string | Always `list` |
| `data` | array | Array of vectors, one per `input` string |
| `data[].object` | string | Always `embedding` |
| `data[].embedding` | number[] | The vector values. With `encoding_format: base64`, the vector arrives as a string |
| `data[].index` | number | Position of the string in the original `input` |
| `model` | string | The model that processed the request |
| `usage.prompt_tokens` | number | Input text tokens. Usage is calculated from them |
| `usage.completion_tokens` | number | Always `0` — embeddings produce no response tokens |
| `usage.total_tokens` | number | Equals `prompt_tokens` |

## Response example

The first three vector values are shown. The full dimensionality depends on the model — for `bitrix/embeddings` it is 4096 values, and the vector arrives normalized to unit length.

```json
{
  "object": "list",
  "model": "bitrix/embeddings",
  "data": [
    {
      "object": "embedding",
      "index": 0,
      "embedding": [0.0203, 0.0034, -0.0156]
    }
  ],
  "usage": {
    "prompt_tokens": 12,
    "completion_tokens": 0,
    "total_tokens": 12
  }
}
```

## Error response example

`501 embeddings_unsupported` — the model does not support embeddings:

```json
{
  "error": {
    "message": "Model \"bitrix/bitrixgpt-5.5\" does not support embeddings.",
    "type": "server_error",
    "code": "embeddings_unsupported"
  }
}
```

## Errors

| HTTP | Code | Description |
|------|-----|---------|
| 400 | `invalid_request` | Invalid parameters — empty `input`, malformed request body |
| 400 | `invalid_request` | A `dimensions` value outside the 32…4096 range was passed for `bitrix/embeddings`. The response carries a `param` field set to `dimensions`. The request was not executed and nothing was charged |
| 404 | `ai_model_not_found` | Model not found or disabled |
| 501 | `embeddings_unsupported` | The model or provider does not support embeddings |
| 402 | `ai_credentials_not_configured` | No provider credentials for the model — connect your own key |
| 402 | `insufficient_balance` | Insufficient funds for a paid model |
| 402 | `ai_quota_exhausted` | The portal's monthly AI quota is exhausted. The `reason` field distinguishes the case: `breaker` — the hourly over-quota spending breaker tripped, `wallet_empty` — the quota is exhausted and the portal balance has no funds, `wallet_off` — over-quota spend is not available for this portal. `resetAt` is the moment when requests will start passing again; it may be absent for `wallet_off`. In the `wallet_empty` case the response may additionally carry a `hint` string and a `topupUrl` link — see "Known specifics" below |
| 402 | `company_budget_exhausted` | The monthly company spend budget set by the portal administrator is exhausted. The `scope` field names the budget that was hit: `USER` — the caller's own budget, `PORTAL` — the budget of the whole portal. The `canRequest` field tells whether an increase can be requested: `true` for a personal budget, `false` for the portal one, which only an administrator raises. The rejection arrives only on calls that draw on the portal balance. Calls inside the plan quota and with your own provider key (BYOK) keep working |
| 403 | `scope_missing` | The API key is missing the `vibe:ai` scope |
| 429 | `ai_congested` | The AI cluster pool is overloaded. The request was not executed and nothing was charged — retry it after the delay in the `Retry-After` header. The response carries the `X-AI-Admission: shed` header, not `X-RateLimit-Scope` |
| 400 | `ai_provider_rejected` | The provider rejected the request itself (responded `400` or `422`). Retrying it unchanged will not help |
| 413 | `ai_payload_too_large` | The provider rejected the request because the body is too large. The `providerStatusCode` field carries the original status `413`. Retrying the same request returns the same response — send fewer or shorter texts |
| 429 | `ai_provider_cooldown` | The model cluster is temporarily unavailable and the platform backs off so retries do not pile up on it. The request was not executed and nothing was charged — retry it after the number of seconds in `Retry-After`. This response carries neither `X-RateLimit-Scope` nor `X-AI-Admission` |
| 502 | `ai_provider_unavailable` | The external provider answered with an error (`401`/`403`/`5xx`). The original status arrives in the `providerStatusCode` field when the provider answered the request with an HTTP status; if processing the provider's response fails (for example, a vector shorter than the requested `dimensions`), the field is absent |
| 502 | `ai_provider_network` | The platform could not connect to the provider, or the connection dropped before a response. This response carries no `providerStatusCode`: the provider never answered. Retry the request |
| 429 | `ai_pacing_limited` | A daily or weekly quota pacing window is exceeded. This is not quota exhaustion — retry the request after the delay in the `Retry-After` header. See [Pacing (day/week smoothing)](/docs/ai/consumption/quota#pacing-day-week-smoothing) |

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

## Known specifics

**Error codes come in lowercase.** Most error bodies use the raw OpenAI format `{ "error": { "message", "type", "code" } }`, without a `success` field. The `402 company_budget_exhausted` rejection uses the same format — the `scope` and `canRequest` fields sit inside the same `error` object. Only quota and pacing rejections — `402 ai_quota_exhausted` and `429 ai_pacing_limited` — and an unexpected `5xx` server error, whose code is written in uppercase, arrive in the `{ "success": false, "error": { … } }` envelope. A handler must accept both envelopes.

**The `input` array preserves order.** The vector `data[i]` corresponds to the string `input[i]`, and the `data[].index` field duplicates this position — you can use it to match the result after parallel processing.

**The processing budget for a request is about 850 seconds.** Once the budget runs out, `503 ai_provider_timeout` is returned. This response deliberately carries no `Retry-After` header: repeating the same request would hit the same budget. Split the `input` array into smaller parts.

**Response latency is not guaranteed for either a single text or an `input` array.** It can vary from call to call, and several requests with an `input` array sent in a row may respond progressively more slowly. The `dimensions` parameter does not affect response speed. Add a pause between such requests and do not rely on the `input` array in an interactive scenario.

**The top-up hint in a `402 ai_quota_exhausted` response.** Only in the `reason: "wallet_empty"` case may the response additionally carry a `hint` string and a `topupUrl` link to top up the balance. Both fields appear when enforced quota control and the top-up hint are enabled on the platform, so treat them as optional. The `hint` field in this response is a string. When the Bitrix24 account has no paid AI access, a `reason: "wallet_off"` response additionally carries `details.learnMoreUrl` — a page that describes the paid Bitrix24 plan that includes AI — and the `message` field names that plan. For other `wallet_off` causes `details.learnMoreUrl` is absent.

**Usage is calculated from input tokens only.** The `usage.completion_tokens` field is always `0`, so you pay only for the input. The current input price of every model comes from [`GET /v1/models`](/docs/ai/models/list) — read it there rather than from a fixed number in the documentation.

**The dimensionality can be reduced.** The `bitrix/embeddings` model is built so that the meaningful part of a vector is concentrated at its start: keeping the first `k` values and re-normalizing the vector to unit length left the ordering of search results unchanged in our tests, down to the smallest dimensions. This is a property of the model rather than a platform guarantee — measure it on your own data. The `dimensions` parameter does this on the platform side; you can get the same result yourself by truncating the vector and dividing every value by the length of the truncated vector. Re-normalizing is mandatory: a raw truncated vector has a length below one (for a 256-value vector the length is about 0.26), and without that step the dot product no longer matches cosine similarity and comparisons against previously stored vectors drift.

Sensible values are 256, 512, 1024, and 2048; these are reference points rather than the only accepted ones — any integer from 32 to 4096 works, including 1536 and 2000. The shorter the vector, the smaller the margin between a relevant result and a semantically close competitor, so choose the dimensionality by measuring on your own data.

**A smaller dimensionality does not make a request cheaper or faster.** The model still computes the full vector and the platform truncates it in the response: input-token usage and processing time do not change. The savings are on your side — less index storage, less memory, and faster search.

**Vectors of different dimensionality must not be mixed in one similarity index.** A 256-value and a 1024-value vector live in different spaces, and distances between them are meaningless. If you change the dimensionality, rebuild the entire index.

## See also

- [Models list](/docs/ai/models/list)
- [Create a chat completion](/docs/ai/chat/completions)
- [Company AI quota](/docs/ai/consumption/quota)
- [AI Router](/docs/ai)
