# 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 and leads 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` and `invoice`, `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` |
| `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` |
| `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 | — | `deal`, `invoice` only. Filter by stage. List: `GET /v1/statuses?filter[entityId]=DEAL_STAGE` |
| `stageSemanticId` (query) | string | no | — | `deal`, `invoice` only. Stage semantics: `P` — intermediate, `S` — successful, `F` — failed |
| `categoryId` (query) | number | no | — | `deal`, `invoice` only. Pipeline identifier. List: `GET /v1/deal-categories` |
| `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. Maximum — 5000 records per call. The `meta.hasMore` field shows whether there are records beyond the current selection.

## 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` and `invoice` 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. `deal`, `invoice` only |
| `data[].stageSemanticId` | string | Stage semantics: `P`, `S`, `F`. `deal`, `invoice` only |
| `data[].categoryId` | number | Pipeline. `deal`, `invoice` 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
  }
}
```

## 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 |
|------|-----|----------|
| 400 | `INVALID_ENTITY_TYPE` | Unknown `entityType`. The message contains the list of supported values |
| 400 | `INVALID_FILTER_FIELD` | The filter field does not apply to the specified `entityType` — for example `statusId` for `deal` |
| 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 |

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

## 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)
- [Entity reference](/docs/entity-api)
- [Errors](/docs/errors)
