curl --request POST \
--url https://www.genviral.io/api/partner/v1/editor/projects/b1d4e8f2-3a5c-4d7e-8f90-1a2b3c4d5e6f/ops \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: 5e9a2c71-0b4d-4f88-b6a3-7c1d9e0f2a34' \
--data '{
"expected_updated_at": "2026-10-02T12:00:00.000Z",
"ops": [
{
"type": "set_sequence",
"segments": [
{ "asset_id": "asset_1", "source_start_ms": 1200, "source_end_ms": 9800 },
{ "asset_id": "asset_1", "source_start_ms": 14500, "source_end_ms": 31000 }
]
}
]
}'
curl --request POST \
--url https://www.genviral.io/api/partner/v1/editor/projects/b1d4e8f2-3a5c-4d7e-8f90-1a2b3c4d5e6f/ops \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: 9a1c3e55-7d20-4b18-8e6f-0c4b2a9d7f10' \
--data '{
"ops": [{ "type": "delete_range", "start_ms": 4200, "end_ms": 5600 }]
}'
{
"ok": true,
"code": 200,
"message": "Editor ops applied",
"data": {
"project_id": "b1d4e8f2-3a5c-4d7e-8f90-1a2b3c4d5e6f",
"title": "Launch cut",
"aspect_ratio": "9:16",
"duration_ms": 25100,
"updated_at": "2026-10-02T12:03:00.000Z",
"assets": [],
"clips": [],
"render": null
}
}
Video Editor
Apply Editor Ops
Cut a video project with an atomic batch of edit operations.
POST
/
api
/
partner
/
v1
/
editor
/
projects
/
{projectId}
/
ops
curl --request POST \
--url https://www.genviral.io/api/partner/v1/editor/projects/b1d4e8f2-3a5c-4d7e-8f90-1a2b3c4d5e6f/ops \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: 5e9a2c71-0b4d-4f88-b6a3-7c1d9e0f2a34' \
--data '{
"expected_updated_at": "2026-10-02T12:00:00.000Z",
"ops": [
{
"type": "set_sequence",
"segments": [
{ "asset_id": "asset_1", "source_start_ms": 1200, "source_end_ms": 9800 },
{ "asset_id": "asset_1", "source_start_ms": 14500, "source_end_ms": 31000 }
]
}
]
}'
curl --request POST \
--url https://www.genviral.io/api/partner/v1/editor/projects/b1d4e8f2-3a5c-4d7e-8f90-1a2b3c4d5e6f/ops \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: 9a1c3e55-7d20-4b18-8e6f-0c4b2a9d7f10' \
--data '{
"ops": [{ "type": "delete_range", "start_ms": 4200, "end_ms": 5600 }]
}'
{
"ok": true,
"code": 200,
"message": "Editor ops applied",
"data": {
"project_id": "b1d4e8f2-3a5c-4d7e-8f90-1a2b3c4d5e6f",
"title": "Launch cut",
"aspect_ratio": "9:16",
"duration_ms": 25100,
"updated_at": "2026-10-02T12:03:00.000Z",
"assets": [],
"clips": [],
"render": null
}
}
Applies a batch of cutting operations to a project and returns the updated project. The batch is
atomic: either every op applies or none does, so a refused batch leaves the project untouched.
A batch holds 1 to 50 ops, applied in order, each against the result of the one before it.
Operations
Ops are objects with a snake_casetype.
type | Fields | Time units | Use it to |
|---|---|---|---|
set_sequence | segments[]: asset_id, source_start_ms, source_end_ms, optional clip_id | Source ms | Replace the base video lane with these source ranges played back to back. Best for “keep only these parts”. |
delete_range | start_ms, end_ms | Timeline ms | Remove a range across all clips and close the gap. Best for dead air. |
split_clip | clip_id, at_ms | Timeline ms | Split a clip in two. |
trim_clip | clip_id, edge (start or end), position_ms | Timeline ms | Move one edge of a clip, trimming its source. |
reorder_clip | clip_id, start_ms | Timeline ms | Move a clip to start at a new position. |
set_sequence takes at most 200 segments. Get source ranges for speech from
Get Editor Transcript; get clip ids and timeline positions
from Get Editor Project.
Concurrency
Passexpected_updated_at with the updated_at you last read. If the project changed since, the
batch is refused with 409 editor_conflict: read the project again, rebuild your ops against the
new state, and retry with a new Idempotency-Key.
While Vira (the Genviral assistant) is editing the same project, edits are refused with
409 editor_locked. Wait a few seconds and retry.
Idempotency
Idempotency-Key is required and is scoped to the API key (or MCP grant) that sends it.
- Retrying with the same key and the same body returns the original result and never applies the batch twice.
- Reusing a key with a different body returns
409 idempotency_key_reused. - A refused batch (for example
422 clip_not_found) does not consume its key.
Path Parameters
string (UUID)
required
The project id.
Body Parameters
object[]
required
1 to 50 operations, applied in order and atomically.
string
The
updated_at you last read (ISO 8601). The batch is refused with editor_conflict if the
project changed since.Response
Returns200 with the updated project, in the same shape as
Get Editor Project. Any earlier render is no longer current
for the new cut.
curl --request POST \
--url https://www.genviral.io/api/partner/v1/editor/projects/b1d4e8f2-3a5c-4d7e-8f90-1a2b3c4d5e6f/ops \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: 5e9a2c71-0b4d-4f88-b6a3-7c1d9e0f2a34' \
--data '{
"expected_updated_at": "2026-10-02T12:00:00.000Z",
"ops": [
{
"type": "set_sequence",
"segments": [
{ "asset_id": "asset_1", "source_start_ms": 1200, "source_end_ms": 9800 },
{ "asset_id": "asset_1", "source_start_ms": 14500, "source_end_ms": 31000 }
]
}
]
}'
curl --request POST \
--url https://www.genviral.io/api/partner/v1/editor/projects/b1d4e8f2-3a5c-4d7e-8f90-1a2b3c4d5e6f/ops \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: 9a1c3e55-7d20-4b18-8e6f-0c4b2a9d7f10' \
--data '{
"ops": [{ "type": "delete_range", "start_ms": 4200, "end_ms": 5600 }]
}'
{
"ok": true,
"code": 200,
"message": "Editor ops applied",
"data": {
"project_id": "b1d4e8f2-3a5c-4d7e-8f90-1a2b3c4d5e6f",
"title": "Launch cut",
"aspect_ratio": "9:16",
"duration_ms": 25100,
"updated_at": "2026-10-02T12:03:00.000Z",
"assets": [],
"clips": [],
"render": null
}
}
Error Responses
400 invalid_json- request body is not valid JSON400 invalid_request-Idempotency-Keyheader is missing or longer than 255 characters401- authentication failed (missing/invalid/revoked token)404 project_not_found- the project does not exist in the key scope409 editor_locked- Vira is editing this project right now; retry shortly409 editor_conflict- the project changed sinceexpected_updated_at; read it again409 idempotency_key_reused- thisIdempotency-Keywas already used with a different body409 request_in_progress- an identical request with this key is still being processed; retry shortly422 invalid_payload- body failed validation (seeissues)422 invalid_op- an op is not valid for the project; the message says how to correct it422 clip_not_found- an op names aclip_idthat is not in the project422 asset_not_found- an op names anasset_idthat is not in the project422 out_of_range- a time or range falls outside the clip, asset, or timeline422 invalid_document- the resulting cut would not be a valid project
