---
title: "Turn a long video into clips with the API"
description: "From an uploaded video to a vertical MP4 file. Create a project, check the price, run the analysis, accept a clip and export it."
url: "https://www.gpt-video.com/developers/guides/clips-from-a-video"
---

# Turn a long video into clips

This guide starts from a video you uploaded and ends with a vertical MP4 file. You create a project, check the price, run the analysis, accept a clip and export it.

Follow the steps in order. A step that needs an id from an earlier answer reads it from a variable that the step before filled in. Change the values in the request bodies to your own. The examples read your key from the `GPT_VIDEO_API_KEY` variable, as [Authentication](/developers/authentication.md) explains.

You need a key and a video that is already uploaded. [Upload a video file](/developers/guides/upload-a-video.md) shows how to upload one. [Pricing](/developers/pricing.md) explains what pays for the analysis.

## Find your video

[`list_assets`](/developers/api/list-assets.md) (`GET /v1/assets`). List your videos.

```bash title="curl"
# This command needs jq to read the answer.
ASSET_ID=$(curl -sS "https://api.gpt-video.com/v1/assets" \
  -H "Authorization: Bearer $GPT_VIDEO_API_KEY" | jq -r '.data[0].id')
```

```ts title="TypeScript"
const res = await fetch("https://api.gpt-video.com/v1/assets", {
  method: "GET",
  headers: {
    "Authorization": `Bearer ${process.env.GPT_VIDEO_API_KEY}`,
  },
});
const data = await res.json();
const assetId = data.data[0].id;
```

```python title="Python"
import os

import requests

r = requests.get(
    "https://api.gpt-video.com/v1/assets",
    headers={
        "Authorization": f"Bearer {os.environ['GPT_VIDEO_API_KEY']}",
    },
)
data = r.json()
asset_id = data["data"][0]["id"]
```

**Example response**

`GET /v1/assets`

```json title="HTTP 200"
{
  "data": [
    {
      "id": "ast_8e21b0d94c6f4a17b358",
      "kind": "video",
      "title": "Product demo",
      "filename": "demo.mp4",
      "origin": "upload",
      "mimeType": "video/mp4",
      "sizeBytes": 50331648,
      "durationSec": 120,
      "width": 1920,
      "height": 1080,
      "fps": 30,
      "hasAudio": true,
      "createdAt": "2026-09-01T09:05:00.000Z",
      "deletesAt": "2026-09-08T09:05:00.000Z"
    },
    {
      "id": "ast_3f9a1c7e5b2d4086a1c4",
      "kind": "video",
      "title": "Interview with a founder",
      "filename": "interview.mp4",
      "origin": "upload",
      "mimeType": "video/mp4",
      "sizeBytes": 734003200,
      "durationSec": 1800,
      "width": 1920,
      "height": 1080,
      "fps": 30,
      "hasAudio": true,
      "createdAt": "2026-09-01T09:00:00.000Z",
      "deletesAt": null
    }
  ],
  "hasMore": false,
  "nextCursor": null
}
```

The list shows your videos with the newest first, so the first one is the video you uploaded last. The example keeps its id in `assetId`. If you uploaded something else since, use the id the upload guide gave you, or add `search` with part of the title.

## Create a project

[`create_project`](/developers/api/create-project.md) (`POST /v1/projects`). Create a project on an uploaded video.

```bash title="curl"
# This command needs jq to read the answer.
# Create the key once. Send this same key again if you retry this request.
IDEMPOTENCY_KEY=$(uuidgen)
PROJECT_ID=$(curl -sS -X POST "https://api.gpt-video.com/v1/projects" \
  -H "Authorization: Bearer $GPT_VIDEO_API_KEY" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "sourceAssetId": "'"$ASSET_ID"'",
    "title": "Product demo clips",
    "settings": {
      "language": "en",
      "clipCount": 5,
      "minDurationSec": 20,
      "maxDurationSec": 45,
      "objective": "howto",
      "captionStyle": "bold-karaoke",
      "reframe": "face"
    }
  }' | jq -r '.project.id')
```

