## Review campaigns

`GET /v1/performan/review/campaigns`

Returns the performance review campaigns the key owner takes part in. The listing is cursor based, and an optional parameter narrows it down to a single campaign.

## Parameters

| Parameter | Type | Required | Default | Description |
|-----------|------|:--------:|---------|-------------|
| `campaignId` (query) | number | no | — | Narrow the listing to one campaign. A campaign id, a positive integer |
| `limit` (query) | number | no | 50 | Records per page, 200 at most |
| `afterCursorId` (query) | number | no | — | The next-page cursor. The value comes from `meta.nextCursor.id` of the previous response. The first request omits it |

## Examples

### curl — personal key

```bash
curl "https://vibecode.bitrix24.com/v1/performan/review/campaigns?limit=20" \
  -H "X-Api-Key: YOUR_API_KEY"
```

### curl — OAuth application

```bash
curl "https://vibecode.bitrix24.com/v1/performan/review/campaigns?limit=20" \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN"
```

### JavaScript — personal key

```javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/performan/review/campaigns?limit=20', {
  headers: { 'X-Api-Key': 'YOUR_API_KEY' },
})

const body = await res.json()

if (!body.success) {
  throw new Error(`${body.error.code}: ${body.error.message}`)
}

for (const campaign of body.data) {
  console.log(campaign.id, campaign.title, campaign.status)
}

// Next page: put the cursor id into afterCursorId of the new request
console.log(body.meta.nextCursor)
```

### JavaScript — OAuth application

```javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/performan/review/campaigns?limit=20', {
  headers: {
    'X-Api-Key': 'YOUR_APP_KEY',
    'Authorization': 'Bearer USER_SESSION_TOKEN',
  },
})

const body = await res.json()

if (!body.success) {
  throw new Error(`${body.error.code}: ${body.error.message}`)
}

console.log(body.data, body.meta.nextCursor)
```

## Response fields

| Field | Type | Description |
|-------|------|-------------|
| `success` | boolean | Always `true` on success |
| `data` | array | The campaign array |
| `data[].id` | number | Campaign id. Goes into the `campaignId` parameter of the other methods of the section |
| `data[].title` | string | Campaign name |
| `data[].status` | string | Campaign state: `created` — created, `inProgress` — running, `finished` — finished |
| `data[].startDate` | string | Start date, ISO 8601 |
| `data[].endDate` | string | End date, ISO 8601 |
| `data[].createdAt` | string | When the campaign was created, ISO 8601 |
| `meta.nextCursor` | object \| null | The next-page cursor. `null` — no more records |
| `meta.nextCursor.id` | number | The value for the `afterCursorId` parameter of the next request |

## Response example

```json
{
  "success": true,
  "data": [
    {
      "id": 2,
      "title": "Q4 performance review",
      "status": "created",
      "startDate": "2026-10-01T09:00:00+00:00",
      "endDate": "2026-12-15T09:00:00+00:00",
      "createdAt": "2026-09-20T11:14:02+00:00"
    },
    {
      "id": 1,
      "title": "Q3 performance review",
      "status": "finished",
      "startDate": "2026-06-01T09:00:00+00:00",
      "endDate": "2026-08-31T09:00:00+00:00",
      "createdAt": "2026-05-25T08:40:11+00:00"
    }
  ],
  "meta": { "nextCursor": { "id": 1 } }
}
```

## Error response example

400 — a parameter failed the check:

```json
{
  "success": false,
  "error": {
    "code": "INVALID_PARAMS",
    "message": "limit must be a positive integer"
  }
}
```

## Errors

| HTTP | Code | Description |
|------|------|-------------|
| 400 | `INVALID_PARAMS` | The value of `campaignId` or `afterCursorId` is not a positive integer, or `limit` is outside the 1–200 range |
| 401 | `MISSING_API_KEY` | The `X-Api-Key` header was not sent |
| 401 | `TOKEN_MISSING` | The key has no portal tokens. An OAuth application key requires the `Authorization: Bearer` header |
| 403 | `SCOPE_DENIED` | The key has no `performan` scope |
| 404 | `ROUTE_NOT_FOUND` | The Performance Review section is not enabled for the account. The answer is indistinguishable from the answer to any address that does not exist |
| 404 | `ENTITY_NOT_FOUND` | The section is enabled, but the account has no Performance Review module: Bitrix24 answers that the `performan.review.*` method was not found |
| 409 | `PERFORMAN_SCOPE_JUST_GRANTED` | A Cowork key was granted the `performan` scope by this very request. Repeat it and it goes through |
| 403 | `BITRIX_ACCESS_DENIED` | Bitrix24 denied access. The permissions the key uses to reach the portal do not include `performan` |
| 422 | `BITRIX_ERROR` | Bitrix24 rejected the request. The cause is in `error.message`, the machine code from Bitrix24 in `error.b24Code` |
| 429 | `RATE_LIMITED` | The request rate on the Bitrix24 side was exceeded |
| 429 | `QUEUE_OVERFLOW`, `QUEUE_TIMEOUT` | The portal request queue is full or the request did not get through the queue in time. The `Retry-After` header suggests the delay |
| 503 | `BITRIX_TIMEOUT` | Bitrix24 accepted the request but did not answer in time. A read is safe to retry |
| 502 | `BITRIX_UNAVAILABLE` | Bitrix24 is unavailable |

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

## Known specifics

**The listing is limited to the key owner's participation.** The method returns the campaigns the employee is a member of, not every campaign on the account. An empty `data` array means this employee has no campaigns. An account administrator gets the same slice.

**A `limit` outside the 1–200 range is refused.** The answer is `400 INVALID_PARAMS` and the request never reaches Bitrix24; ask for a page of 200 records with an explicit `limit=200`.

**The method returns no total campaign count.** To learn the number of records, walk every page by cursor.

## See also

- [Self-review cards](/docs/performan/self-reviews)
- [Manager review cards](/docs/performan/manager-reviews)
- [Performance review operations](/docs/performan/endpoints)
- [Errors](/docs/errors)
