HomeDocumentationAPI Reference
Documentation

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:

  1. Uses a known sessionId to list every clip in that session.
  2. Stores the uuid for each clip returned by GET /sessionResults/{sessionId}/clips.
  3. Calls GET /sessionResults/{sessionId}/clips/{clipUuid} for each clip.
  4. Polls the same clip endpoint while generation is in progress.
  5. Downloads the clip immediately when a temporary clipUrl is 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/v2

Steps

Step 1 - List clips for the session

Endpoint:

GET /api/v2/sessionResults/{sessionId}/clips

Example:

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 uuid values 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 status is GENERATED and clipUrl is 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 GENERATING after 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

StatusMeaningSuggested handling
400Invalid sessionId or clipUuid formatValidate UUIDs before calling the API.
401Invalid or missing access tokenRefresh the access token and retry.
404Session, video, or clip was not foundConfirm the clip UUID came from the session clip list.
429Rate limit exceededBack off and reduce polling or concurrency.
5xxTemporary service or generation issueRetry with backoff; stop after a bounded timeout.

Summary

The clip download workflow is a two-step read plus generation flow:

  1. Call GET /api/v2/sessionResults/{sessionId}/clips to discover clip IDs.
  2. Call GET /api/v2/sessionResults/{sessionId}/clips/{clipUuid} for each clip.
  3. Poll while the clip status is GENERATING.
  4. Download the clip only after the status is GENERATED and clipUrl is present.

Because clip generation is asynchronous, clients should treat getClip as a request-or-status endpoint, not as a guaranteed immediate file download.