```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/projects", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.GPT_VIDEO_API_KEY}`,
    "Idempotency-Key": idempotencyKey,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "sourceAssetId": assetId,
    "title": "Product demo clips",
    "settings": {
      "language": "en",
      "clipCount": 5,
      "minDurationSec": 20,
      "maxDurationSec": 45,
      "objective": "howto",
      "captionStyle": "bold-karaoke",
      "reframe": "face"
    }
  }),
});
const data = await res.json();
const projectId = data.project.id;
```

```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/projects",
    headers={
        "Authorization": f"Bearer {os.environ['GPT_VIDEO_API_KEY']}",
        "Idempotency-Key": idempotency_key,
    },
    json={
        "sourceAssetId": asset_id,
        "title": "Product demo clips",
        "settings": {
            "language": "en",
            "clipCount": 5,
            "minDurationSec": 20,
            "maxDurationSec": 45,
            "objective": "howto",
            "captionStyle": "bold-karaoke",
            "reframe": "face"
        }
    },
)
data = r.json()
project_id = data["project"]["id"]
```

**Example response**

`POST /v1/projects`

```json title="HTTP 201"
{
  "project": {
    "id": "prj_27dd3348d28146ce5a3a",
    "kind": "autoclip",
    "title": "Product demo clips",
    "status": "draft",
    "sourceAssetId": "ast_8e21b0d94c6f4a17b358",
    "compositionId": null,
    "settings": {
      "language": "en",
      "clipCount": 5,
      "minDurationSec": 20,
      "maxDurationSec": 45,
      "objective": "howto",
      "captionStyle": "bold-karaoke",
      "reframe": "face",
      "instructions": null,
      "vocabulary": null
    },
    "missingSettings": [],
    "splitScreen": null,
    "streamer": null,
    "redditStory": null,
    "fakeText": null,
    "vibeEdit": null,
    "errorMessage": null,
    "createdAt": "2026-09-01T11:00:00.000Z",
    "updatedAt": "2026-09-01T11:00:00.000Z"
  }
}
```

A project holds the video and the choices for the analysis: the language, the number of clips, how long a clip may be, the objective, the caption style and the framing. Change them in the body. [get_setting_options](/developers/api/get-setting-options.md) (`GET /v1/reference/setting-options`) lists the values each setting accepts.

A setting you leave out is named in `missingSettings`. Choose it with [update_project](/developers/api/update-project.md) (`PATCH /v1/projects/{projectId}`) before you start the analysis. This call uses no credits.

The example creates the `Idempotency-Key` once, before the request. If the request times out, send it again with the same key and you get the same project back. A new key would create a second project.

## Check the price

[`estimate_analysis`](/developers/api/estimate-analysis.md) (`POST /v1/projects/{projectId}/analyze/estimate`). Price an analysis before starting it.

```bash title="curl"
curl -X POST "https://api.gpt-video.com/v1/projects/$PROJECT_ID/analyze/estimate" \
  -H "Authorization: Bearer $GPT_VIDEO_API_KEY"
