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

# Clips

> Cut a long video into short, captioned, vertical clips — as a job you submit, poll and collect.

Clipping is asynchronous: a submit is answered `202` with a `media_job` you poll (or receive by [webhook](/docs/api/webhooks)). When it completes, every clip is a published [file](/docs/api/files) in your library, scored for virality by the same pipeline the [`clip_video`](/docs/capabilities/tools) tool uses in a session.

## The media job object

<ResponseField name="id" type="string">Unique id (e.g. `med_01H...`).</ResponseField>
<ResponseField name="object" type="string">Always `media_job`.</ResponseField>
<ResponseField name="kind" type="string">Always `clip` here.</ResponseField>
<ResponseField name="status" type="string">`queued` → `processing` → `completed` | `failed`.</ResponseField>
<ResponseField name="model" type="string | null">Always `null` for clipping; the pipeline is not model-selectable.</ResponseField>
<ResponseField name="input" type="object">The request as accepted, defaults filled in. Fields you did not send are `null`.</ResponseField>
<ResponseField name="result" type="object | null">`{ clips: [...] }` once `completed`, otherwise `null`. Each clip: `file_id`, `title`, `start_seconds`, `end_seconds`, `duration_seconds`, and `virality` — `total`, `shareability`, `hook_strength`, `story_quality`, `emotional_impact`, each an integer 0–100.</ResponseField>
<ResponseField name="error" type="object | null">`{ code, message }` once `failed`, otherwise `null`.</ResponseField>
<ResponseField name="cost_micro_usd" type="integer | null">What the job actually cost, settled on completion. `null` until then and for a failed job — a failure is not billed.</ResponseField>
<ResponseField name="session_id" type="string | null">Always `null` for a job submitted here; set when the `clip_video` tool queued it.</ResponseField>
<ResponseField name="created_at" type="string">Submission timestamp.</ResponseField>
<ResponseField name="completed_at" type="string | null">When the job reached a terminal status.</ResponseField>

## Submit a clipping job

`POST /v1/media/clips` — scope `agents:write`. An [`Idempotency-Key`](/docs/api/overview#idempotency) header is **required** — a submit without one is refused with `validation_failed` — so a retried submit replays the job instead of queueing (and billing) a second one; the same key with a different body is `idempotency_conflict`.

<ParamField body="video_url" type="string" required>A public `http(s)` URL of the source video. Private, loopback and link-local hosts are refused with `validation_failed`.</ParamField>
<ParamField body="aspect_ratio" type="string" default="9:16">`9:16` (reframed to the speaker) or `16:9` (kept as shot).</ParamField>
<ParamField body="remove_silence" type="boolean" default="false">Tighten pauses inside each clip.</ParamField>
<ParamField body="caption_preset" type="string" default="none">`none`, `clean`, `bold` or `karaoke`. `none` burns no captions.</ParamField>
<ParamField body="language" type="string">BCP-47 hint for transcription (e.g. `de`, `pt-BR`). Omit to detect.</ParamField>
<ParamField body="min_seconds" type="integer" default="15">Shortest clip to keep, 5–180.</ParamField>
<ParamField body="max_seconds" type="integer" default="60">Longest clip to keep, 5–180; must not be below `min_seconds`.</ParamField>
<ParamField body="title" type="string">A title for clips the scorer does not name itself.</ParamField>

<CodeGroup>
  ```bash cURL theme={"system"}
  curl -fsSL https://api.vetta.sh/v1/media/clips \
    -H "authorization: Bearer sk_live_..." \
    -H "idempotency-key: 6f1c...ab" \
    -H "content-type: application/json" \
    -d '{ "video_url": "https://cdn.example.com/keynote.mp4", "caption_preset": "bold", "language": "en", "max_seconds": 45 }'
  ```
</CodeGroup>

<ResponseExample>
  ```json 202 Accepted theme={"system"}
  {
    "id": "med_01H9HH...",
    "object": "media_job",
    "kind": "clip",
    "status": "queued",
    "model": null,
    "input": {
      "video_url": "https://cdn.example.com/keynote.mp4",
      "aspect_ratio": "9:16",
      "remove_silence": false,
      "caption_preset": "bold",
      "language": "en",
      "min_seconds": 15,
      "max_seconds": 45,
      "title": null
    },
    "result": null,
    "error": null,
    "cost_micro_usd": null,
    "session_id": null,
    "created_at": "2026-09-06T18:00:00.000Z",
    "completed_at": null
  }
  ```
</ResponseExample>

A submit is refused with `402 insufficient_credits` when the organization's prepaid balance is empty. There is no per-tenant concurrency cap on this route — your balance bounds you, not a turn — unlike the `clip_video` tool, which keeps one active job per session.

## Retrieve a job

`GET /v1/media/clips/{id}` — scope `agents:read`

<ResponseExample>
  ```json 200 OK theme={"system"}
  {
    "id": "med_01H9HH...",
    "object": "media_job",
    "kind": "clip",
    "status": "completed",
    "model": null,
    "input": { "video_url": "https://cdn.example.com/keynote.mp4", "aspect_ratio": "9:16", "remove_silence": false, "caption_preset": "bold", "language": "en", "min_seconds": 15, "max_seconds": 45, "title": null },
    "result": {
      "clips": [
        {
          "file_id": "fil_01H9HJ...",
          "title": "The one metric that changed our roadmap",
          "start_seconds": 812.4,
          "end_seconds": 851.0,
          "duration_seconds": 38.6,
          "virality": { "total": 87, "shareability": 82, "hook_strength": 91, "story_quality": 85, "emotional_impact": 90 }
        }
      ]
    },
    "error": null,
    "cost_micro_usd": 412000,
    "session_id": null,
    "created_at": "2026-09-06T18:00:00.000Z",
    "completed_at": "2026-09-06T18:06:31.000Z"
  }
  ```
</ResponseExample>

A failed job reads back with `status: "failed"`, `result: null`, `cost_micro_usd: null` and `error: { "code": "provider_error", "message": "..." }`.

## List jobs

`GET /v1/media/clips` — scope `agents:read`

<ParamField query="status" type="string">`queued`, `processing`, `completed` or `failed`.</ParamField>
<ParamField query="limit" type="number">Page size, 1–100.</ParamField>
<ParamField query="after" type="string">The `next_cursor` of the previous page.</ParamField>

Newest first, in the standard [list envelope](/docs/api/pagination). Only jobs submitted through this API are listed; the clips an agent's `clip_video` tool cuts inside a session reach that session as [files](/docs/api/files), not as jobs here.

## Webhooks

Subscribe an [endpoint](/docs/api/webhooks) to `media.job.queued`, `media.job.completed` (`data.file_ids` names the clips) or `media.job.failed` (`data.code`) to be told rather than poll. Every envelope carries `data.job_id` and `data.kind: "clip"`.

## Pricing

A job is billed what it actually cost — input minutes, transcript words, scoring tokens, cut clips — once it finishes, and `cost_micro_usd` is that figure. See [Pricing](/docs/platform/pricing#media-generation--per-finished-job-media-component).
