Prepare an edit
prepare_timelinePOST/v1/projects/{projectId}/timeline/prepareBeta
Starts the preparation of a Vibe Editing project: the video is transcribed and analyzed once, and the edit is built from 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 once on a new Vibe Editing project; estimate_analysis (POST /v1/projects/{projectId}/analyze/estimate) gives the price first. The answer is 202 with the job of the preparation: follow it with get_job (GET /v1/jobs/{jobId}), then read the edit with get_timeline (GET /v1/projects/{projectId}/timeline). A prepared edit is never prepared or charged again: calling it again answers CONFLICT. Retrying with the same Idempotency-Key never pays twice, and if the preparation is already running you get its job back and nothing is charged.
Example use
An agency that edits the weekly vlog of a client checks the price with estimate_analysis (POST /v1/projects/{projectId}/analyze/estimate), then calls prepare_timeline (POST /v1/projects/{projectId}/timeline/prepare) with an Idempotency-Key. It follows the job with get_job (GET /v1/jobs/{jobId}) and reads the finished edit with get_timeline (GET /v1/projects/{projectId}/timeline).
Endpoint
POST https://api.gpt-video.com/v1/projects/{projectId}/timeline/prepare- Scope
spend. - Rate limit 10 requests a minute for each key and 20 for each account, shared by every
spendcall. See rate limits. Idempotency-Keyis required.- It starts work that runs on its own. The answer comes back before the work is done.
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 | Required | This operation spends, so the key is required. Create it once, before the request, and send the same key on every retry. From 1 to 255 visible characters, such as a UUID. See idempotency. |
Request
Prepare 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_37573e451e0ff7f863c8/timeline/prepare" \
-H "Authorization: Bearer $GPT_VIDEO_API_KEY" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY"// 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_37573e451e0ff7f863c8/timeline/prepare", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.GPT_VIDEO_API_KEY}`,
"Idempotency-Key": idempotencyKey,
},
});
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_37573e451e0ff7f863c8/timeline/prepare",
headers={
"Authorization": f"Bearer {os.environ['GPT_VIDEO_API_KEY']}",
"Idempotency-Key": idempotency_key,
},
)
data = r.json()
print(data){
"method": "tools/call",
"params": {
"name": "prepare_timeline",
"arguments": {
"projectId": "prj_37573e451e0ff7f863c8",
"confirm": true,
"idempotencyKey": "REPLACE-WITH-A-NEW-UUID-FOR-THIS-CALL"
}
}
}Response
A successful call answers HTTP 202. Returns a Job object in job.
Fields to check
| Name | Type | Description |
|---|---|---|
job.id | string | The job id. It starts with job_. |
job.status | string | queued and running are still working; done, failed and cancelled are final. |
creditsCharged | integer | The credits this request was charged: the price of the work it started, 0 when the same work was already running and its job came back. |
reused | boolean | true when the same work was already running on the project and this is its job, charged to nobody again. |
paidWith | string or null | Who paid for this request: plan is the credits or the daily allowance of the account's plan, api_bonus is the API bonus. null when this request paid nothing, because the same work was already running. A retry with the same Idempotency-Key says who paid the first time. |
Response example
{
"job": {
"id": "job_d41c7e0a95b3f26c8e17",
"kind": "analysis",
"status": "queued",
"progress": 0,
"errorMessage": null,
"projectId": "prj_37573e451e0ff7f863c8",
"compositionId": null,
"exportId": null,
"assetId": null,
"toolRunId": null,
"turnId": null,
"createdAt": "2026-09-01T11:10:00.000Z",
"startedAt": null,
"finishedAt": null
},
"creditsCharged": 2,
"reused": false,
"paidWith": "plan"
}All response fields
| Name | Type | Required | Description |
|---|---|---|---|
job | Job object | Required | Described in Job object. |
creditsCharged | integer | Required | The credits this request was charged: the price of the work it started, 0 when the same work was already running and its job came back. At least 0. |
reused | boolean | Required | true when the same work was already running on the project and this is its job, charged to nobody again. |
paidWith | string or null | Required | Who paid for this request: plan is the credits or the daily allowance of the account's plan, api_bonus is the API bonus. null when this request paid nothing, because the same work was already running. A retry with the same Idempotency-Key says who paid the first time. One of plan, api_bonus. |
More examples
The edit is already prepared
POST /v1/projects/prj_b9c1f9c7873dcef51a57/timeline/prepare
{
"error": {
"code": "CONFLICT",
"message": "This edit is already prepared, and it is never prepared or charged again. Read it with get_timeline.",
"requestId": "req_9c2f6a0e4b8d13a75f0c2e61"
}
}A project of another kind
POST /v1/projects/prj_7c2e9a4f1b3d4e8096a5/timeline/prepare
{
"error": {
"code": "CONFLICT",
"message": "This project is not a Vibe Editing project. Timeline operations work on Vibe Editing projects only.",
"requestId": "req_1d5a8e2c6f0b47c93e1a5d82"
}
}Errors
| Code | Status | When | What to do |
|---|---|---|---|
INSUFFICIENT_CREDITS | 402 | See the error reference. | Stop and tell the user. |
PLAN_REQUIRED | 402 | See the error reference. | Stop and tell the user. |
NOT_FOUND | 404 | See the error reference. | Change the request, then send it again. |
CONFLICT | 409 | The edit is already prepared | Read the resource again, then decide. Do not send the same request again. |
SOURCE_MISSING | 422 | See the error reference. | Change the request, then send it again. |
DAILY_LIMIT_REACHED | 429 | See the error reference. | Stop until the limit resets at 00:00 UTC. |
Every call can also answer these errors. VALIDATION_FAILED, UNKNOWN_PARAMETER, IDEMPOTENCY_KEY_REQUIRED, UNAUTHORIZED, SCOPE_MISSING, IDEMPOTENCY_IN_PROGRESS, PAYLOAD_TOO_LARGE, IDEMPOTENCY_KEY_REUSED, RATE_LIMITED, INTERNAL, UNAVAILABLE.
Cost
One credit per started minute of the source video, charged when the preparation starts and not returned if it fails. A prepared edit is never prepared or charged again.
- Paid by the API bonus, it uses the same number of bonus credits.
- When the API bonus pays, 1 of its jobs runs at a time. The next one waits with status
queued. - See API pricing for who pays first.
Related operations
create_vibe_edit_project(POST /v1/projects/vibe-edit) Create a Vibe Editing projectestimate_analysis(POST /v1/projects/{projectId}/analyze/estimate) Estimate the credits of an analysis or a buildget_job(GET /v1/jobs/{jobId}) Get a jobget_timeline(GET /v1/projects/{projectId}/timeline) Get an edit
Questions
What happens if my request times out and I send it again?
Send it again with the same Idempotency-Key: the retry never pays twice, and it answers what the first try paid and who paid it. A new key on a prepared edit answers CONFLICT, and nothing is charged.
Can I prepare the edit again to start over?
No. An edit is prepared once. To go back to an earlier state, restore a revision with restore_timeline (POST /v1/projects/{projectId}/timeline/restore); to start from scratch, create another Vibe Editing project.