```

```ts title="TypeScript"
const res = await fetch(`https://api.gpt-video.com/v1/projects/${projectId}/analyze/estimate`, {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.GPT_VIDEO_API_KEY}`,
  },
});
const data = await res.json();
console.log(data);
```

```python title="Python"
import os

import requests

r = requests.post(
    f"https://api.gpt-video.com/v1/projects/{project_id}/analyze/estimate",
    headers={
        "Authorization": f"Bearer {os.environ['GPT_VIDEO_API_KEY']}",
    },
)
data = r.json()
print(data)
```

**Example response**

`POST /v1/projects/prj_7c2e9a4f1b3d4e8096a5/analyze/estimate`

```json title="HTTP 200"
{
  "estimate": {
    "credits": 30,
    "creditsToCharge": 30,
    "sourceDurationSec": 1800,
    "runningJobId": null,
    "creditsAvailable": 640,
    "planRequired": false,
    "enough": true,
    "payer": "plan",
    "bonusRemaining": null
  }
}
```

`estimate.credits` is the price of the analysis. `estimate.enough` says whether the account can pay it now, and `estimate.payer` says who would pay. Nothing is charged and nothing is reserved.

If `enough` is false, stop here and read [Handle running out of credits](/developers/guides/out-of-credits.md). [Estimate the cost before you spend credits](/developers/guides/estimate-before-spending.md) explains every field of the answer.

## Start the analysis

[`analyze_project`](/developers/api/analyze-project.md) (`POST /v1/projects/{projectId}/analyze`). Analyze a project.

```bash title="curl"
# This command needs jq to read the answer.
# Create the key once. Send this same key again if you retry this request.
IDEMPOTENCY_KEY=$(uuidgen)
JOB_ID=$(curl -sS -X POST "https://api.gpt-video.com/v1/projects/$PROJECT_ID/analyze" \
  -H "Authorization: Bearer $GPT_VIDEO_API_KEY" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY" | jq -r '.job.id')
```

```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/projects/${projectId}/analyze`, {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.GPT_VIDEO_API_KEY}`,
    "Idempotency-Key": idempotencyKey,
  },
});
const data = await res.json();
const jobId = data.job.id;
```

```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(
    f"https://api.gpt-video.com/v1/projects/{project_id}/analyze",
    headers={
        "Authorization": f"Bearer {os.environ['GPT_VIDEO_API_KEY']}",
        "Idempotency-Key": idempotency_key,
    },
)
data = r.json()
job_id = data["job"]["id"]
```

**Example response**

`POST /v1/projects/prj_7c2e9a4f1b3d4e8096a5/analyze`

```json title="HTTP 202"
{
  "job": {
    "id": "job_b582bc8315e372bf9ba3",
    "kind": "analysis",
    "status": "queued",
    "progress": 0,
    "errorMessage": null,
    "projectId": "prj_7c2e9a4f1b3d4e8096a5",
    "compositionId": null,
    "exportId": null,
    "assetId": null,
    "toolRunId": null,
    "turnId": null,
    "createdAt": "2026-09-01T11:10:00.000Z",
    "startedAt": null,
    "finishedAt": null
  },
  "creditsCharged": 30,
  "reused": false,
  "paidWith": "plan"
}
```

The credits are charged now. The answer comes back at once with the job to follow. `creditsCharged` says what this request was charged and `paidWith` says who paid.

If an analysis of the project is already running, you get that job back, `reused` is true and nothing is charged. If this request times out, send it again with the same `Idempotency-Key`. A retry never charges twice.

## Wait for the analysis

[`get_job`](/developers/api/get-job.md) (`GET /v1/jobs/{jobId}`). Wait for a job to finish.

```bash title="curl"
curl "https://api.gpt-video.com/v1/jobs/$JOB_ID?wait=25" \
  -H "Authorization: Bearer $GPT_VIDEO_API_KEY"
```

```ts title="TypeScript"
const res = await fetch(`https://api.gpt-video.com/v1/jobs/${jobId}?wait=25`, {
  method: "GET",
  headers: {
    "Authorization": `Bearer ${process.env.GPT_VIDEO_API_KEY}`,
  },
});
const data = await res.json();
console.log(data);
```

```python title="Python"
import os

import requests

