> ## 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.

# media

> Image and video generation, the model catalogue, stock photo search, video clipping and the audio routes — client.media.

Two generators, a catalogue, and — further down — `stock`, a free search over a licensed photo library, `clips`, the video-clipping job, and `audio`, transcription, speech and speech-to-speech. An image renders in one call and comes back as [files](/docs/sdk/files); a video is a **media job** you read back until it finishes. The catalogue belongs to the provider and changes without a release of ours, so it is a **search**, not a constant.

## images.generate

```ts theme={"system"}
client.media.images.generate(body: {
  prompt: string;
  model?: string;
  image_urls?: string[];
  n?: number;
  aspect_ratio?: string;
  seed?: number;
}): Promise<ImageGeneration>
```

[`POST /v1/media/images`](/docs/api/images), synchronous. Every output is a file:

```ts theme={"system"}
{ object: "image_generation", model: string, files: FileObject[], cost_micro_usd: number }
```

`model` left out picks the cheapest image model in the catalogue. `image_urls` edits or restyles public images instead of rendering from nothing. `n` is 1–4, default 1. The client sends the required [`Idempotency-Key`](/docs/api/overview#idempotency) for you.

```ts theme={"system"}
const out = await client.media.images.generate({ prompt: "a paper kite over a grey harbour", n: 2 });
const png = await client.files.download(out.files[0].id);
```

## videos.generate

```ts theme={"system"}
client.media.videos.generate(body: {
  model: string;
  prompt: string;
  image_urls?: string[];
  seconds?: number;
  aspect_ratio?: string;
  seed?: number;
}): Promise<MediaJob>
```

[`POST /v1/media/videos`](/docs/api/video) — answers at once with the job in `queued`. `model` is required: video models publish no price, so there is no default to pick. `seconds` is 1–60.

## get

```ts theme={"system"}
client.media.get(id: string): Promise<MediaJob>
```

`GET /v1/media/{id}`. Any `med_` of yours, a clipping job's included. Poll until `status` is `completed` or `failed`; a completed video job's `result.files[].file_id` is the rendered video, a completed clipping job's `result.clips` are its clips (see [`clips.get`](#clipsget)).

```ts theme={"system"}
let job = await client.media.videos.generate({ model: "example/reel", prompt: "waves at dusk", seconds: 4 });
while (job.status === "queued" || job.status === "processing") {
  await new Promise((r) => setTimeout(r, 5_000));
  job = await client.media.get(job.id);
}
if (job.status === "failed") throw new Error(job.error?.message);
```

## list

```ts theme={"system"}
client.media.list(query?: {
  kind?: "video" | "clip" | "transcription";
  status?: "queued" | "processing" | "completed" | "failed";
  session_id?: string;
  limit?: number;
  after?: string;
}): Promise<Page<MediaJob>>
```

`GET /v1/media`, newest first. `session_id` narrows to what one session's agent submitted; API-submitted jobs carry `session_id: null`.

## listModels

```ts theme={"system"}
client.media.listModels(query: {
  kind: "image" | "video" | "stt" | "tts" | "s2s";
  search?: string;
  limit?: number;
  after?: string;
}): Promise<Page<MediaModel>>
```

`GET /v1/media/models`. `kind` is required: each is its own capability with its own catalogue. `stt`, `tts` and `s2s` are the [audio](/docs/api/audio#the-catalogues) models — the managed alias for the direction first, then pinned models.

Each entry:

<ResponseField name="id" type="string">The model id you pass as `model` above, or to [`generate_image` / `generate_video`](/docs/capabilities/tools#generation-tools), or pin on an agent as `tools.configs.<tool>.config.models`.</ResponseField>
<ResponseField name="name" type="string">The model's display name.</ResponseField>
<ResponseField name="kind" type="&#x22;image&#x22; | &#x22;video&#x22; | &#x22;stt&#x22; | &#x22;tts&#x22; | &#x22;s2s&#x22;">Which capability it serves.</ResponseField>
<ResponseField name="description" type="string">What the model is good at.</ResponseField>

```ts theme={"system"}
const video = await client.media.listModels({ kind: "video", search: "reel" });
await client.agents.update(agentId, {
  tools: {
    default_config: { permission: "allow" },
    configs: {
      generate_video: { enabled: true, permission: "allow", config: { models: [video.data[0].id] } },
    },
  },
});
```

No price is on the object. A generation is billed what it actually cost, once it has finished — see [Pricing](/docs/platform/pricing#media-generation--per-finished-job-media-component).

## stock.search

```ts theme={"system"}
client.media.stock.search(query: {
  query: string;
  count?: number;                                   // 1–30, default 10
  orientation?: "landscape" | "portrait" | "square";
  color?: string;                                   // a name or a hex value
  size?: "small" | "medium" | "large";            // which size `url` points at
}): Promise<{ data: StockPhoto[] }>
```

[`GET /v1/media/stock`](/docs/api/images). Not a cursor page: the library pages by number, and `count` is the whole page.

Each `StockPhoto`:

<ResponseField name="id" type="string">The library's own id. Opaque, unprefixed, not a Vetta resource.</ResponseField>
<ResponseField name="width" type="number">Pixel width of the original.</ResponseField>
<ResponseField name="height" type="number">Pixel height of the original.</ResponseField>
<ResponseField name="url" type="string">A direct image URL at the requested `size`.</ResponseField>
<ResponseField name="preview_url" type="string">A small thumbnail.</ResponseField>
<ResponseField name="alt" type="string | null">A one-line description of the photo.</ResponseField>
<ResponseField name="photographer" type="string | null">Who took it — show this credit wherever the photo is used.</ResponseField>
<ResponseField name="attribution_url" type="string | null">The page to link the credit to.</ResponseField>
<ResponseField name="dominant_color" type="string | null">The average colour as a hex value.</ResponseField>

```ts theme={"system"}
const { data } = await client.media.stock.search({ query: "office workspace", count: 3, orientation: "landscape" });
const hero = data[0];
// <img src={hero.url} alt={hero.alt ?? ""} />  Photo by <a href={hero.attribution_url}>{hero.photographer}</a>
```

A result is a URL, not a file; to hold the bytes as a `fil_`, import `url` through [`client.files`](/docs/sdk/files). The search is free and is booked as a `search` line at `$0`. A deploy with no stock photo library configured rejects with `feature_not_configured`.

## clips.create

```ts theme={"system"}
client.media.clips.create(body: ClipCreate): Promise<MediaJob>
```

`POST /v1/media/clips`. Submits a clipping job and returns it `queued`; the clips arrive as [files](/docs/sdk/files) when it completes. `ClipCreate` is the [request body](/docs/api/clips#submit-a-clipping-job): `video_url` (public `http(s)` only), and optional `aspect_ratio`, `remove_silence`, `caption_preset` (`none | clean | bold | karaoke`), `language`, `min_seconds`, `max_seconds`, `title`. An `Idempotency-Key` is sent for you, so a retry replays rather than re-queues.

```ts theme={"system"}
const job = await client.media.clips.create({
  video_url: "https://cdn.example.com/keynote.mp4",
  caption_preset: "bold",
  language: "en",
  max_seconds: 45,
});
```

## clips.get

```ts theme={"system"}
client.media.clips.get(id: string): Promise<MediaJob>
```

`GET /v1/media/clips/{id}`. The job as it stands; once `status` is `completed`, `result.clips` holds each clip's `file_id`, `title`, `start_seconds`, `end_seconds`, `duration_seconds` and `virality` scores, and `cost_micro_usd` is what it cost.

```ts theme={"system"}
let job = await client.media.clips.get(id);
while (job.status === "queued" || job.status === "processing") {
  await new Promise((r) => setTimeout(r, 15_000));
  job = await client.media.clips.get(id);
}
if (job.result && "clips" in job.result) {
  for (const clip of job.result.clips) console.log(clip.title, clip.virality.total, clip.file_id);
}
```

Subscribe a [webhook](/docs/sdk/webhooks) to `media.job.completed` / `media.job.failed` to be told instead of polling.

## clips.list

```ts theme={"system"}
client.media.clips.list(query?: {
  status?: "queued" | "processing" | "completed" | "failed";
  limit?: number;
  after?: string;
}): Promise<Page<MediaJob>>
```

`GET /v1/media/clips`. Newest first. Only jobs submitted through the API are listed; what an agent's `clip_video` tool cuts inside a session lands in that session as files.

## audio.transcribe

```ts theme={"system"}
client.media.audio.transcribe(body: {
  file_id?: string;      // exactly one of file_id / url
  url?: string;
  model?: string;
  language?: string;     // ISO 639-1 hint
  timestamps?: boolean;  // default true
}): Promise<MediaJob>
```

`POST /v1/media/audio/transcriptions`. Answers a [`media_job`](/docs/api/audio#the-transcription-job) of `kind: "transcription"` — usually already `completed`, with `result.text` and `result.segments` filled in; a long recording the provider queues comes back `processing`, to be re-read with `audio.get`.

```ts theme={"system"}
const job = await client.media.audio.transcribe({ url: "https://cdn.example.com/call.mp3", language: "en" });
if (job.status === "completed" && job.result && "text" in job.result) console.log(job.result.text);
```

## audio.get

```ts theme={"system"}
client.media.audio.get(id: string): Promise<MediaJob>
```

`GET /v1/media/audio/transcriptions/{id}` — the same job, re-read.

## audio.speak

```ts theme={"system"}
client.media.audio.speak(body: {
  text: string;                    // 1–4 096 characters
  model?: string;
  voice?: string;
  format?: "mp3" | "wav" | "ogg";  // default mp3
  speed?: number;                  // 0.5–2
}): Promise<SpeechGeneration>
```

`POST /v1/media/audio/speech`. Synchronous; the reply's `file` is the stored [file](/docs/sdk/files), so the bytes are one `client.files.download(speech.file.id)` away.

```ts theme={"system"}
const speech = await client.media.audio.speak({ text: "Your order has shipped." });
await writeFile("shipped.mp3", await client.files.download(speech.file.id));
```

## audio.converse

```ts theme={"system"}
client.media.audio.converse(body: {
  file_id: string;                 // a stored recording of one spoken turn
  model?: string;
  voice?: string;
  format?: "mp3" | "wav" | "ogg";  // default mp3
}): Promise<SpeechGeneration>
```

`POST /v1/media/audio/conversations`. Synchronous, and the same `SpeechGeneration` as `speak`: the spoken reply to the turn in `file_id`, stored as a [file](/docs/sdk/files). The input is a stored file — `client.files.upload` or `client.files.import` a recording first.

```ts theme={"system"}
const turn = await client.files.import({ url: "https://cdn.example.com/question.wav" });
const reply = await client.media.audio.converse({ file_id: turn.id, format: "wav" });
await writeFile("reply.wav", await client.files.download(reply.file.id));
```
