## Send a solution event

`POST /v1/solution-events`

A solution reports an event to the Bitrix24 account, and business processes with the "Vibecode solution event" trigger start on that account on behalf of the **owner of the solution card** in the account's catalog. The call is synchronous: the platform passes the event to the Bitrix24 account immediately.

## Access

Call the endpoint with an application key. Use the application's personal key or the key of the OAuth application it is built on. Key types are described in [Keys and authorization](/docs/keys-auth). A developer key not bound to an application gets `403 SOLUTION_KEY_REQUIRED`. The application's external API key does not work here: `403 APP_API_KEY_OUT_OF_SCOPE`. The platform finds the card and the Bitrix24 account by the key; you do not need to pass them.

Until the feature is enabled for the Bitrix24 account, the endpoint responds with `409 SOLUTION_EVENTS_NOT_ENABLED`.

A key in "read-only" mode cannot send events and gets `403 WRITE_BLOCKED_READONLY_KEY`: the card owner's processes change Bitrix24 account data.

## Request fields (body)

| Field | Type | Required | Description |
|---|---|---|---|
| `idempotencyKey` | string | yes | Your event key: 1 to 255 printable ASCII characters without spaces. An opaque identifier: **do not put** email, phone, full name or other personal data in it |
| `data` | object | yes | Any JSON object: nesting no deeper than 64 levels, no more than 262,144 bytes. The business process receives it as `Data`, alongside `CatalogItemId` |

The body must contain no other fields: otherwise `400 UNKNOWN_FIELD`.

### Size and bytes of `data`

- Size is measured on the **compact** serialization of `data` without spaces or line breaks, in UTF-8 bytes.
- The Bitrix24 account remembers the event by the key and the exact bytes of `data`. **Send a retry with the same object and the same key order**, otherwise you get `409 EVENT_KEY_CONFLICT`.
- Integers larger than 2^53 lose precision when parsed: send them as strings.
- JavaScript puts integer keys such as `"1"` and `"20"` at the start of an object. This does no harm as long as the retry is built by the same code.

## Examples

The request body is the same in all examples. Only the headers differ.

### curl — personal key

```bash
curl -X POST "https://vibecode.bitrix24.com/v1/solution-events" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"idempotencyKey": "order-8812-paid", "data": {"orderId": 8812, "status": "paid"}}'
```

### curl — OAuth application

```bash
curl -X POST "https://vibecode.bitrix24.com/v1/solution-events" \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"idempotencyKey": "order-8812-paid", "data": {"orderId": 8812, "status": "paid"}}'
```

### JavaScript — personal key

```javascript
const event = { idempotencyKey: 'order-8812-paid', data: { orderId: 8812, status: 'paid' } };

const res = await fetch('https://vibecode.bitrix24.com/v1/solution-events', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_API_KEY',
    'Content-Type': 'application/json',
  },
  // Retry with the same event object: the Bitrix24 account compares the key and the exact bytes of data.
  body: JSON.stringify(event),
});
const body = await res.json();
if (!body.success && body.error.retryable) {
  const waitSeconds = Number(res.headers.get('retry-after') ?? 30);
  // Retry the request with the same event no sooner than waitSeconds seconds later.
}
```

### JavaScript — OAuth application

```javascript
const event = { idempotencyKey: 'order-8812-paid', data: { orderId: 8812, status: 'paid' } };

const res = await fetch('https://vibecode.bitrix24.com/v1/solution-events', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_APP_KEY',
    'Authorization': 'Bearer USER_SESSION_TOKEN',
    'Content-Type': 'application/json',
  },
  // Retry with the same event object: the Bitrix24 account compares the key and the exact bytes of data.
  body: JSON.stringify(event),
});
const body = await res.json();
if (!body.success && body.error.retryable) {
  const waitSeconds = Number(res.headers.get('retry-after') ?? 30);
  // Retry the request with the same event no sooner than waitSeconds seconds later.
}
```

## Response fields

| Field | Type | Description |
|---|---|---|
| `success` | boolean | Always `true` on success |
| `data` | object | Result of passing the event to the Bitrix24 account |
| `data.status` | string | `accepted` — the Bitrix24 account accepted the event. `duplicate` — an event with this key and this data was already accepted; the Bitrix24 account remembers the key for 48 hours |
| `data.idempotencyKey` | string | Your event key from the request |

`accepted` does not mean a process has started. One event starts as many processes as there are templates on the Bitrix24 account waiting for this trigger, and none if there are no such templates: the Bitrix24 account still responds with `accepted`. A retry of an accepted event gets `duplicate`, even if the feature was later turned off on the Bitrix24 account itself or the card was made inactive.