r = requests.get(
    f"https://api.gpt-video.com/v1/jobs/{job_id}?wait=25",
    headers={
        "Authorization": f"Bearer {os.environ['GPT_VIDEO_API_KEY']}",
    },
)
data = r.json()
print(data)
```

**Example response**

`GET /v1/jobs/job_6c1e8b4a2f0d4a97e3b5?wait=25`

```json title="HTTP 200"
{
  "job": {
    "id": "job_6c1e8b4a2f0d4a97e3b5",
    "kind": "analysis",
    "status": "done",
    "progress": 1,
    "errorMessage": null,
    "projectId": "prj_7c2e9a4f1b3d4e8096a5",
    "compositionId": null,
    "exportId": null,
    "assetId": null,
    "toolRunId": null,
    "turnId": null,
    "createdAt": "2026-09-01T09:11:00.000Z",
    "startedAt": "2026-09-01T09:12:00.000Z",
    "finishedAt": "2026-09-01T09:40:00.000Z"
  }
}
```

The `wait` value holds the request until the job ends or the wait time passes. If `status` is `queued` or `running`, send the same request again. When `status` is `done`, go on to the clips. When it is `failed` or `cancelled`, `errorMessage` says what happened. [Wait for long jobs](/developers/jobs.md) explains this loop.

## List the clips

[`list_clips`](/developers/api/list-clips.md) (`GET /v1/projects/{projectId}/clips`). Only the clips waiting for a decision.

```bash title="curl"
# This command needs jq to read the answer.
CLIP_ID=$(curl -sS "https://api.gpt-video.com/v1/projects/$PROJECT_ID/clips?status=proposed" \
  -H "Authorization: Bearer $GPT_VIDEO_API_KEY" | jq -r '.data[0].id')
```

```ts title="TypeScript"
const res = await fetch(`https://api.gpt-video.com/v1/projects/${projectId}/clips?status=proposed`, {
  method: "GET",
  headers: {
    "Authorization": `Bearer ${process.env.GPT_VIDEO_API_KEY}`,
  },
});
const data = await res.json();
const clipId = data.data[0].id;
```

```python title="Python"
import os

import requests

r = requests.get(
    f"https://api.gpt-video.com/v1/projects/{project_id}/clips?status=proposed",
    headers={
        "Authorization": f"Bearer {os.environ['GPT_VIDEO_API_KEY']}",
    },
)
data = r.json()
clip_id = data["data"][0]["id"]
```

**Example response**

`GET /v1/projects/prj_7c2e9a4f1b3d4e8096a5/clips?status=proposed`

```json title="HTTP 200"
{
  "data": [
    {
      "id": "cnd_2b7f4e91a0c84d35e6f0",
      "projectId": "prj_7c2e9a4f1b3d4e8096a5",
      "sourceAssetId": "ast_3f9a1c7e5b2d4086a1c4",
      "summary": "The founder explains how the first customer paid before the product existed.",
      "reason": "A clear claim in the first seconds and a result at the end.",
      "tags": [
        "startups",
        "sales"
      ],
      "createdAt": "2026-09-01T09:40:00.000Z",
      "status": "proposed",
      "rank": 1,
      "startSec": 300,
      "endSec": 330,
      "durationSec": 30,
      "title": "The first customer",
      "hook": "Our first customer changed everything",
      "score": 81,
      "scoreBreakdown": [
        {
          "factor": "hook",
          "label": "Hook (first 3 seconds)",
          "score": 8,
          "note": "Opens on a concrete claim."
        },
        {
          "factor": "payoff",
          "label": "Payoff / ending",
          "score": 7,
          "note": "Ends on the result."
        },
        {
          "factor": "clarity",
          "label": "Clarity",
          "score": 6,
          "note": "One idea, said plainly."
        },
        {
          "factor": "emotion",
          "label": "Emotion",
          "score": 8,
          "note": "The speaker sounds surprised."
        },
        {
          "factor": "novelty",
          "label": "Novelty / surprise",
          "score": 7,
          "note": "Not the usual advice."
        },
        {
          "factor": "selfContained",
          "label": "Self-contained",
          "score": 6,
          "note": "Needs no context."
        },
        {
          "factor": "quotability",
          "label": "Quotability",
          "score": 8,
          "note": "Has a line people repeat."
        },
        {
          "factor": "dialogue",
          "label": "Dialogue (back-and-forth)",
          "score": 7,
          "note": "A single speaker."
        }
      ],
      "transcriptExcerpt": "before we had a product and that changed everything for our team the first customer paid us before we had a product and that changed everything for our team the first customer paid us before we had a product and that changed everything for our team the first customer paid us before we had a product and that changed everything for our team the first customer paid us before we had a product and that",
      "social": {
        "title": "The first customer",
        "caption": "How a first customer paid before there was a product.",
        "hashtags": [
          "#startup",
          "#founder"
        ]
      },
      "compositionId": null
    }
  ],
  "hasMore": false,
  "nextCursor": null
}
```

The clips come in the order the analysis ranked them. Each one has a title, a hook, its place in the source video and a score. The example keeps the first clip.

If `hasMore` is true, send `nextCursor` back as `cursor` to read the next page. [Pagination and idempotency](/developers/pagination-and-idempotency.md) shows how. To read one clip on its own, call [get_clip](/developers/api/get-clip.md) (`GET /v1/clips/{clipId}`).

## Accept the clip

[`accept_clip`](/developers/api/accept-clip.md) (`POST /v1/clips/{clipId}/accept`). Accept a proposed clip.

```bash title="curl"
# This command needs jq to read the answer.
COMPOSITION_ID=$(curl -sS -X POST "https://api.gpt-video.com/v1/clips/$CLIP_ID/accept" \
  -H "Authorization: Bearer $GPT_VIDEO_API_KEY" | jq -r '.composition.id')
