Restore a revision of an edit
restore_timelinePOST/v1/projects/{projectId}/timeline/restoreBeta
Brings an edit back to a revision listed by list_timeline_revisions (GET /v1/projects/{projectId}/timeline/revisions), as a new revision.
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 to go back to and the revision you read last as expectedRevision. The edit becomes what it was at that revision, and the change is a new revision, so it can be undone the same way. A restore never costs credits: a revision that would bring back a video that is not paid for is refused, and the video can be added again with add_timeline_clip (POST /v1/projects/{projectId}/timeline/clips).
Example use
After an automatic change went wrong, a script sends the revision it found in the list and the revision it read last. The edit goes back as a new revision, so the person can undo the restore the same way.
Endpoint
POST https://api.gpt-video.com/v1/projects/{projectId}/timeline/restore- 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
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 |
|---|---|---|---|
revision | integer | Required | The revision to go back to, as list_timeline_revisions (GET /v1/projects/{projectId}/timeline/revisions) lists it. At least 0. |
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. |
Send only the fields listed here. Any other field is refused with UNKNOWN_PARAMETER.
Request
Restore a revision of an edit.
# 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/restore" \
-H "Authorization: Bearer $GPT_VIDEO_API_KEY" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-H "Content-Type: application/json" \
-d '{
"revision": 2,
"expectedRevision": 3
}'// 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/restore", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.GPT_VIDEO_API_KEY}`,
"Idempotency-Key": idempotencyKey,
"Content-Type": "application/json",
},
body: JSON.stringify({
"revision": 2,
"expectedRevision": 3
}),
});
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/restore",
headers={
"Authorization": f"Bearer {os.environ['GPT_VIDEO_API_KEY']}",
"Idempotency-Key": idempotency_key,
},
json={
"revision": 2,
"expectedRevision": 3
},
)
data = r.json()
print(data){
"method": "tools/call",
"params": {
"name": "restore_timeline",
"arguments": {
"projectId": "prj_b9c1f9c7873dcef51a57",
"revision": 2,
"expectedRevision": 3
}
}
}Response
A successful call answers HTTP 200. Returns a Timeline object in timeline.
Fields to check
| Name | Type | Description |
|---|---|---|
timeline.id | string | The edit id. It starts with tml_. |
timeline.segments[].id | string | The segment id. It starts with seg_. |
timeline.texts[].id | string | The text id. It starts with ovl_. |
timeline.broll[].id | string | The b-roll id. It starts with brl_. |
Response example
{
"timeline": {
"id": "tml_bbe061171a8229f8daed",
"projectId": "prj_b9c1f9c7873dcef51a57",
"compositionId": "cmp_cfbdd85eaaff301fa997",
"revision": 4,
"durationSec": 60,
"aspect": "9:16",
"reframe": "off",
"captions": {
"enabled": false,
"style": "bold-karaoke"
},
"music": null,
"fadeInSec": 0,
"fadeOutSec": 0,
"clips": [
{
"assetId": "ast_3f9a1c7e5b2d4086a1c4",
"title": "Interview with a founder",
"durationSec": 1800,
"state": "ready"
}
],
"segments": [
{
"id": "seg_7279fbb508f4a22e65a4",
"assetId": "ast_3f9a1c7e5b2d4086a1c4",
"sourceStartSec": 120,
"sourceEndSec": 150,
"zoom": 1,
"muted": false,
"volume": 1,
"outputStartSec": 0,
"outputEndSec": 30
},
{
"id": "seg_a9dcee59c1fb9edb6751",
"assetId": "ast_3f9a1c7e5b2d4086a1c4",
"sourceStartSec": 300,
"sourceEndSec": 330,
"zoom": 1,
"muted": false,
"volume": 1,
"outputStartSec": 30,
"outputEndSec": 60
}
],
"texts": [
{
"id": "ovl_49f5239b9cd73496225d",
"text": "Paid before the product",
"preset": "headline",
"outputStartSec": 0,
"durationSec": 3,
"xPercent": null,
"yPercent": null
}
],
"broll": [
{
"id": "brl_f44d18e008ec4009bf10",
"assetId": "ast_8ac5d8b97546145cdbf6",
"outputStartSec": 12,
"durationSec": 4,
"fit": "cover"
}
],
"diagnostics": []
},
"compositionId": "cmp_cfbdd85eaaff301fa997"
}All response fields
| Name | Type | Required | Description |
|---|---|---|---|
timeline | Timeline object | Required | Described in Timeline object. |
compositionId | string | Required | The composition the edit compiles to, to export with create_export (POST /v1/compositions/{compositionId}/exports). |
More examples
A revision that is not kept
POST /v1/projects/prj_b9c1f9c7873dcef51a57/timeline/restore
{
"revision": 0,
"expectedRevision": 1
}{
"error": {
"code": "NOT_FOUND",
"message": "Revision 0 of this edit is not kept. Call list_timeline_revisions to see the ones you can restore.",
"hint": "Check the id. An id that belongs to another account is not found either.",
"requestId": "req_4f8c1a6e3b0d92e75a4c1f08"
}
}Errors
| Code | Status | When | What to do |
|---|---|---|---|
NOT_FOUND | 404 | A revision that is not kept | Change the request, then send it again. |
CONFLICT | 409 | The project is not a Vibe Editing project, its edit is not prepared, it changed after expectedRevision, or the revision would bring back a video that is not paid for. The message says which one. | Read the resource again, then decide. Do not send the same request again. |
NOT_READY | 409 | See the error reference. | Wait for Retry-After, then 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. A restore that would bring back a video that is not paid for is refused.
Related operations
list_timeline_revisions(GET /v1/projects/{projectId}/timeline/revisions) List the revisions of an editget_timeline(GET /v1/projects/{projectId}/timeline) Get an editadd_timeline_clip(POST /v1/projects/{projectId}/timeline/clips) Add a video to an editapply_timeline_operations(POST /v1/projects/{projectId}/timeline/operations) Change an edit
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 my restore refused with a video that is not paid for?
The revision plays a video whose credits were given back, or that this edit never paid for. A restore never charges credits, so it is refused: add the video again with add_timeline_clip (POST /v1/projects/{projectId}/timeline/clips), which says the price.