---
title: "Export a composition | GPT-Video API reference"
description: "Starts rendering a composition to an MP4 file and returns the export to follow."
url: "https://www.gpt-video.com/developers/api/create-export"
operation: "create_export"
method: "POST"
path: "/v1/compositions/{compositionId}/exports"
scope: "spend"
---

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

# Export a composition

Operation id `create_export`. Request `POST /v1/compositions/{compositionId}/exports`.

Starts rendering a composition to an MP4 file and returns the export to follow.

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 this when a composition is ready to become a video file. It answers 202 at once with the export, queued, and a Location to follow; read it with get_export (`GET /v1/exports/{exportId}`) (with ?wait= to hold the request) until it is done, then download the file from its downloadUrl. The export renders the composition as it is when its render starts: a change saved while it waits in the queue is in the file, and a change saved after that is not. Once it is done, its revision says which revision the file holds. Every composition can be exported: of an Auto Clip clip, of a split screen, streamer, Reddit story or fake text project, or of a Vibe Editing edit. A composition that uses a file the API cannot use (one the GPT-Video app put in it) is refused before anything is used. An account that is not on the Creator or Studio plan exports through the API bonus, which pays for exports up to a fixed length (see the FAQ); a longer one answers PLAN_REQUIRED. paidWith says who paid this request. While the face framing of the clip is still being computed the call answers NOT_READY: wait the seconds in Retry-After and send it again. Asking again while the same revision is exporting returns that export and uses nothing. Always send an Idempotency-Key, the same one on every retry: a retry of the same request never uses a second export, and if the first try stopped before the render started, the retry starts it, with the composition as it is at the retry. This answer never carries a download link; get_export does.

## Example use

A team that posts to YouTube Shorts accepts the clips it likes, calls create_export (`POST /v1/compositions/{compositionId}/exports`) for each composition with its own Idempotency-Key, and follows every export with get_export (`GET /v1/exports/{exportId}`) until its file is ready.

## Endpoint

```text
POST https://api.gpt-video.com/v1/compositions/{compositionId}/exports
```

- Scope `spend`.
- Rate limit 10 requests a minute for each key and 20 for each account, shared by every `spend` call. See [rate limits](/developers/rate-limits.md).
- `Idempotency-Key` is required.
- It starts work that runs on its own. The answer comes back before the work is done.

## Parameters

### Path

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `compositionId` | string | Required | The composition to export, as accept_clip (`POST /v1/clips/{clipId}/accept`) or get_composition (`GET /v1/compositions/{compositionId}`) returned it. It starts with cmp_. Matches `^cmp_[0-9a-f]{20}$`. |

### Headers

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `Idempotency-Key` | string | Required | This operation spends, so the key is required. Create it once, before the request, and send the same key on every retry. From 1 to 255 visible characters, such as a UUID. See [idempotency](/developers/pagination-and-idempotency.md). |

## Request

Export a composition.

```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/compositions/cmp_4e8c2a6f09d14b73a1e5/exports" \
  -H "Authorization: Bearer $GPT_VIDEO_API_KEY" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY"
```

```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/compositions/cmp_4e8c2a6f09d14b73a1e5/exports", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.GPT_VIDEO_API_KEY}`,
    "Idempotency-Key": idempotencyKey,
  },
});
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/compositions/cmp_4e8c2a6f09d14b73a1e5/exports",
    headers={
        "Authorization": f"Bearer {os.environ['GPT_VIDEO_API_KEY']}",
        "Idempotency-Key": idempotency_key,
    },
)
data = r.json()
print(data)
```

```json title="MCP (Full)"
{
  "method": "tools/call",
  "params": {
    "name": "create_export",
    "arguments": {
      "compositionId": "cmp_4e8c2a6f09d14b73a1e5",
      "confirm": true,
      "idempotencyKey": "REPLACE-WITH-A-NEW-UUID-FOR-THIS-CALL"
    }
  }
}
```

## Response

A successful call answers HTTP 202. Returns a [Export object](/developers/objects.md#export) in `export`.

### Fields to check

| Name | Type | Description |
| --- | --- | --- |
| `export.id` | string | The export id. It starts with rnd_. |
| `export.jobId` | string or null | The job that renders this export, to read with get_job (`GET /v1/jobs/{jobId}`). It starts with job_; null while the export has no job yet. |
| `export.status` | string | queued and rendering are still working; done, failed and cancelled are final. |
| `export.downloadUrl` | string or null | A link that downloads the MP4 with no API key, for 15 minutes and for this file only. Only get_export (`GET /v1/exports/{exportId}`) and list_exports (`GET /v1/projects/{projectId}/exports`) give one, once the export is done and while the file is kept; every read gives a new link. create_export (`POST /v1/compositions/{compositionId}/exports`) always answers null. |
| `export.downloadUrlExpiresAt` | string or null | When downloadUrl stops working; null when there is no link. |
| `paidWith` | string or null | Who paid for this request: plan is the credits or the daily allowance of the account's plan, api_bonus is the API bonus. null when this request paid nothing, because the same work was already running. A retry with the same Idempotency-Key says who paid the first time. |

### Response example

- `Location: https://api.gpt-video.com/v1/exports/rnd_6d0f2a8c4e1b47a9c3f5`

