# Apply commands

`POST https://api.vidmoat.com/v1/projects/{id}/commands`

- Scope: `projects.write`
- Cost: plan-gated ops

Apply an ordered list of edit commands. This is the only way to change a timeline, and it is the same reducer the editor calls: anything you can do by hand you can do here.

> **Note.** Each command succeeds or fails on its own, and the batch is saved when at least one succeeds: check `ok` and `results[].ok`, not only the HTTP status. A plan-gated op (scripting without a paid plan) is dropped and listed in `blocked` while the rest applies. The call is not idempotent: a retried batch appends the clips again.

### Path parameters

- `id` (string, required): The project id.

### Body

- `commands` (array of objects, required): One to 200 commands, applied in order. Each is an object with a string `op` and that op's parameters, exactly as listed by `GET /v1/schema/commands`.
- `dryRun` (boolean, optional, default `false`): Run the batch without saving and return what would happen.

## Response

Returns `200`.

### Response fields

- `projectId` (string): The project.
- `ok` (boolean): `true` only when every command succeeded.
- `dryRun` (boolean): Whether anything was saved.
- `results` (array): One entry per command: `{ op, ok, error?, data? }`. A failed command is reported here, not as an HTTP error.
- `verification` (object): What happened: `accepted` (count), `failed` (`[{ op, error }]`), `blocked`, `layout` warnings, and `outcome` (`executed`, `needs-repair` or `blocked`).
- `blocked` (array of strings): Plan-gated features that were dropped.
- `lint` (array): Layout warnings: `{ severity: "warn" | "note", time, message }`.
- `suggestedPreviewTimes` (array of numbers): Timestamps worth looking at with the preview endpoint before you render.
- `timeline` (object): `{ clipCount, durationSec }` after the batch.
- `hint` (string): What to check next.

> **Tip: With a test key.** Test keys edit for real.

## Errors

Besides the errors any request can get ([authentication](https://developer.vidmoat.com/developer/docs/authentication#when-a-request-is-refused), [rate limits](https://developer.vidmoat.com/developer/docs/rate-limits)):

| Status | Code | When |
| --- | --- | --- |
| 404 | [`not_found`](https://developer.vidmoat.com/developer/docs/errors#not_found) | No such project, or it is not yours. The two are indistinguishable on purpose. |
| 400 | [`invalid_request`](https://developer.vidmoat.com/developer/docs/errors#invalid_request) | `commands` is missing, empty, longer than 200, or an entry has no string `op`. |
| 409 | [`invalid_request`](https://developer.vidmoat.com/developer/docs/errors#invalid_request) | The project changed while the batch ran (`conflictCode: "stale_rev"`, with `rev`) or is locked by an expert handoff (`help_handoff_locked`). Re-read the project and retry. |

## Request examples

```bash curl
curl -X POST https://api.vidmoat.com/v1/projects/$ID/commands \
  -H "Authorization: Bearer $VIDMOAT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"commands":[
        {"op":"addTextClip","text":"Hello","start":0,"duration":3}
      ]}'
```

```js Node
const ID = 'YOUR_PROJECT_ID';

const res = await fetch(`https://api.vidmoat.com/v1/projects/${ID}/commands`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.VIDMOAT_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    commands: [
      {
        op: 'addTextClip',
        text: 'Hello',
        start: 0,
        duration: 3,
      },
    ],
  }),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error?.message ?? `HTTP ${res.status}`);
console.log(data);
```

```python Python
import os
import requests

ID = "YOUR_PROJECT_ID"

res = requests.post(
    f"https://api.vidmoat.com/v1/projects/{ID}/commands",
    headers={"Authorization": f"Bearer {os.environ['VIDMOAT_KEY']}"},
    json={
      "commands": [
        {
          "op": "addTextClip",
          "text": "Hello",
          "start": 0,
          "duration": 3,
        },
      ],
    },
    timeout=60,
)
res.raise_for_status()
print(res.json())
```

## Example response

```json
{
  "projectId": "cm1abc",
  "ok": true,
  "dryRun": false,
  "results": [{ "op": "addTextClip", "ok": true, "data": { "clipId": "c7" } }],
  "verification": { "version": 1, "accepted": 1, "failed": [], "blocked": [], "layout": [], "outcome": "executed" },
  "blocked": [],
  "lint": [],
  "suggestedPreviewTimes": [1.5],
  "timeline": { "clipCount": 1, "durationSec": 3 },
  "hint": "Applied. Verify with GET /v1/projects/cm1abc/preview?at=1.5"
}
```

---

Source: https://developer.vidmoat.com/developer/docs/api/projects/apply-commands
Previous: [Delete a project](https://developer.vidmoat.com/developer/docs/api/projects/delete-project.md)
Next: [Validate commands](https://developer.vidmoat.com/developer/docs/api/projects/validate-commands.md)
All documentation: https://developer.vidmoat.com/llms-full.txt
