
## Search workgroups

`POST /v1/workgroups/search`

Returns a list of workgroups matching the given filters. The same contract as [GET /v1/workgroups](./list.md), but the conditions are passed in the request body — suitable for long filters and non-Latin values.

## Request fields (body)

| Field | Type | Description |
|------|-----|---------|
| `filter` | object | Filtering conditions. Field list: [GET /v1/workgroups/fields](./fields.md). Syntax: [Filtering](/docs/filtering). |
| `select` | array | List of fields in the response. Names — from [GET /v1/workgroups/fields](./fields.md). |
| `sort` | object | Sorting: `{"<field>": "ASC"\|"DESC"}`. |
| `limit` | number | Maximum records. Default 50, maximum 5000. |
| `offset` | number | Offset from the start of the result set. Default 0. Together with a date-range filter wider than 14 days it is rejected — see `UNSTABLE_OFFSET_PAGINATION` in the "Errors" section. |
| `autoWindow` | boolean | Split the result set into weekly windows when filtering by a date range wider than 14 days. Defaults to `true`. `false` disables splitting |

For `limit > 50` Vibecode automatically paginates the request on the server side. The maximum is 5000 records per call. If more match the filter — `meta.hasMore` will be `true`.

### Get projects only

A single collection stores both workgroups and projects. The `isProject` field distinguishes them: `true` — a project, `false` — an ordinary workgroup. To select projects, pass `"filter": { "isProject": "Y" }`.

## Examples

### curl — personal key

```bash
curl -X POST "https://vibecode.bitrix24.com/v1/workgroups/search" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "filter": { "%name": "Marketing", "archived": "N" },
    "select": ["id", "name", "ownerId", "membersCount"],
    "sort": { "dateCreate": "DESC" },
    "limit": 5
  }'
```

### curl — OAuth application

```bash
curl -X POST "https://vibecode.bitrix24.com/v1/workgroups/search" \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "filter": { "%name": "Marketing", "archived": "N" },
    "select": ["id", "name", "ownerId", "membersCount"],
    "sort": { "dateCreate": "DESC" },
    "limit": 5
  }'
```

### JavaScript — personal key

```javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/workgroups/search', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_API_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    filter: { '%name': 'Marketing', archived: 'N' },
    select: ['id', 'name', 'ownerId', 'membersCount'],
    sort: { dateCreate: 'DESC' },
    limit: 5,
  }),
})

const { success, data, meta } = await res.json()
console.log(`Found ${meta.total} workgroups`)
```

### JavaScript — OAuth application

```javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/workgroups/search', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_APP_KEY',
    'Authorization': 'Bearer USER_SESSION_TOKEN',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    filter: { '%name': 'Marketing', archived: 'N' },
    select: ['id', 'name', 'ownerId', 'membersCount'],
    sort: { dateCreate: 'DESC' },
    limit: 5,
  }),
})

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

## Response fields

| Field | Type | Description |
|------|-----|---------|
| `success` | boolean | Always `true` on success |
| `data` | array | Array of workgroups (all fields — see [Workgroup fields](./fields.md)) |
| `meta.total` | number | How many records matched the filter |
| `meta.hasMore` | boolean | Whether there is a next page |
| `meta.durationMs` | number | Request duration in milliseconds |
| `meta.autoWindowed` | boolean | `true` if the result set was split into time windows |
| `meta.windowCount` | number | Number of windows. Present with `autoWindowed: true` |
| `meta.batchWaves` | number | Number of parallel request waves. Present with `autoWindowed: true` |

The `meta` fields sit next to `data`, not inside it. Pages must be walked by `meta.hasMore`: a `data` length equal to `limit` does not rule out the last page.

## Response example

```json
{
  "success": true,
  "data": [
    {
      "id": 85,
      "name": "Marketing 2026",
      "ownerId": 1271,
      "membersCount": 2,
      "dateCreate": "2026-03-20T06:45:10.000Z"
    },
    {
      "id": 83,
      "name": "New product development team",
      "ownerId": 1269,
      "membersCount": 1,
      "dateCreate": "2026-03-20T06:41:07.000Z"
    },
    {
      "id": 81,
      "name": "Regional office launch",
      "ownerId": 1271,
      "membersCount": 1,
      "dateCreate": "2026-03-20T06:29:07.000Z"
    }
  ],
  "meta": {
    "total": 3,
    "hasMore": false
  }
}
```

With a date-range filter wider than 14 days, `meta` additionally returns `autoWindowed`, `windowCount`, and `batchWaves`:

```json
{
  "success": true,
  "data": [ /* ... */ ],
  "meta": {
    "total": 29,
    "hasMore": true,
    "autoWindowed": true,
    "windowCount": 131,
    "batchWaves": 3,
    "durationMs": 4217
  }
}
```

## Error response example

401 — no authorization key was passed:

```json
{
  "success": false,
  "error": {
    "code": "MISSING_API_KEY",
    "message": "API key required. Pass via X-Api-Key header."
  }
}
```

## Errors

| HTTP | Code | Description |
|------|-----|---------|
| 400 | `INVALID_PARAMS` | The request body does not parse or contains invalid fields |
| 401 | `MISSING_API_KEY` | The `X-Api-Key` header was not passed |
| 401 | `INVALID_API_KEY` | The provided key was not recognized |
| 403 | `SCOPE_DENIED` | The key is missing the `sonet_group` scope |
| 400 | `UNSTABLE_OFFSET_PAGINATION` | `offset` greater than zero together with a date-range filter wider than 14 days. Two different retrieval algorithms produce inconsistent results, so the request is rejected. Take everything in a single request with `limit` up to 5000, or pass `autoWindow: false` with sorting by `id`, or split the date range into parts yourself |

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

## Known specifics

**Time-window splitting.** A date-range filter wider than 14 days is automatically split into weekly windows executed in parallel waves, so the result set bypasses the ceiling of 5000 records per call. `meta` then returns `autoWindowed: true`, the number of windows `windowCount`, and the number of waves `batchWaves`. The `autoWindow: false` parameter disables splitting. While splitting is active, an `offset` greater than zero is rejected with `UNSTABLE_OFFSET_PAGINATION`.

**Boolean fields in the filter can be passed in two ways.** In the POST body both `Y` / `N` and native JSON `true` / `false` are allowed: `"filter": {"active": "Y"}` and `"filter": {"active": true}` produce the same result set. In the response the same fields always come back as `true` / `false`.

**A `%` prefix in the field name — substring search.** `"filter": {"%name": "Marketing"}` will find all groups whose name contains "Marketing". Without the prefix the filter requires an exact match.

**The sort parameter in the body is `sort`.** An `order` key in the body is ignored without an error — the result set is returned in the default order. Use `"sort": {"dateCreate": "DESC"}` or the string form `"sort": "dateCreate"` (the default direction is `ASC`).

## See also

- [List workgroups](./list.md)
- [Get a workgroup](./get.md)
- [Workgroup fields](./fields.md)
- [Filtering syntax](/docs/filtering)
