# Stage history

`GET /v1/stage-history`

Returns the history of CRM record transitions across stages and statuses — when a record reached each pipeline stage. Used for reports on the velocity of deals, leads and smart-process items and for conversion analysis across pipelines.

Bitrix24 API: `crm.stagehistory.list`
Scope: `crm`

## Parameters

The set of stage filter fields depends on the record type. For the stage-based types `deal`, `invoice`, `new-invoice` and smart processes, `stageId`, `stageSemanticId`, `categoryId` are available. For the status-based type `lead`, `statusId`, `statusSemanticId` are available. A field that does not belong to the type returns `400 INVALID_FILTER_FIELD`.

| Parameter | Type | Required | Default | Description |
|----------|-----|:-----:|-----------|----------|
| `entityType` (query) | string | no | `deal` | CRM record type: `deal`, `lead`, `invoice`, `new-invoice` — an alias of `invoice`. For a smart process, pass its numeric ID as the value of `entityType`, for example `?entityType=128`. The separate query keys `entityTypeId` and `entityTypeID` are refused with `400 INVALID_ENTITY_TYPE`. List: `GET /v1/smart-processes`. Supply the parameter at most once |
| `ownerId` (query) | number | no | — | Identifier of the record whose transition history to return. Depends on `entityType`. Sources: `GET /v1/deals`, `GET /v1/leads`, `GET /v1/invoices`, and `GET /v1/items/{entityTypeId}` for smart processes |
| `typeId` (query) | number | no | — | Transition type: `1` — record creation, `2` — move to an intermediate stage, `3` — move to a final stage, `5` — pipeline change |
| `createdAfter` (query) | string | no | — | Transitions from the specified moment, inclusive (ISO 8601) |
| `createdBefore` (query) | string | no | — | Transitions up to the specified moment, inclusive (ISO 8601) |
| `stageId` (query) | string | no | — | Stage-based types only. Filter by stage. List for deals: `GET /v1/statuses?filter[entityId]=DEAL_STAGE`; for a smart process: `GET /v1/statuses?filter[entityId]=DYNAMIC_{entityTypeId}_STAGE_{categoryId}` |
| `stageSemanticId` (query) | string | no | — | Stage-based types only. Stage semantics: `P` — intermediate, `S` — successful, `F` — failed |
| `categoryId` (query) | number | no | — | Stage-based types only. Pipeline identifier. List for deals: `GET /v1/deal-categories`; for a smart process: `GET /v1/categories/{entityTypeId}` |
| `statusId` (query) | string | no | — | `lead` only. Filter by status. List: `GET /v1/statuses?filter[entityId]=STATUS` |
| `statusSemanticId` (query) | string | no | — | `lead` only. Status semantics: `P` — intermediate, `S` — successful, `F` — failed |
| `limit` (query) | number | no | `50` | Number of records per call. Maximum `5000` |
| `offset` (query) | number | no | `0` | Selection offset. Rounded down to a multiple of `50` |

For `limit > 50`, Vibecode automatically paginates the request on the server side. The maximum is 5000 records per call. The `meta.hasMore` field shows whether there are records beyond the current selection.

The generic `filter` parameter is not applied by this endpoint. By default, the API preserves the previous HTTP 200 response and data, adds `Warning: 299 - "filter is not supported on this endpoint and was ignored"`, and records unsupported-filter use in the server log.

## Examples

### curl — personal key

```bash
curl "https://vibecode.bitrix24.com/v1/stage-history?entityType=deal&limit=50" \
  -H "X-Api-Key: YOUR_API_KEY"
```

### curl — OAuth application

```bash
curl "https://vibecode.bitrix24.com/v1/stage-history?entityType=deal&limit=50" \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN"
```

### JavaScript — personal key

```javascript
const params = new URLSearchParams({ entityType: 'deal', limit: '50' })
const res = await fetch(`https://vibecode.bitrix24.com/v1/stage-history?${params}`, {
  headers: { 'X-Api-Key': 'YOUR_API_KEY' },
})

const { success, data, meta } = await res.json()
```

### JavaScript — OAuth application

```javascript
const params = new URLSearchParams({ entityType: 'deal', limit: '50' })
const res = await fetch(`https://vibecode.bitrix24.com/v1/stage-history?${params}`, {
  headers: {
    'X-Api-Key': 'YOUR_APP_KEY',
    'Authorization': 'Bearer USER_SESSION_TOKEN',
  },
})