```json title="HTTP 202"
{
  "export": {
    "id": "rnd_6d0f2a8c4e1b47a9c3f5",
    "compositionId": "cmp_4e8c2a6f09d14b73a1e5",
    "projectId": "prj_7c2e9a4f1b3d4e8096a5",
    "jobId": "job_8c3f1e5a9d2b4f7c06e1",
    "revision": 0,
    "status": "queued",
    "progress": 0,
    "errorMessage": null,
    "createdAt": "2026-09-01T11:00:00.000Z",
    "finishedAt": null,
    "file": null,
    "downloadUrl": null,
    "downloadUrlExpiresAt": null,
    "fileDeletesAt": null,
    "fileDeletedAt": null
  },
  "paidWith": "plan"
}
```

**All response fields**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `export` | Export object | Required | Described in [Export object](/developers/objects.md#export). |
| `paidWith` | string or null | Required | Who paid for this request: plan is the credits or the daily allowance of the account's plan, api_bonus is the API bonus. null when this request paid nothing, because the same work was already running. A retry with the same Idempotency-Key says who paid the first time. One of `plan`, `api_bonus`. |

**More examples**

#### The framing of the clip is still being computed

`POST /v1/compositions/cmp_4e8c2a6f09d14b73a1e5/exports`

- `Retry-After: 10`

```json title="HTTP 409"
{
  "error": {
    "code": "NOT_READY",
    "message": "The framing of this clip is still being computed. The export can start once it is done.",
    "hint": "Wait the number of seconds in the Retry-After header, then send the same request again.",
    "requestId": "req_5c2e8a1f0b7d4e6a9c3f8b21"
  }
}
```

#### Export a split screen video

`POST /v1/compositions/cmp_cc26cb47fd6004aa5fba/exports`

- `Location: https://api.gpt-video.com/v1/exports/rnd_2f9b4e7c1a3d58e06b2c`

```json title="HTTP 202"
{
  "export": {
    "id": "rnd_2f9b4e7c1a3d58e06b2c",
    "compositionId": "cmp_cc26cb47fd6004aa5fba",
    "projectId": "prj_9cf6ba9cd17fd89cf513",
    "jobId": "job_5d1a8e3f0c6b49a27e5d",
    "revision": 0,
    "status": "queued",
    "progress": 0,
    "errorMessage": null,
    "createdAt": "2026-09-01T11:20:00.000Z",
    "finishedAt": null,
    "file": null,
    "downloadUrl": null,
    "downloadUrlExpiresAt": null,
    "fileDeletesAt": null,
    "fileDeletedAt": null
  },
  "paidWith": "plan"
}
```

#### A composition that uses a file the API cannot use

`POST /v1/compositions/cmp_4e8c2a6f09d14b73a1e5/exports`

```json title="HTTP 409"
{
  "error": {
    "code": "CONFLICT",
    "message": "This composition uses a file that cannot be used through the API. Remove it in the GPT-Video app, then export again.",
    "requestId": "req_0d6f3b9e2a5c8f1b4e7a0c36"
  }
}
```

#### An export longer than the API bonus pays for

`POST /v1/compositions/cmp_cc26cb47fd6004aa5fba/exports`

```json title="HTTP 402"
{
  "error": {
    "code": "PLAN_REQUIRED",
    "message": "This composition is 30 minutes long, and the API bonus pays for exports of up to 10 minutes. Choose a Creator or Studio plan to export it.",
    "hint": "The API bonus pays first. When it cannot, choose a Creator or Studio plan at https://www.gpt-video.com/app/billing.",
    "requestId": "req_3c8e1b6f9a2d47e05b8c1f63"
  }
}
```

#### The exports of the day are used up

`POST /v1/compositions/cmp_4e8c2a6f09d14b73a1e5/exports`

```json title="HTTP 429"
{
  "error": {
    "code": "DAILY_LIMIT_REACHED",
    "message": "Daily export limit reached (100). It resets at 00:00 UTC. Upgrade your plan for a higher limit.",
    "hint": "The limit resets at 00:00 UTC. A higher plan raises it: https://www.gpt-video.com/app/billing.",
    "requestId": "req_8b1e5a3d7f0c2e6b9a4d1f72"
  }
}
```

#### A composition that does not exist

`POST /v1/compositions/cmp_7a1f5d3c9e0b42d8f6a2/exports`

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

## Errors

| Code | Status | When | What to do |
| --- | --- | --- | --- |
| [`INSUFFICIENT_CREDITS`](/developers/errors.md#INSUFFICIENT_CREDITS) | 402 | See the error reference. | Stop and tell the user. |
| [`PLAN_REQUIRED`](/developers/errors.md#PLAN_REQUIRED) | 402 | An export longer than the API bonus pays for | Stop and tell the user. |
| [`NOT_FOUND`](/developers/errors.md#NOT_FOUND) | 404 | A composition that does not exist | Change the request, then send it again. |
| [`CONFLICT`](/developers/errors.md#CONFLICT) | 409 | A composition that uses a file the API cannot use | Read the resource again, then decide. Do not send the same request again. |
| [`NOT_READY`](/developers/errors.md#NOT_READY) | 409 | The framing of the clip is still being computed | Wait for Retry-After, then send the same request again. |
| [`DAILY_LIMIT_REACHED`](/developers/errors.md#DAILY_LIMIT_REACHED) | 429 | The exports of the day are used up | Stop until the limit resets at 00:00 UTC. |

Every call can also answer these errors. [`VALIDATION_FAILED`](/developers/errors.md#VALIDATION_FAILED), [`UNKNOWN_PARAMETER`](/developers/errors.md#UNKNOWN_PARAMETER), [`IDEMPOTENCY_KEY_REQUIRED`](/developers/errors.md#IDEMPOTENCY_KEY_REQUIRED), [`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

Uses one export of the daily allowance, which resets at 00:00 UTC, and no credits. When the API bonus pays for it, it also uses one bonus credit. Nothing is returned if the export fails or is cancelled.

- Paid by the API bonus, it uses 1 bonus credit.
- When the API bonus pays, an account can make 5 exports a day, each up to 600 seconds long.
- See [API pricing](/developers/pricing.md) for who pays first.

## Related operations

- [`get_export`](/developers/api/get-export.md) (`GET /v1/exports/{exportId}`) Get an export
- [`list_exports`](/developers/api/list-exports.md) (`GET /v1/projects/{projectId}/exports`) List the exports of a project
- [`accept_clip`](/developers/api/accept-clip.md) (`POST /v1/clips/{clipId}/accept`) Accept a clip
- [`get_composition`](/developers/api/get-composition.md) (`GET /v1/compositions/{compositionId}`) Get a composition
- [`get_job`](/developers/api/get-job.md) (`GET /v1/jobs/{jobId}`) Get a job
- [`get_composition_captions`](/developers/api/get-composition-captions.md) (`GET /v1/compositions/{compositionId}/captions`) Get the captions of a composition

## Questions

### What happens if my request times out and I send it again?

Send it again with the same Idempotency-Key. You get the same export back and no second export is used from the daily allowance; if the first try stopped before the render started, the retry starts it. A new key is a new export.

### Why does the answer have no download link?

The answer to this call is kept and repeated for retries, and a download link must not outlive its few minutes. Read the export with get_export (`GET /v1/exports/{exportId}`): once it is done, every read gives a fresh downloadUrl.

### What does NOT_READY mean here?

The clip is still being framed on the speaker's face. Wait the seconds in Retry-After and send the same request again; nothing was used.

### Why is a composition refused with CONFLICT?

It uses a file the API cannot use, which the GPT-Video app put in it, such as a video made for a UGC project. Remove that file in the app, then export again; nothing was used.

### How long can an export paid by the API bonus be?

Up to 10 minutes. A longer composition answers PLAN_REQUIRED for an account that is not on the Creator or Studio plan; on those plans, the exports of the plan have no such limit.
