---
title: "Create a split screen project | GPT-Video API reference"
description: "Creates a split screen project from two videos you uploaded: the main video on top and a background video under it."
url: "https://www.gpt-video.com/developers/api/create-split-screen-project"
operation: "create_split_screen_project"
method: "POST"
path: "/v1/projects/split-screen"
scope: "clips:write"
---

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

# Create a split screen project

Operation id `create_split_screen_project`. Request `POST /v1/projects/split-screen`.

Creates a split screen project from two videos you uploaded: the main video on top and a background video under it.

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 ids of two videos. The two videos and the language are chosen here and only here: to use other videos, create another project. The layout, the captions and the start of the background can be changed later with update_split_screen_project (`PATCH /v1/projects/{projectId}/split-screen`). Nothing is built and no credits are used until you call build_split_screen_video (`POST /v1/projects/{projectId}/split-screen/build`). Send an Idempotency-Key: a retry without one creates a second project.

## Example use

A channel that posts interview moments uploads an interview clip and a gameplay video, then creates a project with the interview on top and the gameplay under it. It checks the price with estimate_analysis (`POST /v1/projects/{projectId}/analyze/estimate`) before it builds.

## Endpoint

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

- 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 |
| --- | --- | --- | --- |
| `mainAssetId` | string | Required | The main video, shown on top and transcribed for the captions: the asset id that complete_upload (`POST /v1/uploads/{uploadId}/complete`) returned. It cannot be changed later. Matches `^ast_n?[0-9a-f]{20}$`. |
| `backgroundAssetId` | string | Required | The background video, shown under the main video, such as gameplay footage. It cannot be changed later. Matches `^ast_n?[0-9a-f]{20}$`. |
| `layout` | string | Required | How the screen is split: 50-50 gives each video half the screen, 60-40 gives the main video more. One of `50-50`, `60-40`. |
| `captionStyle` | string or null | Required | The caption style, or null for a video with no captions. get_setting_options (`GET /v1/reference/setting-options`) lists every style with what it looks like. One of `bold-karaoke`, `word-pop`, `boxed`, `outline`. |
| `language` | string | Required | The language spoken in the main video: en, it, or auto to detect it. It cannot be changed later. One of `auto`, `it`, `en`. |
| `backgroundStartSec` | number | Optional | Where the background video starts, in seconds, before the end of the background video. Leave it out to let the first build choose a random start, which is then kept. At least 0. |
| `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 split screen 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/split-screen" \
  -H "Authorization: Bearer $GPT_VIDEO_API_KEY" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "mainAssetId": "ast_8e21b0d94c6f4a17b358",
    "backgroundAssetId": "ast_8ac5d8b97546145cdbf6",
    "layout": "60-40",
    "captionStyle": "bold-karaoke",
    "language": "en",
    "title": "Product demo over the city"
  }'
```

```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/split-screen", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.GPT_VIDEO_API_KEY}`,
    "Idempotency-Key": idempotencyKey,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "mainAssetId": "ast_8e21b0d94c6f4a17b358",
    "backgroundAssetId": "ast_8ac5d8b97546145cdbf6",
    "layout": "60-40",
    "captionStyle": "bold-karaoke",
    "language": "en",
    "title": "Product demo over the city"
  }),
});
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/split-screen",
    headers={
        "Authorization": f"Bearer {os.environ['GPT_VIDEO_API_KEY']}",
        "Idempotency-Key": idempotency_key,
    },
    json={
        "mainAssetId": "ast_8e21b0d94c6f4a17b358",
        "backgroundAssetId": "ast_8ac5d8b97546145cdbf6",
        "layout": "60-40",
        "captionStyle": "bold-karaoke",
        "language": "en",
        "title": "Product demo over the city"
    },
)
data = r.json()
print(data)
```

```json title="MCP (Full)"
{
  "method": "tools/call",
  "params": {
    "name": "create_split_screen_project",
    "arguments": {
      "mainAssetId": "ast_8e21b0d94c6f4a17b358",
      "backgroundAssetId": "ast_8ac5d8b97546145cdbf6",
      "layout": "60-40",
      "captionStyle": "bold-karaoke",
      "language": "en",
      "title": "Product demo over the city"
    }
  }
}
```

## 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_5b8e2d71c4a94f06b3e9",
    "kind": "splitscreen",
    "title": "Product demo over the city",
    "status": "draft",
    "sourceAssetId": "ast_8e21b0d94c6f4a17b358",
    "compositionId": null,
    "settings": null,
    "missingSettings": [],
    "errorMessage": null,
    "splitScreen": {
      "mainAssetId": "ast_8e21b0d94c6f4a17b358",
      "backgroundAssetId": "ast_8ac5d8b97546145cdbf6",
      "layout": "60-40",
      "captionStyle": "bold-karaoke",
      "language": "en",
      "backgroundStartSec": null,
      "vocabulary": null
    },
    "streamer": 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 background video that does not exist

`POST /v1/projects/split-screen`

```json title="Request body"
{
  "mainAssetId": "ast_8e21b0d94c6f4a17b358",
  "backgroundAssetId": "ast_0b1e5c9d3a7f40628e2d",
  "layout": "50-50",
  "captionStyle": null,
  "language": "en"
}
```

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

#### A background that is not a video

`POST /v1/projects/split-screen`

```json title="Request body"
{
  "mainAssetId": "ast_8e21b0d94c6f4a17b358",
  "backgroundAssetId": "ast_e8a35ed674148f27b585",
  "layout": "50-50",
  "captionStyle": null,
  "language": "en"
}
```

```json title="HTTP 409"
{
  "error": {
    "code": "CONFLICT",
    "message": "This asset is not a video. A split screen needs two videos: the main video and the background video.",
    "requestId": "req_1f8c3e6a0d2b49f75a3e8c01"
  }
}
```

#### A background start past the end of the background

`POST /v1/projects/split-screen`

```json title="Request body"
{
  "mainAssetId": "ast_8e21b0d94c6f4a17b358",
  "backgroundAssetId": "ast_8ac5d8b97546145cdbf6",
  "layout": "50-50",
  "captionStyle": "word-pop",
  "language": "en",
  "backgroundStartSec": 90
}
```

```json title="HTTP 400"
{
  "error": {
    "code": "VALIDATION_FAILED",
    "message": "backgroundStartSec (90) is past the end of the background video (60 seconds). Use a smaller value.",
    "hint": "Fix the request as the message says, then send it again.",
    "requestId": "req_9b4e1a7d3c0f5e28b6a1d4c7"
  }
}
```

## Errors

| Code | Status | When | What to do |
| --- | --- | --- | --- |
| [`NOT_FOUND`](/developers/errors.md#NOT_FOUND) | 404 | A background video that does not exist | Change the request, then send it again. |
| [`CONFLICT`](/developers/errors.md#CONFLICT) | 409 | A background 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_split_screen_video (`POST /v1/projects/{projectId}/split-screen/build`).

## Related operations

- [`update_split_screen_project`](/developers/api/update-split-screen-project.md) (`PATCH /v1/projects/{projectId}/split-screen`) Update a split screen project
- [`build_split_screen_video`](/developers/api/build-split-screen-video.md) (`POST /v1/projects/{projectId}/split-screen/build`) Build a split screen 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 videos or the language after creating the project?

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

### What does a split screen build cost?

One credit per started minute of the main video and of the background video together. estimate_analysis (`POST /v1/projects/{projectId}/analyze/estimate`) tells you the price before you build.