## Response example

```json
{ "success": true, "data": { "status": "accepted", "idempotencyKey": "order-8812-paid" } }
```

## Error response example

409 — the solution card is inactive on the Bitrix24 account:

```json
{ "success": false, "error": { "code": "CARD_INACTIVE", "message": "The solution's catalog card is inactive on the Bitrix24 account.", "retryable": false } }
```

`error.retryable` appears only in the method's own responses: `true` — retry after a pause.

## Errors

| HTTP | Code | Description |
|---|---|---|
| 400 | `BODY_INVALID` | The body is not a JSON object |
| 400 | `UNKNOWN_FIELD` | The body has a field other than `idempotencyKey` and `data` |
| 400 | `EVENT_KEY_INVALID` | The key is missing, not a string, empty, longer than 255 or not made of printable ASCII characters |
| 400 | `DATA_INVALID` | `data` is missing, not an object, nested deeper than 64 levels, or a string or key name contains a lone surrogate |
| 413 | `DATA_TOO_LARGE` | `data` is larger than 262,144 bytes or the body is larger than 1 MiB |
| 403 | `SOLUTION_KEY_REQUIRED` | The key does not belong to an application of this Bitrix24 account |
| 403 | `APP_API_KEY_OUT_OF_SCOPE` | The application's external API key opens only the application's external API endpoints |
| 409 | `SOLUTION_EVENTS_NOT_ENABLED` | The feature is not enabled for the Bitrix24 account |
| 409 | `CARD_NOT_PUBLISHED` | The application card is not yet published in the Bitrix24 account's catalog or is being removed |
| 409 | `PORTAL_NOT_PAIRED` | The self-hosted Bitrix24 portal has no live connection to the platform; the Bitrix24 account administrator restores the connection |
| 409 | `PORTAL_DOMAIN_NOT_SUPPORTED` | Events are not supported for a cloud Bitrix24 portal on its own domain |
| 409 | `PORTAL_NOT_SUPPORTED` | The module on the Bitrix24 account is outdated: the Bitrix24 account administrator must update it |
| 409 | `PORTAL_FEATURE_DISABLED` | The feature is turned off on the Bitrix24 account itself |
| 409 | `CARD_NOT_FOUND` | There is no solution card on the Bitrix24 account: republish the solution |
| 409 | `CARD_INACTIVE` | The card is inactive on the Bitrix24 account |
| 409 | `OWNER_INACTIVE` | The card owner is inactive on the Bitrix24 account |
| 409 | `SOLUTION_NOT_ELIGIBLE` | The solution is unavailable to the card owner |
| 409 | `TARIFF_RESTRICTED` | The Bitrix24 account's plan does not allow events |
| 409 | `CARD_NOT_BOUND` | The card is not linked to this platform connection |
| 409 | `EVENT_KEY_CONFLICT` | The key was already accepted within the last 48 hours with different data or for a different card |
| 429 | `SOLUTION_EVENTS_RATE_LIMITED` | More than 60 events per minute per key or 120 per minute per Bitrix24 account |
| 502 | `PORTAL_CHANNEL_ERROR` | Channel error on the platform side |
| 503 | `CHANNEL_UNAVAILABLE` | The channel to the Bitrix24 account is temporarily unavailable |
| 503 | `PORTAL_TIMEOUT`, `PORTAL_UNAVAILABLE` | The Bitrix24 account did not respond or is unavailable |
| 503 | `EVENT_IN_PROGRESS` | A parallel request with the same key is still being processed |
| 503 | `STORAGE_FAILED`, `LOOKUP_FAILED`, `START_FAILED` | Failure on the Bitrix24 account; the key was not consumed |
| 503 | `BIZPROC_UNAVAILABLE` | The business processes module on the Bitrix24 account is unavailable |

Common responses for all key methods: `401` for a missing, invalid or expired key and `NO_PORTAL`, `402 ACCOUNT_FROZEN`, `403` for blocks on the key owner or the Bitrix24 account and for key-mode restrictions, `429 CALLER_RATE_LIMITED`, `429 QUOTA_EXCEEDED`, `503 POOL_EXHAUSTED`, `503 user_self_deletion_pending`. The last one means the key owner is deleting their user account on the Bitrix24 account: `Retry-After` is `604800`, and retrying is pointless until the deletion is cancelled. Common responses have no `error.retryable` field.

Events are not billed as API calls. But the key's daily quota is shared by all methods: a key that has exhausted it with Bitrix24 calls while the prepaid balance is empty gets `429 QUOTA_EXCEEDED` on events too.

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

## What to do with the response

