# Generate contextual text

Turn an asset into social posts, blog posts, or newsletter copy with the Mux Robots API, and steer the voice, audience, and call to action. Request named variants of a shared set of written artifacts.

You can use the Generate Contextual Text workflow to turn an interview, tutorial, product demonstration, or any other video, into social posts, blog posts, and newsletter copy grounded in the source media. You can request all the outputs together, and use variants to get more than one version of the whole set: independent takes to choose between, or different angles you describe with `instructions`. See the <ApiRefLink href="/docs/api-reference/robots/generate-contextual-text">Generate Contextual Text API reference</ApiRefLink> for the full endpoint specification. See [Mux Robots pricing](/docs/pricing/overview#mux-robots-pricing) for unit costs.

The workflow builds a shared editorial brief from the asset's central idea, supporting points, and concrete details, then writes each artifact from that brief. The text addresses the subject directly: a tutorial can become an instructional article, and a product demonstration can become promotional copy.

For a simple title, description, and tags workflow, use [Summarize](/docs/guides/robots-summarize).

<Callout type="info">
  The asset must be ready, no longer live, and have a public or signed playback ID and a ready caption track. Audio-only assets can also use a single ready legacy text track that isn't marked as captions. [Generate captions](/docs/guides/add-autogenerated-captions-and-use-transcripts) or [add your own captions](/docs/guides/add-subtitles-to-your-videos) before creating a job. Video assets include a storyboard as visual context; the transcript remains the authoritative source.
</Callout>

## Create a `generate-contextual-text` job

An **artifact** is one deliverable. A **variant** is a named version of every requested artifact. Two variants and two artifacts produce four pieces of text. A variant's `key` only labels it in the results. Any difference in angle comes from its `instructions`.

```bash
curl https://api.mux.com/robots/v1/jobs/generate-contextual-text \
  -H "Content-Type: application/json" \
  -X POST \
  -d '{
    "parameters": {
      "asset_id": "YOUR_ASSET_ID",
      "output_language_code": "en",
      "output_steering": {
        "audience": "video developers",
        "voice": "conversational"
      },
      "variants": [
        { "key": "option_a", "instructions": "Playful voice for developers who are new to video." },
        { "key": "option_b" }
      ],
      "artifacts": [
        { "key": "x_post", "channel": "x", "max_length": { "unit": "characters", "value": 280 } },
        { "key": "blog_post", "instructions": "Write a short blog post.", "max_length": { "unit": "words", "value": 1200 }, "format": "markdown" }
      ]
    }
  }' \
  -u ${MUX_TOKEN_ID}:${MUX_TOKEN_SECRET}
```

<Callout type="info">
  This request is **asynchronous**. The `POST` returns immediately with the job in `pending` status and does not include results. **We strongly recommend listening for the [`robots.v1.job.generate_contextual_text.completed` webhook](/docs/guides/robots#webhooks)**. The payload contains the full completed job, so no follow-up API call is needed. If webhooks aren't an option, poll `GET /robots/v1/jobs/generate-contextual-text/{JOB_ID}` with the `id` from the response until the status is `completed`. Jobs are automatically deleted after 30 days.
</Callout>

## Parameters

| Parameter | Type | Description |
| :-- | :-- | :-- |
| `asset_id` | string | **Required.** The Mux video or audio asset ID whose transcript grounds the text. |
| `variants` | array | **Required.** 1–5 named versions of the complete artifact set. See [Variants and artifacts](#variants-and-artifacts). |
| `artifacts` | array | **Required.** 1–5 deliverables to generate for each variant. See [Variants and artifacts](#variants-and-artifacts). |
| `language_code` | string | BCP 47 language code of the caption source. An explicit value must match a ready source exactly, including case: `en` does not match `en-US`. When omitted, the first ready caption track is selected. Audio-only assets can also use a single ready legacy text track. |
| `output_language_code` | string | BCP 47 language code for generated text. Defaults to the selected transcript track's language when available. |
| `use_shots` | boolean | When `true`, generates or reuses Mux Shots and adds a bounded sample of shot frames as visual evidence. Omitted or `false` uses the storyboard without additional shot frames. Rejected for audio-only assets. Shot generation can incur separate [Mux Video charges](/docs/pricing/overview#shots). |
| `output_steering` | object | Shared controls for the analyzed time range, audience, voice, call to action, and brand terminology. See [Output steering](#output-steering). |

See [language support for Mux Robots](/docs/guides/robots-supported-languages) for verified languages and language tags.

### Variants and artifacts

Each variant and artifact needs a `key`: a lowercase snake\_case identifier starting with a letter, up to 64 characters. Keys must be unique within each array. A key identifies the result; it does not guide generation. Use `instructions` to describe the angle or deliverable.

Naming a variant `funny` does not make it funny. Omit `instructions` to get independent takes on the same brief, which is useful when you want several options to choose from or test. Add `instructions` when each variant should take a specific angle.

[Output steering](#output-steering) applies to every variant. To give variants different voices or audiences, describe them in each variant's `instructions`, for example `"Playful voice for developers who are new to video"`.

<Callout type="info">
  Each generated artifact in each variant costs 250 units, in addition to the job's base and duration charges. Two variants with two artifacts count as four artifacts. See [Mux Robots pricing](/docs/pricing/overview#mux-robots-pricing).
</Callout>

| Field | Type | Description |
| :-- | :-- | :-- |
| `variants[].key` | string | **Required.** Identifier returned with this variant. |
| `variants[].instructions` | string | Optional guidance for the variant's angle, 1–500 characters. Omit it to request an independent take on the shared brief. |
| `artifacts[].key` | string | **Required.** Identifier returned with this artifact in every variant. |
| `artifacts[].instructions` | string | Optional guidance for the deliverable, 1–500 characters. |
| `artifacts[].channel` | string | Publishing conventions and default length. Supported values: `generic`, `x`, `linkedin`, `facebook`, `instagram`, `tiktok`, and `youtube`. Defaults to `generic`. |
| `artifacts[].max_length` | object | Hard output cap with `unit` (`characters` or `words`) and integer `value`. Character caps range from 10 to 20,000; word caps range from 5 to 3,000. Defaults to the channel's cap. `x` always also enforces 280 characters, even when you specify a word cap. |
| `artifacts[].format` | string | `plain` (default) or `markdown`. Use `markdown` when the result should be allowed to include Markdown formatting. |

Default channel caps are 300 words for `generic` and `linkedin`, 250 words for `facebook` and `youtube`, 100 words for `tiktok`, 1,500 characters for `instagram`, and 280 characters for `x`. An explicit `max_length` overrides the default, subject to the `x` ceiling.

Each artifact aims for about 90% of its cap, whatever the length of the source. A one-minute video and a 15-minute video get text of about the same length, so set `max_length` to the length you want rather than relying on the channel default.

`generic` has no platform conventions; its style follows the length cap. Short caps produce a single focused piece. For a blog post, article, or newsletter entry, use `generic` with a `max_length` above 600 words and `format: "markdown"`, as in the `blog_post` artifact above.

### Output steering

Scope limits which transcript cues and visual context ground the text. The other controls apply best-effort writing guidance to every artifact and do not guarantee exact wording.

| Field | Type | Description |
| :-- | :-- | :-- |
| `scope` | object | Execution window in seconds on the original asset timeline. Supply `start_time`, `end_time`, or both. An omitted start means the beginning; an omitted end means the asset's end. The start must be nonnegative, the end must be positive, and the start must precede the end. An end past the asset's duration is clamped to the asset's end, and a range that starts at or after the asset's end is rejected. |
| `audience` | string | Intended reader, up to 160 characters. |
| `voice` | string | `conversational`, `editorial`, `playful`, or `professional`. |
| `call_to_action` | string | `none`, `soft`, or `direct`, guiding whether and how text invites further engagement. |
| `brand_terms` | array of strings | Preferred terms to use when the source supports them. Supply 1–10 terms, up to 40 characters each and 240 characters combined. |

For example, generate copy from just the first two minutes:

```json
{
  "parameters": {
    "asset_id": "YOUR_ASSET_ID",
    "variants": [{ "key": "main" }],
    "artifacts": [{ "key": "post", "channel": "linkedin" }],
    "output_steering": {
      "scope": { "start_time": 0, "end_time": 120 },
      "audience": "video developers",
      "voice": "professional",
      "call_to_action": "soft",
      "brand_terms": ["Mux Video"]
    }
  }
}
```

The selected range must contain usable transcript evidence. A range with no usable transcript cues cannot produce grounded text and the job can fail during processing.

## Output

The `outputs` object is included once the job is `completed`, in the [`robots.v1.job.generate_contextual_text.completed`](/docs/guides/robots#webhooks) webhook or the job's `GET` response.

| Field | Type | Description |
| :-- | :-- | :-- |
| `variants` | array | Generated versions of the complete artifact set. |
| `variants[].key` | string | The requested variant key. |
| `variants[].artifacts` | array | Generated deliverables for this variant. |
| `variants[].artifacts[].key` | string | The requested artifact key. |
| `variants[].artifacts[].content` | string | Generated text in the requested format, constrained by the artifact's length cap. |

Match results to your request using the variant and artifact keys. Each returned artifact in each variant counts once toward unit consumption.

## Example completed job

This example uses a two-minute asset with two variants and two artifacts, consuming 1,520 units. It is the job delivered in the [`robots.v1.job.generate_contextual_text.completed`](/docs/guides/robots#webhooks) webhook's `data` field and returned by `GET /robots/v1/jobs/generate-contextual-text/{JOB_ID}`:

```json
{
  "data": {
    "id": "rjob_abc123",
    "workflow": "generate-contextual-text",
    "status": "completed",
    "units_consumed": 1520,
    "created_at": 1746403200,
    "updated_at": 1746403260,
    "parameters": {
      "asset_id": "YOUR_ASSET_ID",
      "output_language_code": "en",
      "output_steering": { "audience": "video developers", "voice": "conversational" },
      "variants": [
        { "key": "option_a", "instructions": "Playful voice for developers who are new to video." },
        { "key": "option_b" }
      ],
      "artifacts": [
        { "key": "x_post", "channel": "x", "max_length": { "unit": "characters", "value": 280 }, "format": "plain" },
        { "key": "blog_post", "instructions": "Write a short blog post.", "channel": "generic", "max_length": { "unit": "words", "value": 1200 }, "format": "markdown" }
      ]
    },
    "outputs": {
      "variants": [
        {
          "key": "option_a",
          "artifacts": [
            { "key": "x_post", "content": "Never shipped video before? Make a direct upload, send your file, and grab the playback ID. That's the whole trip from your laptop to a working player with Mux Video." },
            { "key": "blog_post", "content": "# Your first video, start to finish\n\nGood news: getting a video from your laptop into a player takes three steps. First, create a direct upload. Once the video is ready, its playback ID connects the uploaded asset to your player." }
          ]
        },
        {
          "key": "option_b",
          "artifacts": [
            { "key": "x_post", "content": "Uploading a video and playing it are separate steps. Follow the asset until it is ready, then use its playback ID to load the player." },
            { "key": "blog_post", "content": "# Understand the upload lifecycle\n\nA direct upload creates a destination for your video. The walkthrough then follows the uploaded asset to readiness before loading it into a player." }
          ]
        }
      ]
    }
  }
}
```
