Create a Vibe Editing project
create_vibe_edit_projectPOST/v1/projects/vibe-editBeta
Creates a Vibe Editing project from a video you uploaded: an edit of the whole video that you change with timeline operations.
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 video to edit. The video is chosen here and only here: to edit another video, create another project. The language, the caption style and the shape are the choices the edit starts with; once it is prepared, change them on the timeline with apply_timeline_operations (POST /v1/projects/{projectId}/timeline/operations). Nothing is prepared and no credits are used until you call prepare_timeline (POST /v1/projects/{projectId}/timeline/prepare). Send an Idempotency-Key: a retry without one creates a second project.
Example use
A podcast producer uploads a full episode, creates a Vibe Editing project from it with the language and caption style of the show, and checks the price with estimate_analysis (POST /v1/projects/{projectId}/analyze/estimate) before the edit is prepared.
Endpoint
POST https://api.gpt-video.com/v1/projects/vibe-edit- Scope
clips:write. - Rate limit 60 requests a minute for each key and 120 for each account, shared by every
writecall. See rate limits. Idempotency-Keyis 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. |
Body
| Name | Type | Required | Description |
|---|---|---|---|
sourceAssetId | string | Required | The video to edit: the asset id that complete_upload (POST /v1/uploads/{uploadId}/complete) returned. It cannot be changed later. Matches ^ast_n?[0-9a-f]{20}$. |
language | string | Required | The language spoken in the video: en, it, or auto to detect it. One of auto, it, en. |
captionStyle | string | Required | The caption style the edit starts with. get_setting_options (GET /v1/reference/setting-options) lists every style with what it looks like. One of bold-karaoke, word-pop, boxed, outline. |
aspect | string | Required | The shape the edit starts with: source keeps the shape of the video, 9:16 is vertical, 1:1 square, 16:9 wide. One of source, 9:16, 1:1, 16:9. |
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 Vibe Editing project.
# 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/vibe-edit" \
-H "Authorization: Bearer $GPT_VIDEO_API_KEY" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-H "Content-Type: application/json" \
-d '{
"sourceAssetId": "ast_8e21b0d94c6f4a17b358",
"language": "en",
"captionStyle": "word-pop",
"aspect": "9:16",
"title": "Product demo, edited"
}'// 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/vibe-edit", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.GPT_VIDEO_API_KEY}`,
"Idempotency-Key": idempotencyKey,
"Content-Type": "application/json",
},
body: JSON.stringify({
"sourceAssetId": "ast_8e21b0d94c6f4a17b358",
"language": "en",
"captionStyle": "word-pop",
"aspect": "9:16",
"title": "Product demo, edited"
}),
});
const data = await res.json();
console.log(data);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/vibe-edit",
headers={
"Authorization": f"Bearer {os.environ['GPT_VIDEO_API_KEY']}",
"Idempotency-Key": idempotency_key,
},
json={
"sourceAssetId": "ast_8e21b0d94c6f4a17b358",
"language": "en",
"captionStyle": "word-pop",
"aspect": "9:16",
"title": "Product demo, edited"
},
)
data = r.json()
print(data){
"method": "tools/call",
"params": {
"name": "create_vibe_edit_project",
"arguments": {
"sourceAssetId": "ast_8e21b0d94c6f4a17b358",
"language": "en",
"captionStyle": "word-pop",
"aspect": "9:16",
"title": "Product demo, edited"
}
}
}Response
A successful call answers HTTP 201. Returns a Project object 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
{
"project": {
"id": "prj_6e2b9d14a7c3f0581e2d",
"kind": "vibeedit",
"title": "Product demo, edited",
"status": "draft",
"sourceAssetId": "ast_8e21b0d94c6f4a17b358",
"compositionId": null,
"settings": null,
"missingSettings": [],
"splitScreen": null,
"streamer": null,
"redditStory": null,
"fakeText": null,
"vibeEdit": {
"sourceAssetId": "ast_8e21b0d94c6f4a17b358",
"language": "en",
"captionStyle": "word-pop",
"aspect": "9:16",
"vocabulary": null,
"prepared": false
},
"errorMessage": 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. |
More examples
The video does not exist
POST /v1/projects/vibe-edit
{
"sourceAssetId": "ast_0b1e5c9d3a7f40628e2d",
"language": "en",
"captionStyle": "word-pop",
"aspect": "9:16"
}{
"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_4b7e1a9c3d0f52e86a1b7c04"
}
}Errors
| Code | Status | When | What to do |
|---|---|---|---|
NOT_FOUND | 404 | The video does not exist | Change the request, then send it again. |
CONFLICT | 409 | The asset is not a video, its file was deleted to free up storage, its size and length cannot be read, or it is longer than Vibe Editing accepts. The message says which one. | Read the resource again, then decide. Do not send the same request again. |
Every call can also answer these errors. VALIDATION_FAILED, UNKNOWN_PARAMETER, UNAUTHORIZED, SCOPE_MISSING, IDEMPOTENCY_IN_PROGRESS, PAYLOAD_TOO_LARGE, IDEMPOTENCY_KEY_REUSED, RATE_LIMITED, INTERNAL, UNAVAILABLE.
Cost
No credits are used. The preparation is charged when you call prepare_timeline (POST /v1/projects/{projectId}/timeline/prepare).
Related operations
complete_upload(POST /v1/uploads/{uploadId}/complete) Complete an uploadprepare_timeline(POST /v1/projects/{projectId}/timeline/prepare) Prepare an editestimate_analysis(POST /v1/projects/{projectId}/analyze/estimate) Estimate the credits of an analysis or a buildget_project(GET /v1/projects/{projectId}) Get a project
Questions
Can I change the video of the project later?
No. The video of a Vibe Editing project is chosen when it is created and never changes. Create another project to edit another video, or add more videos to a prepared edit with add_timeline_clip (POST /v1/projects/{projectId}/timeline/clips).
Why is my video refused?
The video must be a video file you uploaded, with a size and a length that can be read, and short enough for the editor; the answer says which of these failed. A longer video can go through Auto Clip, which picks the best moments of it.