| Response | Could the Bitrix24 account have accepted the event? | What to do |
|---|---|---|
| 200 | yes, accepted | Done, do not retry |
| 400, 413 | no | Fix the request |
| 401, 402, 403 | no | Fix access: key, key mode, payment, block |
| 503 `user_self_deletion_pending` | no | The key owner is deleting their user account on the Bitrix24 account. `Retry-After` is `604800`, the 7-day deletion grace period: retrying is pointless until the deletion is cancelled |
| 409 `EVENT_KEY_CONFLICT` | if an earlier attempt got a "yes" response (see below) — **yes, already accepted** | If there was such an attempt, the event is accepted: do **not** resend with a new key, find out why the bytes of `data` changed. If there was not, the key matched another event: resend with a new key |
| 409, other codes | no | Fix the state: feature, card, self-hosted Bitrix24 portal connection, owner, plan, Bitrix24 account module |
| 502 | no | Channel error on the platform side; a retry does not fix it |
| 429 `QUOTA_EXCEEDED` | no | Top up the balance. The quota is daily: it clears after a top-up or the next day; there is no `Retry-After` |
| 429, other codes | no | Retry after a pause |
| 503 `CHANNEL_UNAVAILABLE`, `STORAGE_FAILED`, `LOOKUP_FAILED`, `START_FAILED`, `BIZPROC_UNAVAILABLE`, `POOL_EXHAUSTED` | no | Retry after a pause |
| 503 `PORTAL_TIMEOUT`, `PORTAL_UNAVAILABLE`, `EVENT_IN_PROGRESS` | **yes** | Retry after a pause; a retry of an accepted event gets `duplicate` |
| 504, 500, your client's timeout, a dropped connection with no response | **yes** | Same as the previous row |

**The pause** is at least `Retry-After` and doubles with each attempt: 30 s, 1 min, 2 min and so on up to 1 hour. Retries count against the **same** limit as new events: retries during a long Bitrix24 account outage crowd out fresh events. Set your client's timeout to at least 30 seconds.

### Event history: when the same key is safe

On a retry, send **the same** key and data. Decide by the whole event history, not by the last response. An attempt with a "yes" response is one whose response falls in a table row where the Bitrix24 account could have accepted the event.

- **The 24-hour window** is counted from the **first** attempt with a "yes" response and is not reset by later responses. After that, decide whether to treat the event as lost or accepted, and show it to a person.
- **The history is closed** by the Bitrix24 account's response to an attempt sent no sooner than 6 minutes after the last attempt with a "yes" response. 200 — the event is accepted; the history ends. 409 `PORTAL_FEATURE_DISABLED`, `CARD_NOT_FOUND`, `CARD_INACTIVE`, `OWNER_INACTIVE`, `SOLUTION_NOT_ELIGIBLE`, `TARIFF_RESTRICTED`, `CARD_NOT_BOUND` and 503 `LOOKUP_FAILED`, `BIZPROC_UNAVAILABLE`, `START_FAILED` — the Bitrix24 account checked the key and did not find it; earlier "yes" attempts are cleared. `EVENT_KEY_CONFLICT` with an uncleared "yes" means "accepted".
- **The history is not closed** by the remaining 409 responses. These are platform refusals before the Bitrix24 account is called: `SOLUTION_EVENTS_NOT_ENABLED`, `CARD_NOT_PUBLISHED`, `PORTAL_NOT_PAIRED`, `PORTAL_DOMAIN_NOT_SUPPORTED`, and `PORTAL_NOT_SUPPORTED` from an outdated Bitrix24 account module. All of them arrive before the key check and know nothing about past attempts, as do 502, 429, 401, 402, 403, 503 `CHANNEL_UNAVAILABLE`, `POOL_EXHAUSTED`, `STORAGE_FAILED`, `EVENT_IN_PROGRESS`.
- **The same key is safe at any time** if the history has no "yes", or none since it was closed.
- **The ceiling without a "yes":** the whole history lasts no longer than 24 hours from the first attempt or, after a closing response, from that response. You cannot fix 409 responses or `QUOTA_EXCEEDED` yourself: retry them no more than once an hour. When time runs out, show the event to a person as undelivered with the last `code`. Only that person resends it, with the same key.

## Limits

No more than 60 events per minute per key and 120 per minute per Bitrix24 account, counting all solutions on the Bitrix24 account together. The `x-ratelimit-limit` header shows the per-key limit: 60 where the counter is shared, and a share of one platform node where it is not. The Bitrix24 account limit is visible only through a `429` refusal.

## See also

- [Application external API](/docs/applications/external-api)
- [A long-running method in a business process](/docs/applications/long-method)
- [Keys and authorization](/docs/keys-auth)
- [Errors](/docs/errors)
