---
title: "Start an upload | GPT-Video API reference"
description: "Opens an upload for a video file and says how many parts to send and how big each one is."
url: "https://www.gpt-video.com/developers/api/create-upload"
operation: "create_upload"
method: "POST"
path: "/v1/uploads"
scope: "clips:write"
---

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

# Start an upload

Operation id `create_upload`. Request `POST /v1/uploads`.

Opens an upload for a video file and says how many parts to send and how big each one is.

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

Use it to send a video from your computer or server. Send the name of the file and its exact size; the answer says how many parts to send and how big each one is. Then send every part with upload_part (`PUT /v1/uploads/{uploadId}/parts/{partNumber}`), in any order and in parallel, and finish with complete_upload (`POST /v1/uploads/{uploadId}/complete`), which turns the file into an asset you can analyze. Only MP4, MOV, MKV, WebM, AVI and MPEG-TS video files are accepted, up to 8 GB, and the file counts toward the storage of the account. A video in another format, such as MPG, FLV, WMV or OGV, is refused before the upload opens: convert it to MP4 or MOV first. Send an Idempotency-Key: a retry with the same key returns the same upload instead of opening a second one.

## Example use

A backend that receives a two-hour webinar from its customer opens an upload with the exact file size. It then sends the parts in parallel with upload_part (`PUT /v1/uploads/{uploadId}/parts/{partNumber}`) and finishes with complete_upload (`POST /v1/uploads/{uploadId}/complete`).

## Endpoint

```text
POST https://api.gpt-video.com/v1/uploads
```

- 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).
- `Idempotency-Key` is optional.

## Parameters

### 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](/developers/pagination-and-idempotency.md). |

### Body

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `filename` | string | Required | The name of the file, such as interview.mp4. It is shown in the app, and its extension gives the type when contentType is missing. From 1 to 255 characters. |
| `sizeBytes` | integer | Required | The exact size of the file in bytes, up to 8 GB. From 1 to 8589934592. |
| `contentType` | string | Optional | The type of the file, such as video/mp4. Leave it out, or send application/octet-stream, to use the extension of filename. From 3 to 255 characters. |

Send only the fields listed here. Any other field is refused with UNKNOWN_PARAMETER.

## Request

Start the upload of a 20 MB video.

```bash title="curl"
# 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" \
  -H "Authorization: Bearer $GPT_VIDEO_API_KEY" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "filename": "keynote.mp4",
    "sizeBytes": 20971520,
    "contentType": "video/mp4"
  }'
```

```ts title="TypeScript"
// 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", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.GPT_VIDEO_API_KEY}`,
    "Idempotency-Key": idempotencyKey,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "filename": "keynote.mp4",
    "sizeBytes": 20971520,
    "contentType": "video/mp4"
  }),
});
const data = await res.json();
console.log(data);
```

```python title="Python"
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",
    headers={
        "Authorization": f"Bearer {os.environ['GPT_VIDEO_API_KEY']}",
        "Idempotency-Key": idempotency_key,
    },
    json={
        "filename": "keynote.mp4",
        "sizeBytes": 20971520,
        "contentType": "video/mp4"
    },
)
data = r.json()
print(data)
```

```json title="MCP (Full)"
{
  "method": "tools/call",
  "params": {
    "name": "create_upload",
    "arguments": {
      "filename": "keynote.mp4",
      "sizeBytes": 20971520,
      "contentType": "video/mp4"
    }
  }
}
```

## Response

A successful call answers HTTP 201. 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 201"
{
  "upload": {
    "id": "upl_9c4e1a7d2b5f4e6083b1",
    "filename": "keynote.mp4",
    "contentType": "video/mp4",
    "sizeBytes": 20971520,
    "partSizeBytes": 8388608,
    "partCount": 3,
    "receivedParts": [],
    "createdAt": "2026-09-01T10:30:00.000Z",
    "expiresAt": "2026-09-01T16:30:00.000Z"
  }
}
```

**All response fields**

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

**More examples**

#### Upload a file that is not a video

`POST /v1/uploads`

```json title="Request body"
{
  "filename": "slides.pdf",
  "sizeBytes": 524288,
  "contentType": "application/pdf"
}
```

```json title="HTTP 400"
{
  "error": {
    "code": "VALIDATION_FAILED",
    "message": "Only video files can be uploaded through the API for now.",
    "hint": "Fix the request as the message says, then send it again.",
    "requestId": "req_2b7e4a9c1d5f8e3a6b0c9d4e"
  }
}
```

