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

# Submit edit

> Run an atomic edit (edit → frame regen → video sync) on an existing video

## Overview

`POST /v1/edits` mutates an existing video with a natural-language instruction. A single call kicks off three internal phases — Claude rewrites the planner, frame images are regenerated for any changed scenes, and the video clips are restitched against the new planner. The endpoint returns immediately with an `edit_id` you poll with [`GET /v1/edits/{edit_id}`](/api-reference/poll-edit).

<Info>
  This endpoint is asynchronous. The response confirms the edit was accepted (`queued` or `processing`); the full edit pipeline runs in the background
  and typically takes 30 seconds to a few minutes depending on how many scenes changed.
</Info>

## Endpoint

```
POST {API_BASE}/v1/edits
```

<Note>Your `API_BASE` is shown in the Developer tab of your dashboard. The default production base is `https://api.knowlify.com`.</Note>

## Authentication

Send your key in the `X-API-Key` header. The key must own the target `video_uuid` — either as the user who created the video, or as an active member of the organization that owns it.

```bash theme={null}
X-API-Key: kn_<64 hex chars>
```

See [Authentication](/api-reference/authentication) for full details on key issuance, rotation, and JWT alternatives.

## Headers

<ParamField header="X-API-Key" type="string" required>
  Your `kn_<64 hex>` API key.
</ParamField>

<ParamField header="Idempotency-Key" type="string">
  Opaque caller-chosen string. Replaying the same key within 24 hours returns the original response without creating a second edit. Use a fresh value
  per logical operation (e.g., a UUID); reuse it on retries of the same operation.
</ParamField>

## Request body

<ParamField body="video_uuid" type="string" required>
  UUID of the video to edit. Must be a `corporate_engine_v2` row owned by the API key's user or organization.
</ParamField>

<ParamField body="edit_request" type="string" required>
  Natural-language instruction describing the change. 1–5000 characters. Examples: `"Make scene 2 more dramatic with closer camera angles"`, `"Replace
    	the bird in scene 3 with a hawk"`, `"Add a new closing scene that summarizes the topic"`.
</ParamField>

<ParamField body="reference_image_urls" type="string[]">
  Up to 5 reference image URLs to condition the edit. Each URL must:

  * Use the `https://` scheme.
  * Resolve to a public IP (private, loopback, link-local, and reserved ranges are rejected).
  * Return `Content-Type: image/*` and `Content-Length` ≤ 5 MB on a `HEAD` request.

  One redirect hop is followed; the redirect target is re-validated end-to-end.
</ParamField>

<ParamField body="aspect_ratio" type="string">
  Optional aspect ratio override. One of `"16:9"` or `"9:16"`. Defaults to the target video's existing aspect ratio.
</ParamField>

<ParamField body="skip_content_safety" type="boolean" default="false">
  Reserved for admin keys. Standard API keys must leave this `false`; the content-safety scan always runs for external callers.
</ParamField>

## Response

A successful call returns HTTP `202 Accepted` once the edit has been recorded and dispatched to the worker.

<ResponseField name="edit_id" type="string">
  Stable identifier for this edit. Pass to [`GET /v1/edits/{edit_id}`](/api-reference/poll-edit) to track progress and to [`POST /v1/edits/{edit_id}
    	/revert`](/api-reference/revert-edit) to undo.
</ResponseField>

<ResponseField name="video_uuid" type="string">
  Echo of the `video_uuid` you submitted.
</ResponseField>

<ResponseField name="status" type="string">
  Initial status. `"processing"` when the worker can start immediately, `"queued"` when another edit on the same video is already in flight.
</ResponseField>

<ResponseField name="queue_position" type="integer">
  1-indexed position behind any other active or queued edits on the same video. `0` when this edit is the first to run.
</ResponseField>

### Error codes