```

```ts title="TypeScript"
const res = await fetch(`https://api.gpt-video.com/v1/clips/${clipId}/accept`, {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.GPT_VIDEO_API_KEY}`,
  },
});
const data = await res.json();
const compositionId = data.composition.id;
```

```python title="Python"
import os

import requests

r = requests.post(
    f"https://api.gpt-video.com/v1/clips/{clip_id}/accept",
    headers={
        "Authorization": f"Bearer {os.environ['GPT_VIDEO_API_KEY']}",
    },
)
data = r.json()
composition_id = data["composition"]["id"]
```

**Example response**

`POST /v1/clips/cnd_2b7f4e91a0c84d35e6f0/accept`

```json title="HTTP 200"
{
  "clip": {
    "id": "cnd_2b7f4e91a0c84d35e6f0",
    "projectId": "prj_7c2e9a4f1b3d4e8096a5",
    "sourceAssetId": "ast_3f9a1c7e5b2d4086a1c4",
    "summary": "The founder explains how the first customer paid before the product existed.",
    "reason": "A clear claim in the first seconds and a result at the end.",
    "tags": [
      "startups",
      "sales"
    ],
    "createdAt": "2026-09-01T09:40:00.000Z",
    "status": "accepted",
    "rank": 1,
    "startSec": 300,
    "endSec": 330,
    "durationSec": 30,
    "title": "The first customer",
    "hook": "Our first customer changed everything",
    "score": 81,
    "scoreBreakdown": [
      {
        "factor": "hook",
        "label": "Hook (first 3 seconds)",
        "score": 8,
        "note": "Opens on a concrete claim."
      },
      {
        "factor": "payoff",
        "label": "Payoff / ending",
        "score": 7,
        "note": "Ends on the result."
      },
      {
        "factor": "clarity",
        "label": "Clarity",
        "score": 6,
        "note": "One idea, said plainly."
      },
      {
        "factor": "emotion",
        "label": "Emotion",
        "score": 8,
        "note": "The speaker sounds surprised."
      },
      {
        "factor": "novelty",
        "label": "Novelty / surprise",
        "score": 7,
        "note": "Not the usual advice."
      },
      {
        "factor": "selfContained",
        "label": "Self-contained",
        "score": 6,
        "note": "Needs no context."
      },
      {
        "factor": "quotability",
        "label": "Quotability",
        "score": 8,
        "note": "Has a line people repeat."
      },
      {
        "factor": "dialogue",
        "label": "Dialogue (back-and-forth)",
        "score": 7,
        "note": "A single speaker."
      }
    ],
    "transcriptExcerpt": "before we had a product and that changed everything for our team the first customer paid us before we had a product and that changed everything for our team the first customer paid us before we had a product and that changed everything for our team the first customer paid us before we had a product and that changed everything for our team the first customer paid us before we had a product and that",
    "social": {
      "title": "The first customer",
      "caption": "How a first customer paid before there was a product.",
      "hashtags": [
        "#startup",
        "#founder"
      ]
    },
    "compositionId": "cmp_b18e4f2a6c0d4973e5a1"
  },
  "composition": {
    "id": "cmp_b18e4f2a6c0d4973e5a1",
    "projectId": "prj_7c2e9a4f1b3d4e8096a5",
    "clipId": "cnd_2b7f4e91a0c84d35e6f0",
    "kind": "autoclip",
    "title": "The first customer",
    "revision": 0,
    "durationSec": 30,
    "width": 1080,
    "height": 1920,
    "fps": 30,
    "source": {
      "assetId": "ast_3f9a1c7e5b2d4086a1c4",
      "startSec": 300,
      "endSec": 330
    },
    "hook": "Our first customer changed everything",
    "captions": {
      "style": "word-pop",
      "wordCount": 75
    },
    "framing": {
      "status": "tracking",
      "jobId": "job_4f0a9c2e7d1b4e3a86c5"
    },
    "createdAt": "2026-09-01T10:30:00.000Z",
    "updatedAt": "2026-09-01T10:30:00.000Z"
  }
}
```

Accepting turns the clip into a composition, the vertical video cut from the source with captions and the hook. It uses no credits. Accepting a clip that is already accepted returns the same composition again.

To drop a clip you do not want, call [reject_clip](/developers/api/reject-clip.md) (`POST /v1/clips/{clipId}/reject`).

## Wait for the framing

[`get_composition`](/developers/api/get-composition.md) (`GET /v1/compositions/{compositionId}`). Get a composition.

```bash title="curl"
curl "https://api.gpt-video.com/v1/compositions/$COMPOSITION_ID" \
  -H "Authorization: Bearer $GPT_VIDEO_API_KEY"
```

```ts title="TypeScript"
const res = await fetch(`https://api.gpt-video.com/v1/compositions/${compositionId}`, {
  method: "GET",
  headers: {
    "Authorization": `Bearer ${process.env.GPT_VIDEO_API_KEY}`,
  },
});
const data = await res.json();
console.log(data);
```

```python title="Python"
import os

