Complete an upload
complete_uploadPOST/v1/uploads/{uploadId}/completeBeta
Turns an upload whose parts all arrived into an asset that a project can use as its source.
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
Call it once receivedParts lists every part. The file is checked and becomes an asset; pass its id to create_project (POST /v1/projects) to analyze it. If parts are missing, nothing changes and the answer names them. The first bytes of the file are checked before anything else: only MP4, MOV, MKV, WebM, AVI and MPEG-TS files go on. A playlist (M3U8), a DASH manifest, a web page or any other text file is refused, and the upload stays open: cancel it with cancel_upload (DELETE /v1/uploads/{uploadId}). If the file passes that check but cannot be read as a video, the upload also stays open with all its parts: call complete_upload (POST /v1/uploads/{uploadId}/complete) again, or cancel it. After a success the upload no longer exists, so a retry without an Idempotency-Key answers not found; send a key, and a retry with the same key returns the same asset.
Example use
Once receivedParts lists every part, a script completes the upload with a title for the video. It passes the id of the new asset to create_project (POST /v1/projects) to start an Auto Clip project on it.
Endpoint
POST https://api.gpt-video.com/v1/uploads/{uploadId}/complete- Scope
clips:write. - Rate limit 60 requests a minute for each key and 120 for each account, shared by every
writecall. See rate limits. Idempotency-Keyis optional.
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}$. |
Headers
| Name | Type | Required | Description |
|---|---|---|---|
Idempotency-Key | string | Optional | Send the same key on every retry of this request. From 1 to 255 visible characters, such as a UUID. See idempotency. |
Body
The body is optional. Send only the fields you want to change.
| Name | Type | Required | Description |
|---|---|---|---|
title | string | Optional | The title of the video in the app. Leave it out to use the name of the file without its extension. From 1 to 200 characters. |
Send only the fields listed here. Any other field is refused with UNKNOWN_PARAMETER.
Request
Complete an upload.
# Create the key once. Send this same key again if you retry this request.
IDEMPOTENCY_KEY=$(uuidgen)
curl -X POST "https://api.gpt-video.com/v1/uploads/upl_e7b04c92a15d4f36b8c7/complete" \
-H "Authorization: Bearer $GPT_VIDEO_API_KEY" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Product teaser"
}'// Create the key once. Send this same key again if you retry this request.
const idempotencyKey = crypto.randomUUID();
const res = await fetch("https://api.gpt-video.com/v1/uploads/upl_e7b04c92a15d4f36b8c7/complete", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.GPT_VIDEO_API_KEY}`,
"Idempotency-Key": idempotencyKey,
"Content-Type": "application/json",
},
body: JSON.stringify({
"title": "Product teaser"
}),
});
const data = await res.json();
console.log(data);import os
import uuid
import requests
# Create the key once. Send this same key again if you retry this request.
idempotency_key = str(uuid.uuid4())
r = requests.post(
"https://api.gpt-video.com/v1/uploads/upl_e7b04c92a15d4f36b8c7/complete",
headers={
"Authorization": f"Bearer {os.environ['GPT_VIDEO_API_KEY']}",
"Idempotency-Key": idempotency_key,
},
json={
"title": "Product teaser"
},
)
data = r.json()
print(data){
"method": "tools/call",
"params": {
"name": "complete_upload",
"arguments": {
"uploadId": "upl_e7b04c92a15d4f36b8c7",
"title": "Product teaser"
}
}
}Response
A successful call answers HTTP 201.
Fields to check
| Name | Type | Description |
|---|---|---|
asset.id | string | The asset id. It starts with ast_. |
Response example
{
"asset": {
"id": "ast_5b0e7c3a9d1f4286b7e4",
"kind": "video",
"title": "Product teaser",
"filename": "teaser.mp4",
"origin": "upload",
"mimeType": "video/mp4",
"sizeBytes": 4096,
"durationSec": 42.5,
"width": 1920,
"height": 1080,
"fps": 30,
"hasAudio": true,
"createdAt": "2026-09-01T10:42:00.000Z",
"deletesAt": null
}
}All response fields
| Name | Type | Required | Description |
|---|---|---|---|
asset | object | Required | The new asset. Its id is what create_project (POST /v1/projects) takes as sourceAssetId. |
asset.id | string | Required | The asset id. It starts with ast_. |
asset.kind | string | Required | One of video, audio, image, font, other. |
asset.title | string | Required | |
asset.filename | string or null | Required | The name of the file as it was uploaded, when there was one. |
asset.origin | string | Required | upload was sent by you, link was imported from a link, converted is a playable copy made for the analysis, export is the file of an export, voice is the audio of a voiceover, separated is the audio an audio separation kept. One of upload, link, converted, export, voice, separated. |
asset.mimeType | string | Required | |
asset.sizeBytes | integer | Required | |
asset.durationSec | number or null | Required | |
asset.width | integer or null | Required | The width as the video is shown, rotation applied. |
asset.height | integer or null | Required | The height as the video is shown, rotation applied. |
asset.fps | number or null | Required | |
asset.hasAudio | boolean or null | Required | |
asset.createdAt | string | Required | An ISO 8601 time in UTC. |
asset.deletesAt | string or null | Required | The first time the file may be deleted to free space: files are kept for a limited time. null when nothing deletes it. |
More examples
Complete an upload with parts missing
POST /v1/uploads/upl_5d2a8f1e6c3b4079e0a1/complete
{
"error": {
"code": "CONFLICT",
"message": "Parts 1, 2 and 3 are missing. Send them, then complete the upload again.",
"requestId": "req_4d9f3b7e1a5c0d8b2f6e9a3c"
}
}Complete an upload that is not a video
POST /v1/uploads/upl_e7b04c92a15d4f36b8c7/complete
{
"error": {
"code": "VALIDATION_FAILED",
"message": "We could not read this file as a video. If it is a video, call complete_upload again; otherwise cancel this upload with cancel_upload and upload the file again as MP4 or MOV.",
"hint": "Fix the request as the message says, then send it again.",
"requestId": "req_1a7c5e9b3d0f6a2c8e4b7d1f"
}
}Complete an upload whose file is a playlist
POST /v1/uploads/upl_e7b04c92a15d4f36b8c7/complete
{
"error": {
"code": "VALIDATION_FAILED",
"message": "This file is not a video file GPT-Video can read. Cancel this upload with cancel_upload and upload the file again as MP4 or MOV.",
"hint": "Fix the request as the message says, then send it again.",
"requestId": "req_6b2e8d4a0c7f3e9b1d5a8c2f"
}
}Complete an upload that does not exist
POST /v1/uploads/upl_3b6f9e2d7c0a4e51a8d9/complete
{
"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_8f2c6a0e4b9d3f7a1c5e8b2d"
}
}Errors
| Code | Status | When | What to do |
|---|---|---|---|
NOT_FOUND | 404 | Complete an upload that does not exist | Change the request, then send it again. |
CONFLICT | 409 | Complete an upload with parts missing | 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, IDEMPOTENCY_IN_PROGRESS, PAYLOAD_TOO_LARGE, IDEMPOTENCY_KEY_REUSED, RATE_LIMITED, INTERNAL, UNAVAILABLE.
Cost
No credits are used. Analyzing the video later is what uses credits.
Related operations
create_upload(POST /v1/uploads) Start an uploadupload_part(PUT /v1/uploads/{uploadId}/parts/{partNumber}) Send a part of an uploadget_upload(GET /v1/uploads/{uploadId}) Get an uploadcancel_upload(DELETE /v1/uploads/{uploadId}) Cancel an uploadget_asset(GET /v1/assets/{assetId}) Get an assetcreate_project(POST /v1/projects) Create a project
Questions
Does completing an upload start the analysis?
No. It only creates the asset. Create a project with the asset as its source, then analyze the project.
What if the file is not a readable video?
The answer is a validation error and the upload stays open. If the start of the file is not a video format GPT-Video reads (MP4, MOV, MKV, WebM, AVI or MPEG-TS), calling again gives the same answer: cancel the upload and send the file again as MP4 or MOV. If the format is right but the file could not be read, call complete_upload (POST /v1/uploads/{uploadId}/complete) again; if it still fails, cancel the upload and send the file again.