## Channel list

`GET /v1/chats/recent/channels`

Returns the public channels of the Bitrix24 account page by page, from the channel with the newest message to older ones. Use it to build a directory of channels the user can subscribe to.

## Parameters

| Parameter | Type | Required | Default | Description |
|----------|-----|:-----:|-----------|---------|
| `limit` (query) | integer | no | 50 | Rows per page, 1 to 200. A smaller value is raised to 1 and a larger one is clamped to 200, and both adjustments are echoed in `meta` |
| `lastMessageId` (query) | integer | no | — | Cursor for the next page: the smallest positive `messageId` among the rows of the previous page. Not passed on the first page |

Other parameters and repeated parameters are rejected with `400 INVALID_PARAMS`.

**Walk.** Rows are ordered by the ID of the channel's last message, from largest to smallest. Request the next page with `lastMessageId` equal to the smallest positive `messageId` of the current page. The bound is exclusive: the channel with that `messageId` is not repeated on the next page. The list ends only at `hasNextPage: false`. If `hasNextPage` is `true` but the page has no row with a positive `messageId`, repeat the request with a larger `limit`, up to 200.

## Examples

### curl — personal key

```bash
curl "https://vibecode.bitrix24.com/v1/chats/recent/channels?limit=50" \
  -H "X-Api-Key: YOUR_API_KEY"
```

### curl — OAuth application

```bash
curl "https://vibecode.bitrix24.com/v1/chats/recent/channels?limit=50" \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN"
```

### JavaScript — personal key

```javascript
const channels = []
const seen = new Set()
let limit = 50
let cursor
for (;;) {
  const url = new URL('https://vibecode.bitrix24.com/v1/chats/recent/channels')
  url.searchParams.set('limit', String(limit))
  if (cursor) url.searchParams.set('lastMessageId', String(cursor))
  const res = await fetch(url, { headers: { 'X-Api-Key': 'YOUR_API_KEY' } })
  const { data } = await res.json()
  for (const item of data.recentItems) {
    if (!seen.has(item.chatId)) { seen.add(item.chatId); channels.push(item) }
  }
  if (!data.hasNextPage) break
  const ids = data.recentItems.map((i) => i.messageId).filter((id) => id > 0)
  const next = ids.length ? Math.min(...ids) : undefined
  if (next === undefined || (cursor !== undefined && next >= cursor)) {
    if (limit === 200) throw new Error('Channel cursor did not advance; restart with a full refresh')
    limit = Math.min(limit * 2, 200)
    continue
  }
  cursor = next
  limit = 50
}
```

### JavaScript — OAuth application

```javascript
const channels = []
const seen = new Set()
let limit = 50
let cursor
for (;;) {
  const url = new URL('https://vibecode.bitrix24.com/v1/chats/recent/channels')
  url.searchParams.set('limit', String(limit))
  if (cursor) url.searchParams.set('lastMessageId', String(cursor))
  const res = await fetch(url, {
    headers: {
      'X-Api-Key': 'YOUR_APP_KEY',
      'Authorization': 'Bearer USER_SESSION_TOKEN',
    },
  })
  const { data } = await res.json()
  for (const item of data.recentItems) {
    if (!seen.has(item.chatId)) { seen.add(item.chatId); channels.push(item) }
  }
  if (!data.hasNextPage) break
  const ids = data.recentItems.map((i) => i.messageId).filter((id) => id > 0)
  const next = ids.length ? Math.min(...ids) : undefined
  if (next === undefined || (cursor !== undefined && next >= cursor)) {
    if (limit === 200) throw new Error('Channel cursor did not advance; restart with a full refresh')
    limit = Math.min(limit * 2, 200)
    continue
  }
  cursor = next
  limit = 50
}
```

## Response fields

