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.
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 Find Best Thumbnails API referenceAPI for the full endpoint specification. See Mux Robots pricing for unit costs.
Find best thumbnails requires a video asset in the ready state. Audio-only assets are not supported, since the workflow scores sampled video frames.
find-best-thumbnails jobcurl 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}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. 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.
| 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. |
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. |
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. |
{
"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"]
}
}
}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.
{
"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.
A new default thumbnail will appear for some clients sooner than others, depending on their local cache settings. See Set a default thumbnail for how the change propagates, and for how to set or clear thumbnail_time directly.
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 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. |
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: https://image.mux.com/{PLAYBACK_ID}/thumbnail.jpg?time={SECONDS}.
This is the payload delivered to the robots.job.find_best_thumbnails.completed webhook, and the same shape you get from GET /robots/v0/jobs/find-best-thumbnails/{JOB_ID}:
{
"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
}
}
]
}
}
}