---
title: "Create a streamer project | GPT-Video API reference"
description: "Creates a streamer project from a clip you uploaded: a vertical video that follows the speaker, with a headline and captions."
url: "https://www.gpt-video.com/developers/api/create-streamer-project"
operation: "create_streamer_project"
method: "POST"
path: "/v1/projects/streamer"
scope: "clips:write"
---

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

# Create a streamer project

Operation id `create_streamer_project`. Request `POST /v1/projects/streamer`.

Creates a streamer project from a clip you uploaded: a vertical video that follows the speaker, with a headline and captions.

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 after complete_upload (`POST /v1/uploads/{uploadId}/complete`), with the id of the clip. The clip and its language are chosen here and only here: to use another clip, create another project. The headline, the captions, the framing and the vocabulary can be changed later with update_streamer_project (`PATCH /v1/projects/{projectId}/streamer`). Nothing is built and no credits are used until you call build_streamer_video (`POST /v1/projects/{projectId}/streamer/build`). Send an Idempotency-Key: a retry without one creates a second project.

## Example use

A streamer's editor uploads a stream highlight and creates a project with a headline, captions and reframe set to face, so the vertical video follows the speaker. It checks the price with estimate_analysis (`POST /v1/projects/{projectId}/analyze/estimate`) before the build.

## Endpoint

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

- 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 |
| --- | --- | --- | --- |
| `sourceAssetId` | string | Required | The clip to make vertical: the asset id that complete_upload (`POST /v1/uploads/{uploadId}/complete`) returned. It cannot be changed later. Matches `^ast_n?[0-9a-f]{20}$`. |
| `headline` | string | Required | The line shown over the clip, up to 120 characters; "" for none. At most 120 characters. |
| `language` | string | Required | The language spoken in the clip: en, it, or auto to detect it. It cannot be changed later. One of `auto`, `it`, `en`. |
| `captionStyle` | string | Required | The caption style. get_setting_options (`GET /v1/reference/setting-options`) lists every style with what it looks like. One of `bold-karaoke`, `word-pop`, `boxed`, `outline`. |
| `reframe` | string | Required | How the video follows the speaker: face follows the face, center keeps the middle of the frame. One of `face`, `center`. |
| `vocabulary` | string | Optional | Names, brands and jargon the transcription should spell right, separated by commas. At most 311 characters. At most 311 characters. |
| `title` | string | Optional | A name for the project, shown in the GPT-Video app. Leave it out to get a default name. From 1 to 120 characters. Matches `\S`. |

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

## Request

Create a streamer project.

```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/projects/streamer" \
  -H "Authorization: Bearer $GPT_VIDEO_API_KEY" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "sourceAssetId": "ast_8e21b0d94c6f4a17b358",
    "headline": "Watch the editor cut it",
    "language": "en",
    "captionStyle": "word-pop",
    "reframe": "face"
  }'
```

```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/streamer", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.GPT_VIDEO_API_KEY}`,
    "Idempotency-Key": idempotencyKey,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "sourceAssetId": "ast_8e21b0d94c6f4a17b358",
    "headline": "Watch the editor cut it",
    "language": "en",
    "captionStyle": "word-pop",
    "reframe": "face"
  }),
});
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/projects/streamer",
    headers={
        "Authorization": f"Bearer {os.environ['GPT_VIDEO_API_KEY']}",
        "Idempotency-Key": idempotency_key,
    },
    json={
        "sourceAssetId": "ast_8e21b0d94c6f4a17b358",
        "headline": "Watch the editor cut it",
        "language": "en",
        "captionStyle": "word-pop",
        "reframe": "face"
    },
)
data = r.json()
print(data)
```

```json title="MCP (Full)"
{
  "method": "tools/call",
  "params": {
    "name": "create_streamer_project",
    "arguments": {
      "sourceAssetId": "ast_8e21b0d94c6f4a17b358",
      "headline": "Watch the editor cut it",
      "language": "en",
      "captionStyle": "word-pop",
      "reframe": "face"
    }
  }
}
```

## Response

A successful call answers HTTP 201. Returns a [Project object](/developers/objects.md#project) in `project`.

### Fields to check

| Name | Type | Description |
| --- | --- | --- |
| `project.id` | string | The project id. It starts with prj_. |
| `project.status` | string | draft has not been analyzed yet, processing is being analyzed, ready has clips, failed means the last analysis failed and errorMessage says why. |

### Response example

```json title="HTTP 201"
{
  "project": {
    "id": "prj_0c7f3a95e1d24b68a4f2",
    "kind": "streamer",
    "title": "Streamer clip",
    "status": "draft",
    "sourceAssetId": "ast_8e21b0d94c6f4a17b358",
    "compositionId": null,
    "settings": null,
    "missingSettings": [],
    "errorMessage": null,
    "splitScreen": null,
    "streamer": {
      "sourceAssetId": "ast_8e21b0d94c6f4a17b358",
      "headline": "Watch the editor cut it",
      "language": "en",
      "captionStyle": "word-pop",
      "reframe": "face",
      "vocabulary": null
    },
    "redditStory": null,
    "fakeText": null,
    "vibeEdit": null,
    "createdAt": "2026-09-01T11:00:00.000Z",
    "updatedAt": "2026-09-01T11:00:00.000Z"
  }
}
```

**All response fields**

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

**More examples**

#### A clip that does not exist

`POST /v1/projects/streamer`

```json title="Request body"
{
  "sourceAssetId": "ast_0b1e5c9d3a7f40628e2d",
  "headline": "",
  "language": "en",
  "captionStyle": "word-pop",
  "reframe": "center"
}
```

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

#### A file that is not a video

`POST /v1/projects/streamer`

```json title="Request body"
{
  "sourceAssetId": "ast_e8a35ed674148f27b585",
  "headline": "",
  "language": "en",
  "captionStyle": "word-pop",
  "reframe": "center"
}
```

```json title="HTTP 409"
{
  "error": {
    "code": "CONFLICT",
    "message": "This asset is not a video. A streamer project needs a video clip.",
    "requestId": "req_7b2f9d4a1e6c05b38d7a2e40"
  }
}
```

## Errors

| Code | Status | When | What to do |
| --- | --- | --- | --- |
| [`NOT_FOUND`](/developers/errors.md#NOT_FOUND) | 404 | A clip that does not exist | Change the request, then send it again. |
| [`CONFLICT`](/developers/errors.md#CONFLICT) | 409 | A file that is not a video | 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 build is charged when you call build_streamer_video (`POST /v1/projects/{projectId}/streamer/build`).

## Related operations

- [`update_streamer_project`](/developers/api/update-streamer-project.md) (`PATCH /v1/projects/{projectId}/streamer`) Update a streamer project
- [`build_streamer_video`](/developers/api/build-streamer-video.md) (`POST /v1/projects/{projectId}/streamer/build`) Build a streamer video
- [`estimate_analysis`](/developers/api/estimate-analysis.md) (`POST /v1/projects/{projectId}/analyze/estimate`) Estimate the credits of an analysis or a build
- [`complete_upload`](/developers/api/complete-upload.md) (`POST /v1/uploads/{uploadId}/complete`) Complete an upload
- [`get_setting_options`](/developers/api/get-setting-options.md) (`GET /v1/reference/setting-options`) Get the setting options

## Questions

### Can I change the clip after creating the project?

No. The clip and its language are chosen when the project is created, because the price and the captions depend on them. To use another clip, create another project.

### What does reframe do?

face makes the vertical video follow the face of the speaker, which is computed after the build; center keeps the middle of the frame. Change it with update_streamer_project (`PATCH /v1/projects/{projectId}/streamer`), then build again.