import requests

r = requests.get(
    f"https://api.gpt-video.com/v1/compositions/{composition_id}",
    headers={
        "Authorization": f"Bearer {os.environ['GPT_VIDEO_API_KEY']}",
    },
)
data = r.json()
print(data)
```

**Example response**

`GET /v1/compositions/cmp_4e8c2a6f09d14b73a1e5`

```json title="HTTP 200"
{
  "composition": {
    "id": "cmp_4e8c2a6f09d14b73a1e5",
    "projectId": "prj_7c2e9a4f1b3d4e8096a5",
    "clipId": "cnd_9e04a6c3d12b4f78a5b9",
    "kind": "autoclip",
    "title": "Paid before the product",
    "revision": 0,
    "durationSec": 30,
    "width": 1080,
    "height": 1920,
    "fps": 30,
    "source": {
      "assetId": "ast_3f9a1c7e5b2d4086a1c4",
      "startSec": 120,
      "endSec": 150
    },
    "hook": "They paid us before we had a product",
    "captions": {
      "style": "word-pop",
      "wordCount": 75
    },
    "framing": {
      "status": "centered",
      "jobId": null
    },
    "createdAt": "2026-09-01T10:00:00.000Z",
    "updatedAt": "2026-09-01T10:00:00.000Z"
  }
}
```

Right after you accept a clip, GPT-Video is still following the face in it, and `framing.status` is `tracking`. Read the composition again until the status changes. An export asked for earlier answers `NOT_READY`.

## Export the clip

[`create_export`](/developers/api/create-export.md) (`POST /v1/compositions/{compositionId}/exports`). Export a composition.

```bash title="curl"
# This command needs jq to read the answer.
# Create the key once. Send this same key again if you retry this request.
IDEMPOTENCY_KEY=$(uuidgen)
EXPORT_ID=$(curl -sS -X POST "https://api.gpt-video.com/v1/compositions/$COMPOSITION_ID/exports" \
  -H "Authorization: Bearer $GPT_VIDEO_API_KEY" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY" | jq -r '.export.id')
```

```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/${compositionId}/exports`, {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.GPT_VIDEO_API_KEY}`,
    "Idempotency-Key": idempotencyKey,
  },
});
const data = await res.json();
const exportId = data.export.id;
```

```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(
    f"https://api.gpt-video.com/v1/compositions/{composition_id}/exports",
    headers={
        "Authorization": f"Bearer {os.environ['GPT_VIDEO_API_KEY']}",
        "Idempotency-Key": idempotency_key,
    },
)
data = r.json()
export_id = data["export"]["id"]
```

**Example response**

`POST /v1/compositions/cmp_4e8c2a6f09d14b73a1e5/exports`

```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"
}
```

The answer comes back at once with the export, queued. It has no download link yet. An export counts against your daily export limit and uses no plan credits. When the API bonus pays for it, it uses bonus credits.

If the framing is not done, the answer is `NOT_READY`. Wait the seconds in the `Retry-After` header and send the same request again with the same `Idempotency-Key`. Asking again while the same version is exporting returns that export and uses nothing.

## Download the file

[`get_export`](/developers/api/get-export.md) (`GET /v1/exports/{exportId}`). Wait for an export to finish.

```bash title="curl"
curl "https://api.gpt-video.com/v1/exports/$EXPORT_ID?wait=25" \
  -H "Authorization: Bearer $GPT_VIDEO_API_KEY"
