---
title: "Send a part of an upload | GPT-Video API reference"
description: "Sends one part of an upload as raw bytes, and returns the parts received so far."
url: "https://www.gpt-video.com/developers/api/upload-part"
operation: "upload_part"
method: "PUT"
path: "/v1/uploads/{uploadId}/parts/{partNumber}"
scope: "clips:write"
---

Do not assume endpoints have parameters other than the ones explicitly specified.

# Send a part of an upload

Operation id `upload_part`. Request `PUT /v1/uploads/{uploadId}/parts/{partNumber}`.

Sends one part of an upload as raw bytes, and returns the parts received so far.

Beta. This endpoint is supported and can gain new fields. If a change would break your code, we tell you before it takes effect and list it in the changelog.

## About

Send the bytes of the part as the body, with Content-Type: application/octet-stream and no JSON around them. Every part is exactly partSizeBytes long except the last one, which holds the rest of the file. Parts are numbered from 1 to partCount and can be sent in any order and in parallel; sending a part again replaces it. When receivedParts lists every part, call complete_upload (`POST /v1/uploads/{uploadId}/complete`). A part sent while the upload is being completed is refused, and so is a part that was still arriving when complete_upload started: send it again, then complete the upload again.

## Example use

A worker that uploads a large recording sends several parts at the same time. If one part fails, it sends that part again and leaves the others as they are.

## Endpoint

```text
PUT https://api.gpt-video.com/v1/uploads/{uploadId}/parts/{partNumber}
```

- Scope `clips:write`.
- Rate limit 60 requests a minute for each key and 120 for each account, shared by every `write` call. See [rate limits](/developers/rate-limits.md).
- No `Idempotency-Key`.

## Parameters

### Path

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `uploadId` | string | Required | The id of the upload, as create_upload (`POST /v1/uploads`) returned it. Matches `^upl_[0-9a-f]{20}$`. |
| `partNumber` | integer | Required | The number of the part, from 1 to the partCount of the upload. From 1 to 1024. |

### Body

The raw bytes of the part, sent with `Content-Type: application/octet-stream`. At most 8388608 bytes.

## Request

Send the first part.

```bash title="curl"
curl -X PUT "https://api.gpt-video.com/v1/uploads/upl_5d2a8f1e6c3b4079e0a1/parts/1" \
  -H "Authorization: Bearer $GPT_VIDEO_API_KEY" \
  -H "Content-Type: application/octet-stream" \
  --data-binary @part-1.bin
```

```ts title="TypeScript"
import { readFile } from "node:fs/promises";

const res = await fetch("https://api.gpt-video.com/v1/uploads/upl_5d2a8f1e6c3b4079e0a1/parts/1", {
  method: "PUT",
  headers: {
    "Authorization": `Bearer ${process.env.GPT_VIDEO_API_KEY}`,
    "Content-Type": "application/octet-stream",
  },
  body: await readFile("part-1.bin"),
});
const data = await res.json();
console.log(data);
```

```python title="Python"
import os

import requests

r = requests.put(
    "https://api.gpt-video.com/v1/uploads/upl_5d2a8f1e6c3b4079e0a1/parts/1",
    headers={
        "Authorization": f"Bearer {os.environ['GPT_VIDEO_API_KEY']}",
        "Content-Type": "application/octet-stream",
    },
    data=open("part-1.bin", "rb").read(),
)
data = r.json()
print(data)
```

## Response