| Code  | Cause                                                                                                                                                                                                     |
| ----- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400` | The `edit_request` was rejected by the content-safety scan.                                                                                                                                               |
| `401` | `X-API-Key` header missing or invalid.                                                                                                                                                                    |
| `402` | Insufficient credits for the worst-case cost of the edit. See [Credit cost](#credit-cost). `detail` is `"INSUFFICIENT_CREDITS: edit could cost up to <N>, have <M>"`.                                     |
| `403` | The API key does not own `video_uuid`.                                                                                                                                                                    |
| `404` | `video_uuid` does not exist.                                                                                                                                                                              |
| `409` | The target video is still being built for the first time (e.g., `pipeline_status` is `lowlevel` or `generating_frames`) or has errored. Wait for the initial render to finish.                            |
| `415` | A `reference_image_urls[]` entry returned the wrong Content-Type, missing Content-Length, or exceeded 5 MB.                                                                                               |
| `422` | Pydantic validation failure (missing field, oversized `edit_request`, `reference_image_urls[]` longer than 5) or a reference URL failed the SSRF host check (non-https, private IP, second-hop redirect). |
| `429` | Per-caller rate limit exceeded. See [Rate Limits](/api-reference/rate-limits).                                                                                                                            |

All non-2xx responses share the standard envelope: `{ "detail": "<message>" }`. See [Errors](/api-reference/errors) for the full reference.

## Code examples

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST "https://api.knowlify.com/v1/edits" \
    -H "X-API-Key: kn_YOUR_KEY_HERE" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440099" \
    -d '{
      "video_uuid": "550e8400-e29b-41d4-a716-446655440000",
      "edit_request": "Make scene 2 more dramatic with closer camera angles.",
      "reference_image_urls": [
        "https://cdn.knowlify.com/assets/style-ref.jpg"
      ]
    }'
  ```

  ```javascript JavaScript theme={null}
  const res = await fetch("https://api.knowlify.com/v1/edits", {
  	method: "POST",
  	headers: {
  		"X-API-Key": "kn_YOUR_KEY_HERE",
  		"Content-Type": "application/json",
  		"Idempotency-Key": crypto.randomUUID(),
  	},
  	body: JSON.stringify({
  		video_uuid: "550e8400-e29b-41d4-a716-446655440000",
  		edit_request: "Make scene 2 more dramatic with closer camera angles.",
  		reference_image_urls: ["https://cdn.knowlify.com/assets/style-ref.jpg"],
  	}),
  });

  const { edit_id, status } = await res.json();
  console.log(`${edit_id} → ${status}`);
  ```

  ```python Python theme={null}
  import requests
  import uuid

  resp = requests.post(
      "https://api.knowlify.com/v1/edits",
      headers={
          "X-API-Key": "kn_YOUR_KEY_HERE",
          "Idempotency-Key": str(uuid.uuid4()),
      },
      json={
          "video_uuid": "550e8400-e29b-41d4-a716-446655440000",
          "edit_request": "Make scene 2 more dramatic with closer camera angles.",
          "reference_image_urls": [
              "https://cdn.knowlify.com/assets/style-ref.jpg",
          ],
      },
      timeout=30,
  )
  resp.raise_for_status()
  body = resp.json()
  print(body["edit_id"], body["status"])
  ```
</CodeGroup>

#### Example success response

```json theme={null}
{
	"edit_id": "e942890a-7776-4029-82d6-ce733d0a2cb2",
	"video_uuid": "550e8400-e29b-41d4-a716-446655440000",
	"status": "processing",
	"queue_position": 0
}
```

## How edits work

Internally a single `POST /v1/edits` runs three phases in sequence inside one worker job:

1. **Edit** — Claude reads the current planner and rewrites the affected scenes based on `edit_request`. Sets `pipeline_status` to `edit_complete`.
2. **Frame regen** — first/last frame images are regenerated for any changed or added scenes. Sets `pipeline_status` to `edit_pending_apply`.
3. **Sync** — video clips for changed and added scenes are regenerated and stitched into the existing timeline, producing a new `link` and `timestamps`. Sets `pipeline_status` back to `complete`.

<Note>
  If the target video has never been fully rendered (no `timestamps` / `link` exists), phase 3 is **skipped automatically**. The edit lives in the
  planner and the next render will reflect it. `result.video_synced` on the poll response will be `false` in that case.
</Note>

If Claude returns a clarifying question instead of an edit (a plan-mode response), the endpoint auto-confirms with `"yes, proceed"` up to twice. If a third call still returns a question, the edit terminates with `status: "error"` and `error_message: "edit request was ambiguous; rephrase and retry."`.

## Credit cost

Phase 1 (the Claude edit itself) is free. Phases 2 and 3 are billed **per scene** that successfully regenerates, after the underlying provider call returns:

| Phase                 | Cost per scene | Charged for                                                                                  |
| --------------------- | -------------- | -------------------------------------------------------------------------------------------- |
| Frame regeneration    | **4 credits**  | Each Veo scene whose first/last frame is regenerated in phase 2.                             |
| Video clip (Veo/grok) | **25 credits** | Each Veo scene whose video clip is regenerated in phase 3 (same rate as the initial render). |
| Video clip (Remotion) | **9 credits**  | Each Remotion scene that is re-rendered in phase 3.                                          |

You only pay for scenes that actually regenerate. A scene that fails partway through is never billed, and worker-level retries of the same edit are deduplicated so you cannot be charged twice for the same scene.

### Pre-flight check

Before accepting the edit, `POST /v1/edits` computes a worst-case upper bound assuming every scene in the planner is touched:

```
upper_bound = (4 + 25) × veo_scenes  +  9 × remotion_scenes
```

If your account balance is below this number the request is rejected with `402 Insufficient Credits` and no work is started. The actual charge after the edit completes is almost always lower — typically only one or a few scenes change.

<Note>
  If the target video has never been fully rendered, phase 3 is skipped (see the note in [How edits work](#how-edits-work)) and you are only charged for phase 2.
</Note>

## Next step

The response gives you an `edit_id`. Use it with [`GET /v1/edits/{edit_id}`](/api-reference/poll-edit) to track the edit through `queued` → `processing` → `applying` → `complete`, and [`POST /v1/edits/{edit_id}/revert`](/api-reference/revert-edit) to undo a completed edit.

## Limits & errors

* 10 requests / 60 seconds per caller. See [Rate Limits](/api-reference/rate-limits).
* One video per request; concurrent edits on the same video are queued serially via `pending_edits[]`.
* Full status code reference: [Errors](/api-reference/errors).
