## Upload file content to Drive

`POST /v1/disk/uploads`

Writes a file to Bitrix24 Drive under a previously reserved operation — creates a new file or a new version of an existing one, as set at reserve time.

The body is flat, with no `fields` wrapper.

## Request fields (body)

| Field | Type | Required | Description |
|------|-----|:-----:|----------|
| `operationId` | string | yes | Operation ID from the [`POST /v1/disk/uploads/reserve`](/docs/disk/uploads-reserve) response. A lost ID can be found in [`GET /v1/disk/uploads`](/docs/disk/uploads-list) |
| `intentHash` | string | yes | The `intentHash` field from the reserve response — 64 lowercase characters |
| `effectiveFilename` | string | yes | File name matching the reserve's `intent[6]` character for character |
| `content` | string | yes | File bytes in base64 per RFC 4648: alphabet `A-Z`, `a-z`, `0-9`, `+`, `/`, padding `=`, with no line breaks and no `data:` prefix. At most `26214400` bytes after decoding (25 MiB) |
| `contentHash` | string | yes | SHA-256 of the file bytes before base64 encoding — 64 lowercase characters. Matches the reserve's `intent[3]` |

The body accepts no other fields: an unknown field returns `400 INVALID_PARAMS`. The total request body size is at most 35 MiB.

## Examples

### curl — personal key

```bash
curl -X POST https://vibecode.bitrix24.com/v1/disk/uploads \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "operationId": "xykPPgOlNBiKMTlAV3SfBoGJhG6Hi6AX57Ede4rAFoU",
    "intentHash": "3e10ae1152a0e4b4e89d3b312bfacafc1f3e0cf22d8a5eae3e231fa8bc274498",
    "effectiveFilename": "vibe-disk-docs-check-2.txt",
    "content": "VmliZSBEaXNrIGFnZW50IGRvY3MgY2hlY2sgMgo=",
    "contentHash": "6be5cdb4cdbf136301c74adf2fd5a380fe5ceda1e998ce4b62f01cf7d20589a5"
  }'
```

### curl — OAuth application

```bash
curl -X POST https://vibecode.bitrix24.com/v1/disk/uploads \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "operationId": "xykPPgOlNBiKMTlAV3SfBoGJhG6Hi6AX57Ede4rAFoU",
    "intentHash": "3e10ae1152a0e4b4e89d3b312bfacafc1f3e0cf22d8a5eae3e231fa8bc274498",
    "effectiveFilename": "vibe-disk-docs-check-2.txt",
    "content": "VmliZSBEaXNrIGFnZW50IGRvY3MgY2hlY2sgMgo=",
    "contentHash": "6be5cdb4cdbf136301c74adf2fd5a380fe5ceda1e998ce4b62f01cf7d20589a5"
  }'
```

### JavaScript — personal key

```javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/disk/uploads', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_API_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    operationId: 'xykPPgOlNBiKMTlAV3SfBoGJhG6Hi6AX57Ede4rAFoU',
    intentHash: '3e10ae1152a0e4b4e89d3b312bfacafc1f3e0cf22d8a5eae3e231fa8bc274498',
    effectiveFilename: 'vibe-disk-docs-check-2.txt',
    content: 'VmliZSBEaXNrIGFnZW50IGRvY3MgY2hlY2sgMgo=',
    contentHash: '6be5cdb4cdbf136301c74adf2fd5a380fe5ceda1e998ce4b62f01cf7d20589a5',
  }),
})

const { success, data, error } = await res.json()
if (!success) throw new Error(error.code)
if (data.application === 'unknown') {
  // Do not repeat the write: check the folder or file on Drive first
  console.warn('Write result unknown:', data.operationId)
} else if (data.application === 'applied') {
  console.log('File written:', data.fileId, data.safeLink)
}
```

### JavaScript — OAuth application

```javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/disk/uploads', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_APP_KEY',
    'Authorization': 'Bearer USER_SESSION_TOKEN',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    operationId: 'xykPPgOlNBiKMTlAV3SfBoGJhG6Hi6AX57Ede4rAFoU',
    intentHash: '3e10ae1152a0e4b4e89d3b312bfacafc1f3e0cf22d8a5eae3e231fa8bc274498',
    effectiveFilename: 'vibe-disk-docs-check-2.txt',
    content: 'VmliZSBEaXNrIGFnZW50IGRvY3MgY2hlY2sgMgo=',
    contentHash: '6be5cdb4cdbf136301c74adf2fd5a380fe5ceda1e998ce4b62f01cf7d20589a5',
  }),
})

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

## Response fields

| Field | Type | Description |
|------|-----|----------|
| `success` | boolean | `true` if the request was processed. Whether the file was written is shown by `data.application` |
| `data.operationId` | string | Operation ID |
| `data.phase` | string | Operation stage: `prepared` — reserved, `submitting` — the write has been sent to Bitrix24, `settled` — finished |
| `data.application` | string | Write result: `applied` — Bitrix24 confirmed the write, `not_applied` — the write was not sent to Bitrix24, `unknown` — the write was sent but not confirmed |
| `data.verification` | string | Check against the current state of the file: `matched` — the file matches what was written, `superseded` — the file has changed since, `unavailable` — the check was not performed |
| `data.fileId` | string | File ID on Drive. Returned with `application: "applied"`. Used as `intent[2]` in `version` mode |
| `data.contentFileId` | string | ID of the written content. Returned with `application: "applied"`. The first element of the expected state `intent[5]` for the next version |
| `data.contentGeneration` | string | File content version number. Returned with `application: "applied"`. The second element of `intent[5]` for the next version |
| `data.safeLink` | string | HTTPS address of the file in Bitrix24. Returned when Bitrix24 provided it. Access is limited by the employee's permissions in Bitrix24 |
| `data.atomicConcurrency` | boolean | Always `false`: the write is not protected against another user changing the file at the same time |
| `data.conflictDetected` | boolean | `true` if, in `version` mode, the current state of the file did not match `intent[5]` and no write was performed |
| `data.outcomeUnknown` | boolean | `true` with `application: "unknown"` |

## Response example

File written:

```json
{
  "success": true,
  "data": {
    "operationId": "xykPPgOlNBiKMTlAV3SfBoGJhG6Hi6AX57Ede4rAFoU",
    "phase": "settled",
    "application": "applied",
    "verification": "matched",
    "fileId": "10901",
    "contentFileId": "39571",
    "contentGeneration": "1",
    "safeLink": "https://example.bitrix24.com/company/personal/user/1/disk/file/vibe-disk-docs-check-2.txt",
    "atomicConcurrency": false,
    "conflictDetected": false,
    "outcomeUnknown": false
  }
}
```

The write was sent but not confirmed — the folder already had a file with that name:

```json
{
  "success": true,
  "data": {
    "operationId": "zQl-VU0ZeSeCYnH14kibBVeiYRM7iihEu6-QDSTPJFQ",
    "phase": "settled",
    "application": "unknown",
    "verification": "unavailable",
    "atomicConcurrency": false,
    "conflictDetected": false,
    "outcomeUnknown": true
  }
}
```

## Error response example

409 — `contentHash` does not match the SHA-256 of the bytes sent:

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

## Errors

| HTTP | Code | Description |
|------|-----|----------|
| 400 | `INVALID_PARAMS` | The body failed validation: a required field is missing, `intentHash` or `contentHash` is not 64 hexadecimal characters, an invalid file name, an unknown field |
| 401 | `MISSING_API_KEY` | The `X-Api-Key` header is missing |
| 401 | `INVALID_API_KEY` | The key is not recognized |
| 401 | `TOKEN_MISSING` | The key has no connection to Bitrix24 |
| 401 | `DISK_AUTH_UNAVAILABLE` | The Bitrix24 user could not be confirmed: an application key was sent without `Authorization: Bearer`, the session does not belong to the key's application, or the key is not supported by the Drive agent |
| 401 | `DISK_AUTH_UNAVAILABLE` | `version` mode: the current state of the file could not be read. No write was performed, the operation finished with `application: "not_applied"` |
| 401 | `TOKEN_REFRESH_FAILED` | The portal authorization for the key could not be refreshed. A retry does not help until the key is authorized again |
| 403 | `SCOPE_DENIED` | The key has no `disk` scope |
| 403 | `WRITE_BLOCKED_READONLY` | The key is in read-only mode |
| 403 | `DISK_ACCESS_DENIED` | Bitrix24 denied access while checking the user |
| 409 | `DISK_OPERATION_CONFLICT` | `content` is not base64 per RFC 4648, `contentHash` does not match the SHA-256 of the bytes, or `intentHash`, `effectiveFilename`, the hash or the byte size does not match the reserve |
| 409 | `DISK_OPERATION_EXPIRED` | The `operationId` operation was not found in the key's context or its `reservationExpiresAt` deadline has passed. Create a new reserve |
| 409 | `DISK_VERSION_CONFLICT` | `version` mode: the current state of the file did not match `intent[5]`. No write was performed, the operation finished with `application: "not_applied"` and `conflictDetected: true` |
| 413 | `DISK_UPLOAD_TOO_LARGE` | The content exceeds `26214400` bytes after decoding |
| 413 | `PAYLOAD_TOO_LARGE` | The request body exceeds 35 MiB |
| 429 | `LARGE_BODY_BACKEND_BUSY` | The server is processing many large requests at once. The response carries a `Retry-After` header with the pause in seconds |
| 502 | `DISK_READ_UNAVAILABLE` | Bitrix24 returned an error that Vibecode did not recognize |
| 503 | `QUERY_LIMIT_EXCEEDED` | Bitrix24 limited the request rate. Retry later |
| 503 | `BITRIX_TIMEOUT` | Bitrix24 did not respond in time |
| 503 | `QUEUE_OVERFLOW`, `QUEUE_TIMEOUT` | The portal call queue is full. The `Retry-After` header gives the pause before a retry |
| 503 | `RATE_LIMITED` | The portal responded with HTTP 429. The `Retry-After` header gives the pause before a retry |
| 503 | `ERROR_LOOP_DETECTED` | Repeated errors of this key on this method have temporarily stopped calls. The `Retry-After` header gives the pause |
| 503 | `user_self_deletion_pending` | The key owner requested deletion of their account — Drive calls are stopped |

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

## Known specifics

- **The write is sent to Bitrix24 once.** Vibecode does not retry it on its own, either on a timeout or on a connection failure. If Bitrix24 did not confirm a sent write — refused, did not respond or responded incompletely — the operation finishes with `application: "unknown"`, and a repeat call with the same `operationId` returns that state without a new write.
- **With `application: "unknown"`, check Drive before writing again.** A new reserve with `lineage.kind: "retry"` from such an operation is rejected with `409 DISK_OUTCOME_UNKNOWN`. Find the file via [`POST /v1/disk/search`](/docs/disk/search) or [`POST /v1/disk/sync/check`](/docs/disk/sync-check) and decide whether a new write with `lineage.kind: "initial"` and a different `intent` is needed.
- **`create` mode does not overwrite a file.** If the folder already has a file with the same name, the existing file stays unchanged and the operation finishes with `application: "unknown"`. To replace content, use `version` mode.
- **Repeating a finished operation recalculates `verification`.** For `application: "applied"`, the field is checked against the current file again.
- **The `intent[5]` check in `version` mode runs before the write and does not lock the file.** A change to the file between the check and the write is not detected. After `409 DISK_VERSION_CONFLICT`, take the current `contentFileId`, `contentGeneration` and `size` and reserve a new operation.

## See also

- [Reserve a file upload to Drive](/docs/disk/uploads-reserve)
- [Get an upload operation](/docs/disk/uploads-get)
- [List upload operations](/docs/disk/uploads-list)
- [Search Drive](/docs/disk/search)
- [Check Drive object visibility](/docs/disk/sync-check)
- [Errors](/docs/errors)