A successful call answers HTTP 200. Returns a [Upload object](/developers/objects.md#upload) in `upload`.

### Fields to check

| Name | Type | Description |
| --- | --- | --- |
| `upload.id` | string | The upload id. It starts with upl_. |

### Response example

```json title="HTTP 200"
{
  "upload": {
    "id": "upl_5d2a8f1e6c3b4079e0a1",
    "filename": "keynote.mp4",
    "contentType": "video/mp4",
    "sizeBytes": 20971520,
    "partSizeBytes": 8388608,
    "partCount": 3,
    "receivedParts": [
      1
    ],
    "createdAt": "2026-09-01T10:30:00.000Z",
    "expiresAt": "2026-09-01T16:31:00.000Z"
  }
}
```

**All response fields**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `upload` | Upload object | Required | Described in [Upload object](/developers/objects.md#upload). |

**More examples**

#### Send a part the upload does not have

`PUT /v1/uploads/upl_5d2a8f1e6c3b4079e0a1/parts/4`

A body of 1024 bytes.

```json title="HTTP 400"
{
  "error": {
    "code": "VALIDATION_FAILED",
    "message": "Part 4 does not exist: this upload has 3 parts, numbered 1 to 3.",
    "hint": "Fix the request as the message says, then send it again.",
    "requestId": "req_7c1e5a3b9d0f2e8c4a6b1d5f"
  }
}
```

#### Send the last part with the wrong size

`PUT /v1/uploads/upl_5d2a8f1e6c3b4079e0a1/parts/3`

A body of 8388608 bytes.

```json title="HTTP 400"
{
  "error": {
    "code": "VALIDATION_FAILED",
    "message": "Part 3 must be 4194304 bytes, and the request declares 8388608.",
    "hint": "Fix the request as the message says, then send it again.",
    "requestId": "req_3f8a0c6e2b9d5a1f7c4e0b3a"
  }
}
```

#### Send a part of an upload that does not exist

`PUT /v1/uploads/upl_3b6f9e2d7c0a4e51a8d9/parts/1`

A body of 1024 bytes.

```json title="HTTP 404"
{
  "error": {
    "code": "NOT_FOUND",
    "message": "Upload upl_3b6f9e2d7c0a4e51a8d9 not found",
    "hint": "Check the id. An id that belongs to another account is not found either.",
    "requestId": "req_9b2d6f0a4e8c1b5d3f7a2e6c"
  }
}
```

## Errors

| Code | Status | When | What to do |
| --- | --- | --- | --- |
| [`NOT_FOUND`](/developers/errors.md#NOT_FOUND) | 404 | Send a part of an upload that does not exist | Change the request, then send it again. |
| [`CONFLICT`](/developers/errors.md#CONFLICT) | 409 | A part arrives while complete_upload (`POST /v1/uploads/{uploadId}/complete`) is running, or it was still arriving when complete_upload started. Send it again if the upload is still open after that. | Read the resource again, then decide. Do not send the same request again. |

Every call can also answer these errors. [`VALIDATION_FAILED`](/developers/errors.md#VALIDATION_FAILED), [`UNKNOWN_PARAMETER`](/developers/errors.md#UNKNOWN_PARAMETER), [`UNAUTHORIZED`](/developers/errors.md#UNAUTHORIZED), [`SCOPE_MISSING`](/developers/errors.md#SCOPE_MISSING), [`PAYLOAD_TOO_LARGE`](/developers/errors.md#PAYLOAD_TOO_LARGE), [`RATE_LIMITED`](/developers/errors.md#RATE_LIMITED), [`INTERNAL`](/developers/errors.md#INTERNAL), [`UNAVAILABLE`](/developers/errors.md#UNAVAILABLE).

## Cost

No credits are used.

## Related operations

- [`create_upload`](/developers/api/create-upload.md) (`POST /v1/uploads`) Start an upload
- [`get_upload`](/developers/api/get-upload.md) (`GET /v1/uploads/{uploadId}`) Get an upload
- [`complete_upload`](/developers/api/complete-upload.md) (`POST /v1/uploads/{uploadId}/complete`) Complete an upload

## Questions

### Can I send the parts in parallel?

Yes. Parts can arrive in any order and at the same time; each one is written in its own place in the file.

### What do I do when a part fails?

Send the same part again. A part that did not arrive whole is not in receivedParts, and sending it again replaces whatever arrived before.

### How do I know the size of the last part?

It is sizeBytes minus partSizeBytes times (partCount minus 1): the bytes left after the full parts.