```

```ts title="TypeScript"
const res = await fetch(`https://api.gpt-video.com/v1/exports/${exportId}?wait=25`, {
  method: "GET",
  headers: {
    "Authorization": `Bearer ${process.env.GPT_VIDEO_API_KEY}`,
  },
});
const data = await res.json();
console.log(data);
```

```python title="Python"
import os

import requests

r = requests.get(
    f"https://api.gpt-video.com/v1/exports/{export_id}?wait=25",
    headers={
        "Authorization": f"Bearer {os.environ['GPT_VIDEO_API_KEY']}",
    },
)
data = r.json()
print(data)
```

**Example response**

`GET /v1/exports/rnd_0f6b3e9d2c7a4158b4a3?wait=25`

```json title="HTTP 200"
{
  "export": {
    "id": "rnd_0f6b3e9d2c7a4158b4a3",
    "compositionId": "cmp_4e8c2a6f09d14b73a1e5",
    "projectId": "prj_7c2e9a4f1b3d4e8096a5",
    "jobId": "job_a3e9d05c7b1f4e2896d4",
    "revision": 0,
    "status": "done",
    "progress": 1,
    "errorMessage": null,
    "createdAt": "2026-09-01T10:10:00.000Z",
    "finishedAt": "2026-09-01T10:15:00.000Z",
    "file": {
      "mimeType": "video/mp4",
      "sizeBytes": 18874368,
      "durationSec": 30,
      "width": 1080,
      "height": 1920
    },
    "downloadUrl": "https://www.gpt-video.com/media/ast_c47d2e8a19b54f06d2e1?exp=1788258600&sig=9b2e61c04d7f3a58e1b6c2d9f0a4e7b3",
    "downloadUrlExpiresAt": "2026-09-01T10:30:00.000Z",
    "fileDeletesAt": "2026-09-15T10:15:00.000Z",
    "fileDeletedAt": null
  }
}
```

Send the request again while `status` is `queued` or `rendering`. When `status` is `done`, `downloadUrl` is a link to the MP4 file. The link works without your API key, so do not send the key to it. Save the response body as an `.mp4` file.

The link is short-lived, and `downloadUrlExpiresAt` says until when. Read the export again right before you download to get a fresh link. If the export failed, `errorMessage` says why. Export the composition again with a new `Idempotency-Key`.

## Next steps

- Read [GPT-Video API errors and what to do next](/developers/errors.md) to handle each error code in your program.
- Use [rate limits](/developers/rate-limits.md) to see how many requests you can send.
- Run the analysis again on the same project when you want a new set of clips. The clips you accepted stay.
