## Search Drive

`POST /v1/disk/search`

Searches for files and folders across the whole Drive currently available to the Bitrix24 user and returns object cards without file contents. Results are read page by page, with a cursor.

## Request fields (body)

| Field | Type | Required | Default | Description |
|------|-----|:-----:|-----------|----------|
| `query` | string | yes | — | Search string. Leading and trailing whitespace is trimmed, inner whitespace is collapsed to a single space. Length is 3 to 255 characters both before and after this processing |
| `type` | string | yes | — | What to search for: `file`, `folder` or `all` |
| `filter.storageId` | string | no | — | Storage ID, a decimal string. Storage list: [`GET /v1/storages`](/docs/entities/storages/list) |
| `filter.folderId` | string | no | — | Folder ID, a decimal string. The storage root folder is the `rootFolderId` field in [`GET /v1/storages`](/docs/entities/storages/list), nested folders come from [`GET /v1/folders`](/docs/entities/folders/list) or the `objectId` of a card with `objectType: "folder"` from this search |
| `filter.fileType` | string | no | — | File type, for example `document`. Length 1 to 64 characters. Only with `type: "file"`; with any other `type` the request is rejected with `400 INVALID_PARAMS` |
| `limit` | number | no | `50` | Upper bound on the number of cards in the response, 1 to 1000 |
| `cursor` | string | no | — | The `nextCursor` value from the previous response. Not passed in the first request |

The body accepts no other fields: an unknown field, or a field in `filter` not listed above, returns `400 INVALID_PARAMS`.

## Pagination

1. Send the first request without `cursor`.
2. If the response has `hasMore: true`, repeat the request with the same `query`, `type` and `filter`, passing the whole `nextCursor` in the `cursor` field.
3. Continue until `hasMore` becomes `false`. A page can be shorter than `limit`, and even empty, with `hasMore: true` — this is not the end of the results.

A cursor is valid for one hour from the first request of the traversal; continuing does not extend it. The cursor is bound to the search string, `type`, `filter` and the key binding from [`GET /v1/disk/capabilities`](/docs/disk/capabilities). An expired cursor, a cursor from a different request and a cursor after `identity.accountGeneration` has grown return `403 DISK_GENERATION_STALE` — start the traversal again without `cursor`.

## Examples

### curl — personal key

```bash
curl -X POST https://vibecode.bitrix24.com/v1/disk/search \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "vibe-disk-docs-check",
    "type": "file",
    "limit": 1
  }'
```

### curl — OAuth application

```bash
curl -X POST https://vibecode.bitrix24.com/v1/disk/search \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "vibe-disk-docs-check",
    "type": "file",
    "limit": 1
  }'
```

### JavaScript — personal key

```javascript
// Traverse all result pages with the cursor
const items = []
let cursor

do {
  const res = await fetch('https://vibecode.bitrix24.com/v1/disk/search', {
    method: 'POST',
    headers: {
      'X-Api-Key': 'YOUR_API_KEY',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      query: 'vibe-disk-docs-check',
      type: 'file',
      limit: 1,
      ...(cursor ? { cursor } : {}),
    }),
  })
  const { success, data, error } = await res.json()
  if (!success) throw new Error(error.code)
  items.push(...data.items)
  cursor = data.hasMore ? data.nextCursor : undefined
} while (cursor)

console.log(items.map(item => item.name))
```

### JavaScript — OAuth application

```javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/disk/search', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_APP_KEY',
    'Authorization': 'Bearer USER_SESSION_TOKEN',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    query: 'vibe-disk-docs-check',
    type: 'file',
    limit: 1,
  }),
})

const { data } = await res.json()
console.log(data.items, data.hasMore)
```

## Response fields

| Field | Type | Description |
|------|-----|----------|
| `success` | boolean | `true` on a successful response |
| `data.items` | array | Cards of the objects found, no more than `limit` |
| `data.items[].objectId` | string | File or folder ID. For a file, pass it to [`GET /v1/files/:id`](/docs/entities/files/get); for a folder, pass it as `filter.folderId` in the next search |
| `data.items[].objectType` | string | `file` or `folder` |
| `data.items[].name` | string | Object name |
| `data.items[].storageId` | string | ID of the storage that holds the object |
| `data.items[].parentId` | string | Parent folder ID. May be absent |
| `data.items[].size` | string | Files only. Size in bytes, a decimal string |
| `data.items[].contentFileId` | string | Files only. ID of the current file content |
| `data.items[].contentGeneration` | string | Files only. Number of the current content version. Together with `contentFileId` and `size` it is required to upload a new version via [`POST /v1/disk/uploads/reserve`](/docs/disk/uploads-reserve) |
| `data.items[].safeLink` | string | Address of the object in the Bitrix24 web interface. `https` only, without parameters or credentials. Not a download link. Absent if the Bitrix24 account did not return such an address |
| `data.items[].source` | string | Card origin. In search it is always `native-current` — the current Bitrix24 account data at the time of the request |
| `data.hasMore` | boolean | Whether more results follow |
| `data.nextCursor` | string | Cursor of the next page. Only with `hasMore: true`, see "Pagination" |
| `data.truncated` | boolean | `true` when the results hit the 1000-result limit of the Bitrix24 account search: results beyond this boundary are unavailable, narrow `query` or `filter` |
| `data.limitations` | string[] | Notes on the results, in English. The first two lines always arrive. Additional lines report that some objects found could not be shown, or that the page ended early because of the time or response size limit — in that case continue the traversal with `nextCursor` |

## Response example

First page with `limit: 1` — two files found, the second one arrives via `nextCursor`:

```json
{
  "success": true,
  "data": {
    "items": [
      {
        "objectId": "10901",
        "objectType": "file",
        "name": "vibe-disk-docs-check-2.txt",
        "storageId": "1",
        "parentId": "1",
        "size": "29",
        "contentFileId": "39571",
        "contentGeneration": "1",
        "safeLink": "https://example.bitrix24.com/company/personal/user/1/disk/file/vibe-disk-docs-check-2.txt",
        "source": "native-current"
      }
    ],
    "hasMore": true,
    "nextCursor": "4OI2oJB6n4E0MLK5:ipx34E3vaJmBYDYNX5IKpKEMOJJEocUC2GenIl8JleEm7X1UnoGfWTtf2E1/gDE0aR3Nq+fesCeRRqN3520Gq0hkB2XxI+rSCbg+W3T6NbfMNHYlDWg010aWQOYz2VkxapPddVjEYUT3BUX4Hn5j68DX05BTFZv9gE5F4qUwxcEOTaeW8ReIfBa/dcCR0CzHMaEVxmilisorOtaRWGLDhdtjvYgRmIQpcFSOPuEfYn2JWYTZPRQmcn92Bi8PZx1Y70zRSYXxzw/mrsYgzAuzICojBvE=:LaEM6qPdvLmXkEoPFgtftA==",
    "truncated": false,
    "limitations": [
      "Native search pages contain at most 50 objects and offsets stop at 1000.",
      "The native index and access rights can change between requests; search is not a stable snapshot."
    ]
  }
}
```

On the last page `hasMore` is `false`, and there is no `nextCursor` field.

## Error response example

400 — the search string is shorter than three characters:

```json
{
  "success": false,
  "error": {
    "code": "INVALID_PARAMS",
    "message": "Disk request unavailable",
    "retrySafe": false
  }
}
```

## Errors

| HTTP | Code | Description |
|------|-----|----------|
| 400 | `INVALID_PARAMS` | The body violates the field rules: `query` or `type` not passed, `query` length outside 3–255, `filter.fileType` with a `type` other than `file`, `limit` outside 1–1000, an ID that is not a decimal string, an unknown field |
| 401 | `TOKEN_MISSING` | The key has no authorization configured on the Bitrix24 portal |
| 401 | `DISK_AUTH_UNAVAILABLE` | The Bitrix24 user could not be confirmed: the key's webhook does not match the portal or the user, an OAuth application key was passed without `Authorization: Bearer`, the session token belongs to a different application or portal, or the Bitrix24 token has expired or been revoked |
| 401 | `MISSING_API_KEY` | The `X-Api-Key` header was not passed |
| 401 | `INVALID_API_KEY` | The key was not recognized |
| 401 | `TOKEN_REFRESH_FAILED` | The portal authorization for the key could not be refreshed. Retrying will not help until the key is authorized again |
| 403 | `DISK_GENERATION_STALE` | The cursor has expired, was issued for a different request, or was issued before `identity.accountGeneration` grew. Start the traversal without `cursor` |
| 403 | `DISK_ACCESS_DENIED` | Bitrix24 denied the user access to search |
| 403 | `SCOPE_DENIED` | The key lacks the `disk` scope |
| 501 | `DISK_UNSUPPORTED` | The portal does not support Drive search — the `search.available` flag in [`GET /v1/disk/capabilities`](/docs/disk/capabilities) |
| 502 | `DISK_UNSUPPORTED` | The portal returned results in an unexpected shape, or the very first card exceeds the response size limit |
| 502 | `DISK_READ_UNAVAILABLE` | The search failed for a reason not covered by the other codes. Retry the request later |
| 503 | `BITRIX_TIMEOUT` | The 20 seconds allotted to the request ran out before the first call to the portal search, or the portal did not respond in time |
| 503 | `QUERY_LIMIT_EXCEEDED` | The portal throttled the request rate |
| 503 | `QUEUE_OVERFLOW`, `QUEUE_TIMEOUT` | The portal call queue is full. The `Retry-After` header gives the pause before retrying |
| 503 | `user_self_deletion_pending` | The key owner has requested deletion of their account — Drive calls are stopped |
| 503 | `RATE_LIMITED` | The portal responded with HTTP 429. The `Retry-After` header gives the pause before retrying |
| 503 | `ERROR_LOOP_DETECTED` | Repeated errors of this key on this method have temporarily stopped calls. The `Retry-After` header gives the pause |

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

## Known specifics

**Results are not a snapshot of Drive.** The Bitrix24 account search index and access rights change between requests, so during a cursor traversal an object can appear, disappear or shift between pages.

**A single request is limited in time and volume.** A request is allotted 20 seconds, including user verification. One request reads as many Bitrix24 account search pages of 50 results as it takes to collect `limit` cards, and the cards in the response take no more than 2 MiB. If time, pages or size run out before `limit` cards are collected, the response returns what was collected, `hasMore: true` and a line with the reason in `limitations`.

**A read-only key can search.** The request does not change data and is available to a key in [read-only mode](/docs/keys-auth/access-mode).

**Contents of a single folder.** To get the contents of a known folder with conditions on fields instead of searching the whole Drive, use [`POST /v1/files/search`](/docs/entities/files/search).

## See also

- [Get Drive capabilities](/docs/disk/capabilities)
- [Check Drive object visibility](/docs/disk/sync-check)
- [Reserve a file upload to Drive](/docs/disk/uploads-reserve)
- [Storage list](/docs/entities/storages/list)
- [Folder contents list](/docs/entities/folders/list)
- [Errors](/docs/errors)
