Send a part of an upload
upload_partPUT/v1/uploads/{uploadId}/parts/{partNumber}Beta
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
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
writecall. See rate limits. - 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.
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.binimport { 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);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 in upload.
Fields to check
| Name | Type | Description |
|---|---|---|
upload.id | string | The upload id. It starts with upl_. |
Response example
{
"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. |
More examples
Send a part the upload does not have
PUT /v1/uploads/upl_5d2a8f1e6c3b4079e0a1/parts/4
A body of 1024 bytes.
{
"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.
{
"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.
{
"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 | 404 | Send a part of an upload that does not exist | Change the request, then send it again. |
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, UNKNOWN_PARAMETER, UNAUTHORIZED, SCOPE_MISSING, PAYLOAD_TOO_LARGE, RATE_LIMITED, INTERNAL, UNAVAILABLE.
Cost
No credits are used.
Related operations
create_upload(POST /v1/uploads) Start an uploadget_upload(GET /v1/uploads/{uploadId}) Get an uploadcomplete_upload(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.