---
title: "Get a composition | GPT-Video API reference"
description: "Returns a composition: the vertical video made from a clip, with its size, captions, hook and framing."
url: "https://www.gpt-video.com/developers/api/get-composition"
operation: "get_composition"
method: "GET"
path: "/v1/compositions/{compositionId}"
scope: "clips:read"
---

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

# Get a composition

Operation id `get_composition`. Request `GET /v1/compositions/{compositionId}`.

Returns a composition: the vertical video made from a clip, with its size, captions, hook and framing.

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 after accept_clip (`POST /v1/clips/{clipId}/accept`) to follow the framing: while framing.status is tracking, the face is still being followed by the job in framing.jobId, and create_export (`POST /v1/compositions/{compositionId}/exports`) answers NOT_READY until it is done. Every change to the composition adds one to revision, the finished framing included, and an export renders the composition as it is when its render starts, and a finished export says which revision it rendered. Compositions of other kinds of project are shown too: source is filled for Auto Clip and streamer compositions, hook for Auto Clip compositions only.

## Example use

After accepting a clip, a script reads the composition every few seconds until framing.status is no longer tracking. It then starts the export and stores the revision it saw, to compare it with the finished export.

## Endpoint

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

- Scope `clips:read`.
- Rate limit 120 requests a minute for each key and 240 for each account, shared by every `read` call. See [rate limits](/developers/rate-limits.md).
- No `Idempotency-Key`.

## Parameters

### Path

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `compositionId` | string | Required | The id of the composition: accept_clip (`POST /v1/clips/{clipId}/accept`) returns it for a clip, and list_compositions (`GET /v1/projects/{projectId}/compositions`) or the project's compositionId give it for the other kinds of project. Matches `^cmp_[0-9a-f]{20}$`. |

## Request

Get a composition.

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

```ts title="TypeScript"
const res = await fetch("https://api.gpt-video.com/v1/compositions/cmp_4e8c2a6f09d14b73a1e5", {
  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(
    "https://api.gpt-video.com/v1/compositions/cmp_4e8c2a6f09d14b73a1e5",
    headers={
        "Authorization": f"Bearer {os.environ['GPT_VIDEO_API_KEY']}",
    },
)
data = r.json()
print(data)
```

```json title="MCP (Full)"
{
  "method": "tools/call",
  "params": {
    "name": "get_composition",
    "arguments": {
      "compositionId": "cmp_4e8c2a6f09d14b73a1e5"
    }
  }
}
```

## Response

A successful call answers HTTP 200. Returns a [Composition object](/developers/objects.md#composition) in `composition`.

### Fields to check

| Name | Type | Description |
| --- | --- | --- |
| `composition.id` | string | The composition id. It starts with cmp_. |
| `composition.framing.status` | string |   |
| `composition.framing.jobId` | string or null |   |

### Response example

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

**All response fields**

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

**More examples**

#### A composition that does not exist

`GET /v1/compositions/cmp_7a1f5d3c9e0b42d8f6a2`

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

## Errors

| Code | Status | When | What to do |
| --- | --- | --- | --- |
| [`NOT_FOUND`](/developers/errors.md#NOT_FOUND) | 404 | A composition that does not exist | Change the request, then send it 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), [`RATE_LIMITED`](/developers/errors.md#RATE_LIMITED), [`INTERNAL`](/developers/errors.md#INTERNAL), [`UNAVAILABLE`](/developers/errors.md#UNAVAILABLE).

## Cost

No credits are used.

## Related operations

- [`get_composition_captions`](/developers/api/get-composition-captions.md) (`GET /v1/compositions/{compositionId}/captions`) Get the captions of a composition
- [`accept_clip`](/developers/api/accept-clip.md) (`POST /v1/clips/{clipId}/accept`) Accept a clip
- [`create_export`](/developers/api/create-export.md) (`POST /v1/compositions/{compositionId}/exports`) Export a composition
- [`get_job`](/developers/api/get-job.md) (`GET /v1/jobs/{jobId}`) Get a job
- [`list_compositions`](/developers/api/list-compositions.md) (`GET /v1/projects/{projectId}/compositions`) List the compositions of a project
- [`edit_composition`](/developers/api/edit-composition.md) (`POST /v1/compositions/{compositionId}/edit`) Edit a composition

## Questions

### When is a composition ready to export?

When framing.status is no longer tracking. Right after accept_clip (`POST /v1/clips/{clipId}/accept`) the framing is still being computed, and an export answers NOT_READY until it is done.

### Why are source and hook null?

source is filled for Auto Clip and streamer compositions, and hook for Auto Clip compositions only: a composition of another kind has no single source window to show. source is also null when its video no longer exists or cannot be used through the API.