| Field | Type | Description |
|------|-----|---------|
| `success` | boolean | Always `true` on success |
| `data.recentItems` | array | List rows, one per channel |
| `data.recentItems[].dialogId` | string | Dialog ID of the channel, `chatXXX` |
| `data.recentItems[].chatId` | number | Chat ID of the channel, the deduplication key |
| `data.recentItems[].messageId` | number | ID of the channel's last message, `0` for a channel without messages. The smallest positive value on the page is the cursor for the next one |
| `data.recentItems[].pinned` | boolean | Whether the channel is pinned for the current user. It does not affect the row order |
| `data.recentItems[].dateLastActivity` | null | Always `null` in this list: channels are ordered by message ID, not by date |
| `data.chats` | array | Channel cards: `id`, `dialogId`, `name`, `type`, `owner`, the current user's `role` and other chat fields |
| `data.messages` | array | The channels' last messages |
| `data.users` | array | Authors of the last messages |
| `data.recentConfigs` | array | The list sections each channel is counted in: `chatId` and `sections` |
| `data.hasNextPage` | boolean | `false` — the end of the list |
| `meta.requestedLimit` | number | The `limit` passed. Returned together with `appliedLimit`, and only when the value was outside the range of 1 to 200 |
| `meta.appliedLimit` | number | The `limit` applied |

## Response example

The main fields are shown:

```json
{
  "success": true,
  "data": {
    "recentItems": [
      {
        "dialogId": "chat206",
        "chatId": 206,
        "messageId": 329393,
        "type": "chat",
        "pinned": false,
        "unread": false,
        "dateUpdate": null,
        "dateLastActivity": null
      },
      {
        "dialogId": "chat2",
        "chatId": 2,
        "messageId": 0,
        "type": "chat",
        "pinned": false,
        "unread": false,
        "dateUpdate": null,
        "dateLastActivity": null
      }
    ],
    "chats": [
      {
        "id": 206,
        "dialogId": "chat206",
        "name": "Company news",
        "type": "openChannel",
        "owner": 4,
        "role": "guest"
      }
    ],
    "messages": [
      {
        "id": 329393,
        "chatId": 206,
        "authorId": 4,
        "date": "2026-09-10T09:15:00+00:00",
        "text": "Quarterly results are published"
      }
    ],
    "recentConfigs": [
      { "chatId": 206, "sections": ["default", "openChannel", "channel"] }
    ],
    "hasNextPage": false
  }
}
```

## Error response example

400 — the cursor is not a positive number:

```json
{
  "success": false,
  "error": {
    "code": "INVALID_PARAMS",
    "message": "`lastMessageId` must be an integer, 1 or more — the smallest positive `messageId` among the rows of the previous page."
  }
}
```

## Errors

| HTTP | Code | Description |
|------|-----|---------|
| 400 | `INVALID_PARAMS` | A parameter other than `limit` and `lastMessageId`, a repeated parameter, a non-numeric `limit`, or `lastMessageId` less than `1`. Checked before the Bitrix24 call |
| 422 | `BITRIX_ERROR` | Bitrix24 returned an error; the Bitrix24 code is in `error.b24Code` |
| 403 | `BITRIX_ACCESS_DENIED` | Bitrix24 denied access |
| 403 | `SCOPE_DENIED` | The API key does not have the `im` scope |
| 401 | `TOKEN_MISSING` | The API key has no Bitrix24 tokens configured |
| 502 | `BITRIX_UNAVAILABLE` | Bitrix24 is unavailable or returned a server error |

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

## Known specifics

**The list holds all public channels of the Bitrix24 account, not only yours.** Channels the user is not a member of are returned too, with the `guest` role in `data.chats[].role`.

**Channels without messages come last.** Their `messageId` is `0`, and they do not serve as a cursor. If such channels do not fit on one page of 200 rows, the walk cannot continue past them: the next page would have no cursor.

## See also

- [Recent dialogs](/docs/chats/discovery/recent)
- [Collab list](/docs/chats/discovery/recent-collabs)
- [External chats of a section](/docs/chats/discovery/recent-external)
- [Chat discovery](/docs/chats/discovery)
