Applies a list of operations to an edit in one change: cuts, order, transitions, zoom, sound, captions, texts, b-roll, music and shape.
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
Send the revision you read with get_timeline (GET /v1/projects/{projectId}/timeline) and from 1 to 50 operations: they apply in order, as one change and one new revision, or not at all. Segments, texts and b-roll are named by the ids get_timeline shows; times of segments are seconds of the source video, times of texts and b-roll are seconds of the video that comes out. Adding a video is not an operation here, because it costs credits: use add_timeline_clip (POST /v1/projects/{projectId}/timeline/clips). The answer is the edit after the change, with warnings that say what was adjusted.
Example use
An editing tool lets a person remove a long pause and add a title text. It sends both as one list of operations with the revision it read, then shows the warnings of the answer next to the new edit.
Endpoint
Code
POST https://api.gpt-video.com/v1/projects/{projectId}/timeline/operations
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.
Idempotency-Key is optional.
Parameters
Path
Name
Type
Required
Description
projectId
string
Required
The project id. It starts with prj_. Matches ^prj_[0-9a-f]{20}$.
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
expectedRevision
integer
Required
The revision of the edit you read last, from get_timeline (GET /v1/projects/{projectId}/timeline) or from the answer of your last change. If the edit changed since then, nothing is written and the answer is CONFLICT. At least 0.
operations
array of objects
Required
The operations, from 1 to 50, applied in this order. The op field says which one each is. From 1 to 50 items.
Each item of operations is one of 21 variants, chosen by op. Send exactly one variant for each item.
op = "split_segment"
Name
Type
Required
Description
operations[].op
string
Required
Always split_segment.
operations[].segmentId
string
Required
The segment id. It starts with seg_. Matches ^seg_[0-9a-f]{20}$.
operations[].atSourceSec
number
Required
Where to cut, in seconds of the source video. More than 0.
op = "trim_segment"
Name
Type
Required
Description
operations[].op
string
Required
Always trim_segment.
operations[].segmentId
string
Required
The segment id. It starts with seg_. Matches ^seg_[0-9a-f]{20}$.
operations[].sourceStartSec
number
Optional
The new start, in seconds of the source video. At least 0.
operations[].sourceEndSec
number
Optional
The new end, in seconds of the source video. More than 0.
op = "remove_segments"
Name
Type
Required
Description
operations[].op
string
Required
Always remove_segments.
operations[].segmentIds
array of strings
Required
The segment ids, from 1 to 200. From 1 to 200 items.
op = "move_segment"
Name
Type
Required
Description
operations[].op
string
Required
Always move_segment.
operations[].segmentId
string
Required
The segment id. It starts with seg_. Matches ^seg_[0-9a-f]{20}$.
operations[].beforeSegmentId
string or null
Required
The segment to move it in front of; null moves it to the end. Matches ^seg_[0-9a-f]{20}$.
op = "set_transition"
Name
Type
Required
Description
operations[].op
string
Required
Always set_transition.
operations[].segmentId
string
Required
The segment id. It starts with seg_. Matches ^seg_[0-9a-f]{20}$.
operations[].kind
string
Required
One of dip, dissolve.
operations[].sec
number
Required
How long the transition into this segment lasts, from 0 to 3 seconds; 0 removes it. From 0 to 3.
op = "set_video_fade"
Name
Type
Required
Description
operations[].op
string
Required
Always set_video_fade.
operations[].edge
string
Required
One of start, end.
operations[].sec
number
Required
How long the fade lasts, from 0 to 3 seconds; 0 removes it. From 0 to 3.
op = "set_zoom"
Name
Type
Required
Description
operations[].op
string
Required
Always set_zoom.
operations[].segmentIds
array of strings
Required
The segment ids, from 1 to 200. From 1 to 200 items.
operations[].factor
number
Required
1 removes the zoom; up to 1.5. From 1 to 1.5.
operations[].mode
string
Required
One of static, push.
op = "set_segment_audio"
Name
Type
Required
Description
operations[].op
string
Required
Always set_segment_audio.
operations[].segmentIds
array of strings
Required
The segment ids, from 1 to 200. From 1 to 200 items.
operations[].volume
number
Optional
From 0 to 2.
operations[].muted
boolean
Optional
op = "remove_silences"
Name
Type
Required
Description
operations[].op
string
Required
Always remove_silences.
operations[].aggressiveness
string
Required
One of soft, standard, aggressive.
op = "remove_fillers"
Name
Type
Required
Description
operations[].op
string
Required
Always remove_fillers.
op = "set_captions"
Name
Type
Required
Description
operations[].op
string
Required
Always set_captions.
operations[].enabled
boolean
Optional
operations[].style
string
Optional
One of bold-karaoke, word-pop, boxed, outline.
operations[].options
object
Optional
The caption options to change. A field you leave out stays as it is.
operations[].options.fontFamily
string
Optional
The typeface of the captions. One of Montserrat, Anton, System.
operations[].options.fontSize
integer
Optional
The size of the caption text, from 20 to 200. From 20 to 200.
operations[].options.yPercent
number
Optional
Where the captions sit, from 0 at the top to 100 at the bottom. From 0 to 100.
operations[].options.color
string
Optional
The color of the words, as #RRGGBB. Matches ^#[0-9A-Fa-f]{6}$.
operations[].options.highlightColor
string
Optional
The color of the word being spoken, as #RRGGBB. Matches ^#[0-9A-Fa-f]{6}$.
operations[].options.maxWordsPerGroup
integer
Optional
How many words are on screen at once, at most, from 1 to 12. From 1 to 12.
operations[].options.maxCharsPerGroup
integer
Optional
How many characters are on screen at once, at most, from 4 to 80. From 4 to 80.
operations[].options.uppercase
boolean
Optional
true shows the captions in capital letters.
operations[].options.maxGapSec
number
Optional
The longest pause, in seconds, inside one group of words on screen, from 0.1 to 5. From 0.1 to 5.
op = "replace_caption_word"
Name
Type
Required
Description
operations[].op
string
Required
Always replace_caption_word.
operations[].from
string
Required
From 1 to 200 characters.
operations[].to
string
Required
From 1 to 200 characters.
operations[].occurrence
integer
Optional
Which match to replace, from 1; leave it out to replace every match. At least 1.
op = "add_text"
Name
Type
Required
Description
operations[].op
string
Required
Always add_text.
operations[].text
string
Required
From 1 to 200 characters.
operations[].preset
string
Required
One of headline, hook, caption-note.
operations[].outputStartSec
number
Required
A time in the video that comes out, in seconds. At least 0.
operations[].durationSec
number
Required
A length in seconds. More than 0.
operations[].xPercent
number
Optional
The horizontal position, from 0 to 100. From 0 to 100.
operations[].yPercent
number
Optional
The vertical position, from 0 to 100. From 0 to 100.
op = "update_text"
Name
Type
Required
Description
operations[].op
string
Required
Always update_text.
operations[].textId
string
Required
The text id. It starts with ovl_. Matches ^ovl_[0-9a-f]{20}$.
operations[].text
string
Optional
From 1 to 200 characters.
operations[].preset
string
Optional
One of headline, hook, caption-note.
operations[].outputStartSec
number
Optional
A time in the video that comes out, in seconds. At least 0.
operations[].durationSec
number
Optional
A length in seconds. More than 0.
operations[].xPercent
number or null
Optional
The horizontal position, from 0 to 100. From 0 to 100.
operations[].yPercent
number or null
Optional
The vertical position, from 0 to 100. From 0 to 100.
op = "remove_text"
Name
Type
Required
Description
operations[].op
string
Required
Always remove_text.
operations[].textId
string
Required
The text id. It starts with ovl_. Matches ^ovl_[0-9a-f]{20}$.
op = "add_broll"
Name
Type
Required
Description
operations[].op
string
Required
Always add_broll.
operations[].assetId
string
Required
Matches ^ast_n?[0-9a-f]{20}$.
operations[].outputStartSec
number
Required
A time in the video that comes out, in seconds. At least 0.
operations[].durationSec
number
Required
A length in seconds. More than 0.
operations[].fit
string
Required
One of cover, contain.
op = "update_broll"
Name
Type
Required
Description
operations[].op
string
Required
Always update_broll.
operations[].brollId
string
Required
The b-roll id. It starts with brl_. Matches ^brl_[0-9a-f]{20}$.
operations[].outputStartSec
number
Optional
A time in the video that comes out, in seconds. At least 0.
operations[].durationSec
number
Optional
A length in seconds. More than 0.
operations[].fit
string
Optional
One of cover, contain.
op = "remove_broll"
Name
Type
Required
Description
operations[].op
string
Required
Always remove_broll.
operations[].brollId
string
Required
The b-roll id. It starts with brl_. Matches ^brl_[0-9a-f]{20}$.
op = "set_music"
Name
Type
Required
Description
operations[].op
string
Required
Always set_music.
operations[].assetId
string or null
Required
The music file; null removes the music. Matches ^ast_n?[0-9a-f]{20}$.
operations[].volume
number
Optional
From 0 to 0.5.
op = "set_aspect"
Name
Type
Required
Description
operations[].op
string
Required
Always set_aspect.
operations[].aspect
string
Required
One of source, 9:16, 1:1, 16:9.
op = "set_reframe"
Name
Type
Required
Description
operations[].op
string
Required
Always set_reframe.
operations[].mode
string
Required
One of off, face, center.
Send only the fields listed here. Any other field is refused with UNKNOWN_PARAMETER.
Request
Cut a segment in two and remove another.
Request example
# 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/prj_b9c1f9c7873dcef51a57/timeline/operations" \
-H"Authorization: Bearer $GPT_VIDEO_API_KEY" \
-H"Idempotency-Key: $IDEMPOTENCY_KEY" \
-H"Content-Type: application/json" \
-d'{
"expectedRevision": 1,
"operations": [
{
"op": "split_segment",
"segmentId": "seg_7279fbb508f4a22e65a4",
"atSourceSec": 135
},
{
"op": "remove_segments",
"segmentIds": [
"seg_a9dcee59c1fb9edb6751"
]
}
]
}'
// 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/prj_b9c1f9c7873dcef51a57/timeline/operations", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.GPT_VIDEO_API_KEY}`,
"Idempotency-Key": idempotencyKey,
"Content-Type": "application/json",
},
body: JSON.stringify({
"expectedRevision": 1,
"operations": [
{
"op": "split_segment",
"segmentId": "seg_7279fbb508f4a22e65a4",
"atSourceSec": 135
},
{
"op": "remove_segments",
"segmentIds": [
"seg_a9dcee59c1fb9edb6751"
]
}
]
}),
});
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/prj_b9c1f9c7873dcef51a57/timeline/operations",
headers={
"Authorization": f"Bearer {os.environ['GPT_VIDEO_API_KEY']}",
"Idempotency-Key": idempotency_key,
},
json={
"expectedRevision": 1,
"operations": [
{
"op": "split_segment",
"segmentId": "seg_7279fbb508f4a22e65a4",
"atSourceSec": 135
},
{
"op": "remove_segments",
"segmentIds": [
"seg_a9dcee59c1fb9edb6751"
]
}
]
},
)
data = r.json()
print(data)
{
"error": {
"code": "CONFLICT",
"message": "This edit changed since revision 0; its revision is now 1.",
"hint": "Read it again with get_timeline, then send the operations with its current revision.",
"requestId": "req_8e0b4d2a6c1f93e57a0d4b29"
}
}
A segment that is not in the edit
POST /v1/projects/prj_b9c1f9c7873dcef51a57/timeline/operations
{
"error": {
"code": "NOT_FOUND",
"message": "Segment seg_77e5dc932c642be5375c not found",
"hint": "Check the id. An id that belongs to another account is not found either.",
"requestId": "req_2c7a9e1d4f0b36e85c2a7d91"
}
}
No credits are used. Adding a video costs credits, and has its own call: add_timeline_clip (POST /v1/projects/{projectId}/timeline/clips).
Related operations
get_timeline (GET /v1/projects/{projectId}/timeline) Get an edit
add_timeline_clip (POST /v1/projects/{projectId}/timeline/clips) Add a video to an edit
list_timeline_revisions (GET /v1/projects/{projectId}/timeline/revisions) List the revisions of an edit
restore_timeline (POST /v1/projects/{projectId}/timeline/restore) Restore a revision of an edit
create_export (POST /v1/compositions/{compositionId}/exports) Export a composition
Questions
Why do I get CONFLICT with a revision number?
The edit changed after you read it: another call, the Editor Assistant or someone in the GPT-Video app wrote a new revision. Read it again with get_timeline (GET /v1/projects/{projectId}/timeline), then send your change with the revision it shows.
Why is the op "add_clip" refused?
Adding a video costs credits, so it has its own call with its own Idempotency-Key: add_timeline_clip (POST /v1/projects/{projectId}/timeline/clips). Every operation of this call is free, and an op this call does not know is refused with VALIDATION_FAILED.
What happens if one operation of the list fails?
Nothing is written: the operations apply all together or not at all, and the answer says which one failed. Fix it and send the list again with the same expectedRevision.