# Find best thumbnails

Find and rank the best thumbnail frames in a video with the Mux Robots API. Use this workflow to pick poster images or thumbnails for your video gallery.

<Callout type="warning" title="Experimental">
  The `find-best-thumbnails` workflow is experimental. The API shape, parameters, behavior, and pricing may change. If we make changes to this workflow while you're using it, we'll let you know via email.
</Callout>

Find the strongest thumbnail frames in a video. Mux samples frames across a Mux asset, scores each one for thumbnail quality, and returns the top candidates ranked best to worst with per-category subscores and a short description of each frame. It's useful for auto-selecting a poster image, building a thumbnail picker, or A/B testing thumbnail options. See the <ApiRefLink href="/docs/api-reference/robots/find-best-thumbnails">Find Best Thumbnails API reference</ApiRefLink> for the full endpoint specification. See [Mux Robots pricing](/docs/pricing/overview#mux-robots-pricing) for unit costs.

<Callout type="info">
  Find best thumbnails requires a video asset in the `ready` state. Audio-only assets are not supported, since the workflow scores sampled video frames.
</Callout>

## Create a `find-best-thumbnails` job

```bash
curl https://api.mux.com/robots/v0/jobs/find-best-thumbnails \
  -H "Content-Type: application/json" \
  -X POST \
  -d '{
    "parameters": {
      "asset_id": "YOUR_ASSET_ID",
      "max_thumbnails": 3
    }
  }' \
  -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.job.find_best_thumbnails.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, you can poll `GET /robots/v0/jobs/find-best-thumbnails/{JOB_ID}` with the `id` from the response until the status is `completed`.
</Callout>

## Parameters

| Parameter | Type | Description |
| :-- | :-- | :-- |
| `asset_id` | string | **Required.** The Mux asset ID of the video asset to pull thumbnail candidates from. |
| `max_thumbnails` | integer | **Optional.** Maximum number of candidate thumbnails to return, from `1` to `5`. Defaults to `1`. |
| `output_steering` | object | **Optional.** Curated controls that guide how candidates are scored without changing the response schema. See [Output steering](#output-steering). |
| `update_asset_thumbnail` | boolean | **Optional.** When `true`, the highest-scoring thumbnail's timestamp is written to the asset's `thumbnail_time` once the job completes, making that frame the asset's default thumbnail. Defaults to `false`. See [Set the asset's default thumbnail](#set-the-assets-default-thumbnail). |

### Output steering

Use `output_steering` when you want best-effort control over which frames score highest. These fields guide the workflow but do not guarantee exact output.

| Field | Type | Description |
| :-- | :-- | :-- |
| `selection_strategy` | string | Preferred kind of thumbnail candidate to favor. Supported values: `face_or_action`, `clean_composition`, `high_contrast`, `brand_safe`, and `campaign_thumbnail`. |
| `looking_for` | string | Open-ended description of what you're explicitly looking for in candidate thumbnails. |
| `audience` | string | Intended audience used to guide scoring. |
| `campaign_style` | string | Short description of the campaign or channel thumbnail style to prefer when supported by the frame. |
| `scoring_priorities` | array of strings | Rubric criteria to emphasize in close calls. |

```json
{
  "parameters": {
    "asset_id": "YOUR_ASSET_ID",
    "max_thumbnails": 5,
    "output_steering": {
      "selection_strategy": "face_or_action",
      "looking_for": "a clear shot of the host mid-demo with the product visible",
      "audience": "developers evaluating a video API",
      "campaign_style": "clean, high-contrast tech explainer",
      "scoring_priorities": ["sharp focus", "readable at small sizes"]
    }
  }
}
```

### Set the asset's default thumbnail

By default, the workflow only returns candidates and leaves the asset untouched. Set `update_asset_thumbnail` to `true` to have Mux write the top-scoring frame's timestamp to the asset's `thumbnail_time` when the job completes, so requests to `https://image.mux.com/{PLAYBACK_ID}/thumbnail.jpg` return that frame.

```json
{
  "parameters": {
    "asset_id": "YOUR_ASSET_ID",
    "max_thumbnails": 3,
    "update_asset_thumbnail": true
  }
}
```

The write is best-effort. If it fails, the job still completes and returns its ranked candidates, and you can set `thumbnail_time` yourself from `outputs.best_thumbnails`.

<Callout type="info">
  A new default thumbnail will appear for some clients sooner than others, depending on their local cache settings. See [Set a default thumbnail](/docs/guides/set-a-default-thumbnail#when-the-new-thumbnail-appears) for how the change propagates, and for how to set or clear `thumbnail_time` directly.
</Callout>

## Output

The `outputs` object is included in the job once its status is `completed`. You'll receive it on the [`robots.job.find_best_thumbnails.completed`](/docs/guides/robots#webhooks) webhook (recommended), or you can fetch it with `GET /robots/v0/jobs/find-best-thumbnails/{JOB_ID}`. It contains:

| Field | Type | Description |
| :-- | :-- | :-- |
| `best_thumbnails` | array | Selected candidate thumbnails, ordered best to worst by overall quality score. |
| `best_thumbnails[].timestamp_ms` | number | Frame timestamp in milliseconds. |
| `best_thumbnails[].overall` | number | Overall quality score from `0` to `1`. |
| `best_thumbnails[].description` | string | AI-generated description of the frame content. |
| `best_thumbnails[].subscores` | object | Per-category quality scores, each from `0` to `1`. |
| `best_thumbnails[].subscores.focus` | number | Sharpness and focus quality. |
| `best_thumbnails[].subscores.face_or_action` | number | Presence of faces or dynamic action. |
| `best_thumbnails[].subscores.composition` | number | Visual composition and framing quality. |
| `best_thumbnails[].subscores.contrast_color` | number | Color contrast and vibrancy. |
| `best_thumbnails[].subscores.brand_fit` | number | Alignment with brand guidelines. |

<Callout type="info">
  Each candidate is identified by its `timestamp_ms`. To render the frame as an image, convert the timestamp to seconds and pass it to the [image API](/docs/guides/get-images-from-a-video): `https://image.mux.com/{PLAYBACK_ID}/thumbnail.jpg?time={SECONDS}`.
</Callout>

## Example response

This is the payload delivered to the [`robots.job.find_best_thumbnails.completed`](/docs/guides/robots#webhooks) webhook, and the same shape you get from `GET /robots/v0/jobs/find-best-thumbnails/{JOB_ID}`:

```json
{
  "data": {
    "id": "rjob_thumb123",
    "workflow": "find-best-thumbnails",
    "status": "completed",
    "units_consumed": 3500,
    "parameters": {
      "asset_id": "YOUR_ASSET_ID",
      "max_thumbnails": 3
    },
    "outputs": {
      "best_thumbnails": [
        {
          "timestamp_ms": 42000,
          "overall": 0.91,
          "description": "The host faces the camera mid-sentence with the product dashboard sharp behind them.",
          "subscores": {
            "focus": 0.95,
            "face_or_action": 0.92,
            "composition": 0.88,
            "contrast_color": 0.86,
            "brand_fit": 0.9
          }
        },
        {
          "timestamp_ms": 118500,
          "overall": 0.78,
          "description": "A wide shot of the upload screen with the cursor on the upload button.",
          "subscores": {
            "focus": 0.82,
            "face_or_action": 0.6,
            "composition": 0.85,
            "contrast_color": 0.8,
            "brand_fit": 0.83
          }
        }
      ]
    }
  }
}
```
