Download clips from a session result
Introduction
This tutorial shows how to use the Results API to download clips from a session video. A session can have zero clips or many clips.
Start by listing the clips for the session, then request a specific clip by ID. The clip download endpoint starts a generation process when a generated clip file is not already available, so clients must poll until the response says the clip is ready.
Use this workflow when you want to archive clips in your own storage, feed clip videos into downstream analysis, or make selected research moments available to another internal system.
You’ll use two endpoints:
GET /api/v2/sessionResults/{sessionId}/clips
GET /api/v2/sessionResults/{sessionId}/clips/{clipId}What you will build
A repeatable clip download flow that:
- Uses a known
sessionIdto list every clip in that session. - Stores the
uuidfor each clip returned byGET /sessionResults/{sessionId}/clips. - Calls
GET /sessionResults/{sessionId}/clips/{clipUuid}for each clip. - Polls the same clip endpoint while generation is in progress.
- Downloads the clip immediately when a temporary
clipUrlis returned.
Target audience
- Data engineers
- Research operations teams
- Analytics engineers
- AI or ML teams preparing session media for downstream analysis
Prerequisites
- A valid access token (
ACCESS_TOKEN). - A known session ID (
SESSION_ID). - A client that can retry and poll with backoff.
- Optional object storage, such as S3 or GCS, if you want to keep a permanent
copy of each downloaded clip.
Use the external base URL:
https://api.use2.usertesting.com/api/v2Steps
Step 1 - List clips for the session
Endpoint:
GET /api/v2/sessionResults/{sessionId}/clipsExample:
curl --location \
"https://api.use2.usertesting.com/api/v2/sessionResults/SESSION_ID/clips" \
--header "Authorization: Bearer ACCESS_TOKEN" \
--header "Content-Type: application/json"Why this matters:
- Not every session has clips.
- A session can have more than one clip.
- The clip list response gives you the
uuidvalues needed for download.
Example response:
{
"studyUuid": "e320c95c-2741-49bc-9e04-37b77d6fece3",
"clips": [
{
"uuid": "a0ff28a2-020b-4944-ab5a-ba035269641e",
"name": "Clip #1",
"description": "Participant reacts to checkout flow",
"timeStartMs": 3708,
"timeEndMs": 33708,
"videoUuid": "5a1d8707-0cf1-47d4-86f3-7c7b8722921d",
"hasAudio": true,
"hasCamera": false
}
]
}Key fields to store:
clips[].uuid
Best practice: If clips is an empty array, treat the session as valid but
skip clip download for that session.
Step 2 - Request a specific clip
Endpoint:
GET /api/v2/sessionResults/{sessionId}/clips/{clipUuid}Example:
curl --location \
"https://api.use2.usertesting.com/api/v2/sessionResults/SESSION_ID/clips/a0ff28a2-020b-4944-ab5a-ba035269641e" \
--header "Authorization: Bearer ACCESS_TOKEN" \
--header "Content-Type: application/json"Important behavior:
- This endpoint starts clip generation when no ready generated file exists.
- The first response can return
GENERATING, which means the API has accepted the request and the clip file is not ready yet. - Poll the same endpoint until the response returns
GENERATED. - Only download the clip when
statusisGENERATEDandclipUrlis present.
Example response while generation is in progress:
{
"clipUuid": "a0ff28a2-020b-4944-ab5a-ba035269641e",
"status": "GENERATING"
}Example response when the clip is ready:
{
"clipUuid": "a0ff28a2-020b-4944-ab5a-ba035269641e",
"status": "GENERATED",
"clipUrl": "https://example-presigned-url"
}Best practice: Do not store clipUrl as the permanent identifier. Treat it as a temporary download URL. Download the clip immediately, then store your own object storage path and metadata.
Step 3 - Poll until the clip is ready
Poll with a bounded retry policy. Do not tight-loop. Start with a short delay, increase the delay between attempts, and stop after a maximum wait time.
Example pseudo-code:
const sleep = (delayMs) =>
new Promise((resolve) => setTimeout(resolve, delayMs));
async function waitForClipDownloadUrl({
sessionId,
clipUuid,
accessToken,
}) {
const maxAttempts = 10;
const initialDelayMs = 3000;
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
const response = await getClip({
sessionId,
clipUuid,
accessToken,
});
if (response.status === "GENERATED" && response.clipUrl) {
return response.clipUrl;
}
if (response.status === "ERROR") {
throw new Error(`Clip generation failed for ${clipUuid}`);
}
await sleep(initialDelayMs * attempt);
}
throw new Error(`Timed out waiting for clip ${clipUuid}`);
}Recommended polling behavior:
- Poll one clip at a time or use limited concurrency.
- Use exponential or linear backoff.
- Stop polling after a fixed number of attempts.
- Retry later if the clip is still
GENERATINGafter your timeout window. - Apply your normal handling for rate limit responses such as
429.
Step 4 - Download and store the clip
Once clipUrl is available, download the media file immediately. The download URL is temporary and expires after 15 minutes.
Example:
curl --location "CLIP_URL" --output "CLIP_UUID.mp4"End-to-end code example
async function downloadSessionClips({
sessionId,
accessToken,
}) {
const clipList = await getClips({
sessionId,
accessToken,
});
for (const clip of clipList.clips) {
const clipUrl = await waitForClipDownloadUrl({
sessionId,
clipUuid: clip.uuid,
accessToken,
});
const storagePath = await downloadAndStoreClip({
clipUrl,
clipUuid: clip.uuid,
});
await saveClipMetadata({
sessionId,
studyUuid: clipList.studyUuid,
clipUuid: clip.uuid,
clipName: clip.name,
timeStartMs: clip.timeStartMs,
timeEndMs: clip.timeEndMs,
storagePath,
downloadedAt: new Date().toISOString(),
});
}
}Common errors and troubleshooting
| Status | Meaning | Suggested handling |
|---|---|---|
400 | Invalid sessionId or clipUuid format | Validate UUIDs before calling the API. |
401 | Invalid or missing access token | Refresh the access token and retry. |
404 | Session, video, or clip was not found | Confirm the clip UUID came from the session clip list. |
429 | Rate limit exceeded | Back off and reduce polling or concurrency. |
5xx | Temporary service or generation issue | Retry with backoff; stop after a bounded timeout. |
Summary
The clip download workflow is a two-step read plus generation flow:
- Call
GET /api/v2/sessionResults/{sessionId}/clipsto discover clip IDs. - Call
GET /api/v2/sessionResults/{sessionId}/clips/{clipUuid}for each clip. - Poll while the clip status is
GENERATING. - Download the clip only after the status is
GENERATEDandclipUrlis present.
Because clip generation is asynchronous, clients should treat getClip as a request-or-status endpoint, not as a guaranteed immediate file download.