const { success, data, meta } = await res.json()
```

## Response fields

The set of record fields depends on the type: the stage-based types (`deal`, `invoice`, `new-invoice`, smart processes) return `stageId`, `stageSemanticId`, `categoryId`, while the status-based type `lead` returns `statusId`, `statusSemanticId`.

| Field | Type | Description |
|------|-----|----------|
| `success` | boolean | Always `true` on success |
| `data` | array | Array of history records, newest to oldest |
| `data[].id` | number | History record identifier |
| `data[].typeId` | number | Transition type: `1` — creation, `2` — intermediate stage, `3` — final stage, `5` — pipeline change |
| `data[].ownerId` | number | Identifier of the record in which the transition occurred |
| `data[].createdAt` | string | Moment of the transition to the stage (ISO 8601) |
| `data[].stageId` | string | Stage. Stage-based types only |
| `data[].stageSemanticId` | string | Stage semantics: `P`, `S`, `F`. Stage-based types only |
| `data[].categoryId` | number | Pipeline. Stage-based types only |
| `data[].statusId` | string | Status. `lead` only |
| `data[].statusSemanticId` | string | Status semantics: `P`, `S`, `F`. `lead` only |
| `meta.total` | number | Total number of records under the filter |
| `meta.hasMore` | boolean | Whether there are records beyond the current selection |

## Response example

Deal (`entityType=deal`) — transitions across pipeline stages:

```json
{
  "success": true,
  "data": [
    {
      "id": 12857,
      "typeId": 1,
      "ownerId": 7913,
      "createdAt": "2026-06-24T00:03:50+00:00",
      "categoryId": 9,
      "stageSemanticId": "P",
      "stageId": "C9:NEW"
    },
    {
      "id": 12855,
      "typeId": 1,
      "ownerId": 7911,
      "createdAt": "2026-06-23T00:02:34+00:00",
      "categoryId": 9,
      "stageSemanticId": "P",
      "stageId": "C9:NEW"
    }
  ],
  "meta": {
    "total": 2941,
    "hasMore": true
  }
}
```

Lead (`entityType=lead`) — transitions across statuses:

```json
{
  "success": true,
  "data": [
    {
      "id": 3689,
      "typeId": 1,
      "ownerId": 1001083,
      "createdAt": "2026-06-10T18:03:12+00:00",
      "statusId": "NEW",
      "statusSemanticId": "P"
    }
  ],
  "meta": {
    "total": 296,
    "hasMore": true
  }
}
```

Smart process (`entityType=164`) — transitions across stages, exactly as for a deal:

```json
{
  "success": true,
  "data": [
    {
      "id": 47,
      "typeId": 1,
      "ownerId": 55,
      "createdAt": "2026-08-04T22:07:23+00:00",
      "categoryId": 27,
      "stageSemanticId": "P",
      "stageId": "DT164_27:NEW"
    }
  ],
  "meta": {
    "total": 1,
    "hasMore": false
  }
}
```

## Error response example

400 — the filter field does not apply to the record type:

```json
{
  "success": false,
  "error": {
    "code": "INVALID_FILTER_FIELD",
    "message": "\"statusId\" is not a valid filter for entityType \"deal\". entityType \"deal\" is stage-based — use stageId."
  }
}
```

## Errors

| HTTP | Code | Description |
|------|-----|----------|
| 200 | `Warning: 299` | The filter is ignored by default; previous data is preserved and the warning is sent in the `Warning` header. |
| 400 (strict mode only) | `UNSUPPORTED_FILTER` | The server mode is disabled by default; until it is enabled, the endpoint preserves HTTP 200 and the previous data. |
| 400 | `INVALID_ENTITY_TYPE` | `entityType` was not recognised: an unknown name, a non-canonical numeric form, the numeric ID of a dedicated entity (`1`, `2`, `31` — use the name), an entity with no stage history (`3`, `4`, `5`, `7`), a repeated parameter, or an ID that does not exist on this portal. The separate query keys `entityTypeId` and `entityTypeID` are also refused; use `entityType`, for example `?entityType=128` |
| 400 | `INVALID_FILTER_FIELD` | The filter field does not apply to the specified `entityType` — for example `statusId` for `deal` |
| 400 | `INVALID_PARAMS` | The parameter was not supplied as a single string value — for example `?ownerId[]=5` or `?ownerId[x]=1`. Bracket, repeated and mixed forms are refused: `?ownerId[]=5&ownerId[]=6` and `?ownerId[]=6&ownerId=5` would otherwise silently filter by a single value. The numeric parameters (`ownerId`, `typeId`, `categoryId`, `limit`, `offset`) are additionally accepted only as an integer written in full: `?ownerId=1e3` would otherwise read the history of owner `1`, and `?ownerId=abc` would return a wider selection than requested |
| 403 | `SCOPE_DENIED` | The key lacks the `crm` scope |
| 401 | `TOKEN_MISSING` | The key has no access tokens configured |
| 401 | `MISSING_API_KEY` | The `X-Api-Key` header was not provided |
| 429 | `ERROR_LOOP_DETECTED` | Too many consecutive rejected requests on this key. Probing `entityTypeId` values is a common cause: take the ID from `GET /v1/smart-processes` instead of guessing |

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

## How to get a smart process `entityTypeId`

The numeric type identifier is returned by `GET /v1/smart-processes` — the `entityTypeId` field of
each item. It differs from the type `id` and is not derived from it.

```bash
curl "https://vibecode.bitrix24.com/v1/smart-processes" \
  -H "X-Api-Key: YOUR_API_KEY"
```

Pass the value you get as `entityType`:

```bash
curl "https://vibecode.bitrix24.com/v1/stage-history?entityType=164&limit=50" \
  -H "X-Api-Key: YOUR_API_KEY"
```

**Do not probe for the identifier.** Every request carrying a non-existent `entityTypeId` reaches
Bitrix24 and is rejected, and a run of such rejections trips `429 ERROR_LOOP_DETECTED` for the key —
after which ordinary deal history requests stop going through as well.

## Known specifics

**Sorting is not configurable.** The sort parameter is not accepted — the order of records cannot be changed.

## See also

- [Deals](/docs/entities/deals)
- [Leads](/docs/entities/leads)
- [Statuses and stages](/docs/entities/statuses)
- [Sales pipelines](/docs/entities/deal-categories)
- [Smart processes](/docs/entities/smart-processes)
- [API overview](/docs/entity-api)
- [Errors](/docs/errors)
