Send a message to the Editor Assistant
create_assistant_turnPOST/v1/projects/{projectId}/assistant/turnsBeta
Asks the Editor Assistant to change a project in plain words, and returns the turn that does 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 this to ask the Editor Assistant to edit a project, for example "cut the pauses longer than one second" or "make the captions bolder". The call answers 202 at once with the turn, queued, and a Location to follow; read it with get_assistant_turn (GET /v1/assistant/turns/{turnId}) (with ?wait= to hold the request) until it is done, then read its reply and its actions. The conversation is the same one the project shows in the GPT-Video app, where this message is marked as sent through the API. A turn through the API never spends credits or exports by itself: when the request needs an analysis, a build, a new clip in an edit or an export, the reply names the API call to make, which you send with its own Idempotency-Key. It runs at the Fast level, and it does not save notes. While a turn is running on the project, another turn sent through the API is running in the account, or a message written in the GPT-Video app is waiting for its answer, the call answers NOT_READY: wait for it, then send your message again. When the Editor Assistant is paused, the call answers UNAVAILABLE and uses no turn of the day. An Editor Assistant turn can change the project, so the key needs the clips:write scope as well as spend. Always send an Idempotency-Key, the same one on every retry: a retry of the same request returns the same turn and never uses a second turn of the day.
Example use
A support tool lets a client type a request such as "make the captions bolder" for their project. The tool sends it here with an Idempotency-Key, then follows the turn with get_assistant_turn (GET /v1/assistant/turns/{turnId}) until the reply arrives.
Endpoint
POST https://api.gpt-video.com/v1/projects/{projectId}/assistant/turns- Scope
spend. The key needs all ofspendandclips:write. - 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 id of the project the Editor Assistant works on. 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. |
Body
| Name | Type | Required | Description |
|---|---|---|---|
message | string | Required | What you want done, in plain words, up to 4000 characters. From 1 to 4000 characters. |
Send only the fields listed here. Any other field is refused with UNKNOWN_PARAMETER.
Request
Ask for a change to 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/assistant/turns" \
-H "Authorization: Bearer $GPT_VIDEO_API_KEY" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-H "Content-Type: application/json" \
-d '{
"message": "Cut the pauses longer than one second."
}'// 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/assistant/turns", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.GPT_VIDEO_API_KEY}`,
"Idempotency-Key": idempotencyKey,
"Content-Type": "application/json",
},
body: JSON.stringify({
"message": "Cut the pauses longer than one second."
}),
});
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/assistant/turns",
headers={
"Authorization": f"Bearer {os.environ['GPT_VIDEO_API_KEY']}",
"Idempotency-Key": idempotency_key,
},
json={
"message": "Cut the pauses longer than one second."
},
)
data = r.json()
print(data){
"method": "tools/call",
"params": {
"name": "create_assistant_turn",
"arguments": {
"projectId": "prj_b9c1f9c7873dcef51a57",
"message": "Cut the pauses longer than one second.",
"confirm": true,
"idempotencyKey": "REPLACE-WITH-A-NEW-UUID-FOR-THIS-CALL"
}
}
}Response
A successful call answers HTTP 202. Returns a Assistant turn object in turn.
Fields to check
| Name | Type | Description |
|---|---|---|
turn.id | string | The turn id. It starts with agm_. |
turn.status | string | queued and running are still working; done, failed and cancelled are final. |
turn.actions[].id | string | The action id. It starts with tc_. |
turn.actions[].status | string | |
turn.jobId | string or null | The job that runs the turn; null once it is gone. |
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
Location: https://api.gpt-video.com/v1/assistant/turns/agm_7c2e9a4f1b6d38e05a2c
{
"turn": {
"id": "agm_7c2e9a4f1b6d38e05a2c",
"projectId": "prj_b9c1f9c7873dcef51a57",
"status": "queued",
"message": "Cut the pauses longer than one second.",
"reply": null,
"outcome": null,
"question": null,
"options": [],
"actions": [],
"jobId": "job_3e8b1d6f9a2c40e75b1d",
"errorMessage": null,
"createdAt": "2026-09-01T11:00:00.000Z",
"finishedAt": null
},
"paidWith": "plan"
}All response fields
| Name | Type | Required | Description |
|---|---|---|---|
turn | Assistant turn object | Required | Described in Assistant turn object. |
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
A turn is still running on the project
POST /v1/projects/prj_b9c1f9c7873dcef51a57/assistant/turns
{
"message": "And make the text overlays shorter."
}Retry-After: 10
{
"error": {
"code": "NOT_READY",
"message": "A turn of the Editor Assistant is already running on this project. Wait for it with get_assistant_turn, then send your message again.",
"hint": "Wait the number of seconds in the Retry-After header, then send the same request again.",
"requestId": "req_8b3e1f7a2c6d4e9a0f5b2c71"
}
}Another turn sent through the API is still running
POST /v1/projects/prj_b9c1f9c7873dcef51a57/assistant/turns
{
"message": "Cut the pauses longer than one second."
}Retry-After: 10
{
"error": {
"code": "NOT_READY",
"message": "Another Editor Assistant turn started through the API is still running. Wait for it to finish, then send your message again.",
"hint": "Wait the number of seconds in the Retry-After header, then send the same request again.",
"requestId": "req_2d7f4a9c1e6b3d8f0a5c7e42"
}
}A message written in the app is waiting
POST /v1/projects/prj_b9c1f9c7873dcef51a57/assistant/turns
{
"message": "Cut the pauses longer than one second."
}Retry-After: 10
{
"error": {
"code": "NOT_READY",
"message": "A message written in the GPT-Video app is waiting for the Editor Assistant to answer it. Wait for that answer, then send your message again.",
"hint": "Wait the number of seconds in the Retry-After header, then send the same request again.",
"requestId": "req_fbc30cb25d9763c7cba21957"
}
}The Editor Assistant is paused
POST /v1/projects/prj_b9c1f9c7873dcef51a57/assistant/turns
{
"message": "Cut the pauses longer than one second."
}Retry-After: 60
{
"error": {
"code": "UNAVAILABLE",
"message": "GPT-Video cannot take new work right now. Try again in a few minutes.",
"hint": "Retry later.",
"requestId": "req_977388db082429a89d2b5f25"
}
}A project that does not exist
POST /v1/projects/prj_9f0d3b7e1c5a4d28b6f3/assistant/turns
{
"message": "Cut the pauses longer than one second."
}{
"error": {
"code": "NOT_FOUND",
"message": "Project prj_9f0d3b7e1c5a4d28b6f3 not found",
"hint": "Check the id. An id that belongs to another account is not found either.",
"requestId": "req_6f1c9e3a7b2d4f8e0c5a9b17"
}
}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 | A project that does not exist | Change the request, then send it again. |
NOT_READY | 409 | A turn is still running on the project | Wait for Retry-After, then send the same request 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
Uses one Editor Assistant turn of the plan's daily allowance, which resets at 00:00 UTC; the Creator and Studio plans allow part of their turns through the API. When the API bonus pays, it also uses two bonus credits. Nothing is returned if the turn fails. A turn through the API never spends credits or exports on its own: the actions that would, answer with the API call to use.
- Paid by the API bonus, it uses 2 bonus credits.
- When the API bonus pays, an account can take 20 Editor Assistant turns a day.
- See API pricing for who pays first.
Related operations
get_assistant_turn(GET /v1/assistant/turns/{turnId}) Get an Editor Assistant turnlist_assistant_turns(GET /v1/projects/{projectId}/assistant/turns) List the Editor Assistant turns of a projectundo_assistant_action(POST /v1/assistant/turns/{turnId}/actions/{actionId}/undo) Undo an action of the Editor Assistantget_job(GET /v1/jobs/{jobId}) Get a job
Questions
Why does the reply tell me to call another operation?
A turn through the API never spends credits or exports by itself. When your request needs an analysis, a build, a new clip in an edit or an export, the reply names the call that does it, for example create_export (POST /v1/compositions/{compositionId}/exports). Send that call with its own Idempotency-Key.
What happens if my request times out and I send it again?
Send it again with the same Idempotency-Key. You get the same turn back and no second turn of the day is used. A new key sends a new message.
Can I send a message while the Editor Assistant is working?
No. While a turn runs on the project the call answers NOT_READY, and nothing is used. Wait for the turn with get_assistant_turn (GET /v1/assistant/turns/{turnId}), then send your message again.
Why does my key get SCOPE_MISSING?
A turn spends a turn of the day and changes the project, so the key needs both the spend and the clips:write scopes.