> ## Documentation Index
> Fetch the complete documentation index at: https://docs.genviral.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Apply Editor Ops

> Cut a video project with an atomic batch of edit operations.

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\_case `type`.

| `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](/api-reference/get-editor-transcript); get clip ids and timeline positions
from [Get Editor Project](/api-reference/get-editor-project).

## Concurrency

Pass `expected_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

<ParamField path="projectId" type="string (UUID)" required>
  The project id.
</ParamField>

## Body Parameters

<ParamField body="ops" type="object[]" required>
  1 to 50 operations, applied in order and atomically.
</ParamField>

<ParamField body="expected_updated_at" type="string">
  The `updated_at` you last read (ISO 8601). The batch is refused with `editor_conflict` if the
  project changed since.
</ParamField>

## Response

Returns `200` with the updated project, in the same shape as
[Get Editor Project](/api-reference/get-editor-project). Any earlier render is no longer current
for the new cut. The edit shows live in the Genviral web editor at the project's `editor_url`.

<RequestExample>
  ```bash cURL theme={null}
  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 }
          ]
        }
      ]
    }'
  ```

  ```bash cURL (delete dead air) theme={null}
  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 }]
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json Response theme={null}
  {
    "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,
      "editor_url": "https://www.genviral.io/editor/b1d4e8f2-3a5c-4d7e-8f90-1a2b3c4d5e6f?workspace=personal"
    }
  }
  ```
</ResponseExample>

## Error Responses

* `400 invalid_json` - request body is not valid JSON
* `400 invalid_request` - `Idempotency-Key` header is missing or longer than 255 characters
* `401` - authentication failed (missing/invalid/revoked token)
* `404 project_not_found` - the project does not exist in the key scope
* `409 editor_locked` - Vira is editing this project right now; retry shortly
* `409 editor_conflict` - the project changed since `expected_updated_at`; read it again
* `409 idempotency_key_reused` - this `Idempotency-Key` was already used with a different body
* `409 request_in_progress` - an identical request with this key is still being processed; retry shortly
* `422 invalid_payload` - body failed validation (see `issues`)
* `422 invalid_op` - an op is not valid for the project; the message says how to correct it
* `422 clip_not_found` - an op names a `clip_id` that is not in the project
* `422 asset_not_found` - an op names an `asset_id` that is not in the project
* `422 out_of_range` - a time or range falls outside the clip, asset, or timeline
* `422 invalid_document` - the resulting cut would not be a valid project


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.