#### Upload a video in a format GPT-Video does not read

`POST /v1/uploads`

```json title="Request body"
{
  "filename": "talk.mpg",
  "sizeBytes": 4000000000,
  "contentType": "video/mpeg"
}
```

```json title="HTTP 400"
{
  "error": {
    "code": "VALIDATION_FAILED",
    "message": "GPT-Video reads MP4, MOV, MKV, WebM, AVI and MPEG-TS files. Convert this video to MP4 or MOV, then upload it.",
    "hint": "Fix the request as the message says, then send it again.",
    "requestId": "req_d12173c7e361c8964781331b"
  }
}
```

#### Go past the storage of an account without a paid plan

`POST /v1/uploads`

```json title="Request body"
{
  "filename": "conference.mp4",
  "sizeBytes": 1610612736,
  "contentType": "video/mp4"
}
```

```json title="HTTP 409"
{
  "error": {
    "code": "CONFLICT",
    "message": "Your stored videos and uploads in progress would take 2.3 GB, over the 2 GB this account can store. Delete videos you no longer need in the GPT-Video app, or cancel uploads in progress, then try again. A paid plan stores up to 50 GB.",
    "requestId": "req_8d3f1b6e0a4c7d2e9f5a1b8c"
  }
}
```

#### Start an upload while ten are open

`POST /v1/uploads`

```json title="Request body"
{
  "filename": "panel.mp4",
  "sizeBytes": 104857600,
  "contentType": "video/mp4"
}
```

```json title="HTTP 409"
{
  "error": {
    "code": "CONFLICT",
    "message": "You already have 10 uploads in progress. Complete or cancel one of them first; an upload with no activity for 6 hours is removed.",
    "requestId": "req_5a9c2e7f1b4d8a3c6e0f2b7d"
  }
}
```

#### Start an upload when the service has no room for it

`POST /v1/uploads`

```json title="Request body"
{
  "filename": "festival.mp4",
  "sizeBytes": 6442450944,
  "contentType": "video/mp4"
}
```

```json title="HTTP 503"
{
  "error": {
    "code": "UNAVAILABLE",
    "message": "GPT-Video cannot take new work right now. Try again in a few minutes.",
    "hint": "Retry later.",
    "requestId": "req_0e6b3d9a2c5f7e1b4a8d3c6f"
  }
}
```

## Errors

| Code | Status | When | What to do |
| --- | --- | --- | --- |
| [`CONFLICT`](/developers/errors.md#CONFLICT) | 409 | Go past the storage of an account without a paid plan | 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), [`IDEMPOTENCY_IN_PROGRESS`](/developers/errors.md#IDEMPOTENCY_IN_PROGRESS), [`PAYLOAD_TOO_LARGE`](/developers/errors.md#PAYLOAD_TOO_LARGE), [`IDEMPOTENCY_KEY_REUSED`](/developers/errors.md#IDEMPOTENCY_KEY_REUSED), [`RATE_LIMITED`](/developers/errors.md#RATE_LIMITED), [`INTERNAL`](/developers/errors.md#INTERNAL), [`UNAVAILABLE`](/developers/errors.md#UNAVAILABLE).

## Cost

No credits are used. The file counts toward the storage of the account.

## Related operations

- [`upload_part`](/developers/api/upload-part.md) (`PUT /v1/uploads/{uploadId}/parts/{partNumber}`) Send a part of 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
- [`cancel_upload`](/developers/api/cancel-upload.md) (`DELETE /v1/uploads/{uploadId}`) Cancel an upload
- [`get_asset`](/developers/api/get-asset.md) (`GET /v1/assets/{assetId}`) Get an asset

## Questions

### How many uploads can be open at the same time?

There is a limit per account, and it counts the uploads started in the GPT-Video app too. When you reach it, the refusal says how many uploads are open. Complete or cancel one to open another; an upload with no activity is removed when its expiresAt passes.

### What happens if I lose the answer of this call?

Send the same request again with the same Idempotency-Key: you get the same upload back. Without a key, a second call opens a second upload.

### Which video formats can I send?

MP4, MOV, MKV, WebM, AVI and MPEG-TS files up to 8 GB. MP4 and MOV work best. complete_upload (`POST /v1/uploads/{uploadId}/complete`) checks the start of the file and refuses anything else, such as a playlist or a text file, and a file that cannot be read as a video.
