Clips API#
The Clips endpoints expose the short-form clips that are available in your tenant. Integrations use this data to populate media pickers, drive recommendation rails, or deep link back into Storyteller CMS analytics.
Endpoint Summary#
| Endpoint | Description |
|---|---|
GET /api/clips |
Retrieve published clips with pagination, search, and sorting |
GET /api/clips/{externalId} |
Fetch a single clip by its external identifier |
GET /api/clips/by-id/{id} |
Fetch a single clip by its internal GUID identifier |
GET /api/clips/{externalId}/details |
Retrieve comprehensive clip details including caption localisations |
GET /api/clips/by-id/{id}/details |
Retrieve comprehensive clip details by internal GUID identifier |
ℹ️ Clip analytics are documented separately in Clip Analytics.
List Clips (GET /api/clips)#
Query Parameters#
| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
searchText |
string | No | – | Case-insensitive match against displayTitle |
externalId |
string | No | – | Exact match on externalId |
publishedSince |
string (ISO 8601) | No | – | Return clips published on or after this date/time. Accepts ISO 8601 format (e.g., 2025-01-15T00:00:00Z) or standard date formats. |
currentPage |
integer | No | 1 |
Must be >= 1 |
pageSize |
integer | No | 10 |
Must be between 1 and 100 |
sort |
string | No | AlphabeticalAsc |
Supported values: AlphabeticalAsc, CreatedDesc, PublishedDesc |
Clips returned from this endpoint are already filtered to
Publishedstatus inside Storyteller.
Example Response#
{
"clips": [
{
"id": "98226576-21a4-a1c9-efd6-3a12dc9eef9b",
"externalId": "clip-001",
"internalTitle": "STX Launch Teaser",
"displayTitle": "STX Launch Teaser",
"createdAt": "2025-01-15T10:30:00Z",
"publishedAt": "2025-01-15T12:00:00Z",
"duration": 32000,
"hasAudio": true,
"videoUrl": "https://cdn.yourtenant.net/clips/clip-001/video.mp4",
"thumbnailUrl": "https://cdn.yourtenant.net/clips/clip-001/thumbnail.jpg",
"thumbnailMediumUrl": "https://cdn.yourtenant.net/clips/clip-001/thumbnail-medium.jpg",
"thumbnailLargeUrl": "https://cdn.yourtenant.net/clips/clip-001/thumbnail-large.jpg",
"thumbnailWebpUrl": "https://cdn.yourtenant.net/clips/clip-001/thumbnail.webp",
"playcardUrl": "https://cdn.yourtenant.net/clips/clip-001/playcard.jpg",
"cmsUrl": "https://yourtenant.usestoryteller.com/clips/98226576-21a4-a1c9-efd6-3a12dc9eef9b/form",
"cmsAnalyticsUrl": "https://yourtenant.usestoryteller.com/clips/98226576-21a4-a1c9-efd6-3a12dc9eef9b/analytics",
"universalLinkUrl": "https://yourtenant.shar.estori.es/clips/98226576-21a4-a1c9-efd6-3a12dc9eef9b",
"deepLinkUrl": "yourtenantstories://open/clip/98226576-21a4-a1c9-efd6-3a12dc9eef9b",
"captionRemoteUrl": "https://cdn.yourtenant.net/clips/clip-001/captions.vtt",
"categories": [
{
"title": "Product",
"externalId": "product"
}
],
"collections": [
{
"id": "featured-clips",
"internalTitle": "Featured Clips",
"displayTitle": "Featured Clips"
}
]
}
],
"pageSize": 10,
"currentPage": 1,
"totalPages": 5
}
Response Fields#
| Field | Type | Description |
|---|---|---|
id |
string (GUID) | Internal clip identifier |
externalId |
string | External system identifier |
internalTitle |
string | Internal title shown in the CMS |
displayTitle |
string | Consumer-facing title |
createdAt |
string (ISO 8601) | Clip creation time |
publishedAt |
string (ISO 8601) | Most recent publish timestamp (falls back to creation time) |
duration |
integer | Clip duration in milliseconds |
hasAudio |
boolean | Indicates whether the clip has audio |
videoUrl |
string | CDN-aware video URL for the clip |
thumbnailUrl |
string | Primary thumbnail |
thumbnailMediumUrl |
string | Medium thumbnail (falls back to primary) |
thumbnailLargeUrl |
string | Large thumbnail (falls back to primary) |
thumbnailWebpUrl |
string | WebP thumbnail if available |
playcardUrl |
string | Playcard image/video used in some clients |
cmsUrl |
string | Direct link to the clip’s CMS form |
cmsAnalyticsUrl |
string | Link to the clip analytics dashboard inside the CMS |
universalLinkUrl |
string | Viewer-friendly link using the tenant’s sharing domain |
deepLinkUrl |
string | Custom URL scheme deep link |
captionRemoteUrl |
string | CDN-formatted caption file if captions exist |
categories |
array | Assigned categories (see table below) |
collections |
array | Collections containing the clip |
Category Object
| Field | Description |
|---|---|
title |
Category display name |
externalId |
Category external identifier |
Collection Object
| Field | Description |
|---|---|
id |
Collection internal identifier |
internalTitle |
CMS title |
displayTitle |
Public-facing title |
Usage Examples#
import fetch from 'node-fetch';
export async function listClips(apiKey, { searchText, publishedSince, pageSize = 20, page = 1 } = {}) {
const params = new URLSearchParams({ currentPage: page, pageSize });
if (searchText) params.set('searchText', searchText);
if (publishedSince) params.set('publishedSince', publishedSince);
const response = await fetch(`https://integrations.usestoryteller.com/api/clips?${params}`, {
headers: { 'x-storyteller-api-key': apiKey }
});
if (!response.ok) {
const problem = await response.json();
throw new Error(`${problem.title}: ${problem.detail}`);
}
return response.json();
}
import requests
from datetime import datetime
def list_clips(
api_key: str,
search_text: str | None = None,
published_since: datetime | str | None = None,
page: int = 1,
page_size: int = 20
):
params = {
'currentPage': page,
'pageSize': page_size
}
if search_text:
params['searchText'] = search_text
if published_since:
# Convert datetime to ISO 8601 string if needed
if isinstance(published_since, datetime):
params['publishedSince'] = published_since.isoformat()
else:
params['publishedSince'] = published_since
response = requests.get(
'https://integrations.usestoryteller.com/api/clips',
headers={'x-storyteller-api-key': api_key},
params=params,
timeout=30
)
response.raise_for_status()
return response.json()
public sealed class ClipClient
{
private readonly HttpClient _client;
public ClipClient(string apiKey)
{
_client = new HttpClient { BaseAddress = new Uri("https://integrations.usestoryteller.com/") };
_client.DefaultRequestHeaders.Add("x-storyteller-api-key", apiKey);
}
public async Task<ListClipsResponse?> GetClipsAsync(
int page = 1,
int pageSize = 20,
string? searchText = null,
DateTime? publishedSince = null)
{
var query = new Dictionary<string, string>
{
["currentPage"] = page.ToString(),
["pageSize"] = pageSize.ToString()
};
if (!string.IsNullOrWhiteSpace(searchText)) query["searchText"] = searchText;
if (publishedSince.HasValue) query["publishedSince"] = publishedSince.Value.ToString("o");
var url = QueryHelpers.AddQueryString("api/clips", query);
return await _client.GetFromJsonAsync<ListClipsResponse>(url);
}
}
# Get clips published since January 15, 2025
curl -X GET "https://integrations.usestoryteller.com/api/clips?currentPage=1&pageSize=20&publishedSince=2025-01-15T00:00:00Z" \
-H "x-storyteller-api-key: YOUR_API_KEY"
Get Clip by External ID (GET /api/clips/{externalId})#
Returns the same payload as the list endpoint for a single clip. Use this when you already know the clip external ID and need CMS or sharing links for that clip.
Example#
curl -X GET "https://integrations.usestoryteller.com/api/clips/clip-001" \
-H "x-storyteller-api-key: YOUR_API_KEY"
Get Clip by ID (GET /api/clips/by-id/{id})#
Fetches a single clip using its internal GUID identifier. This is useful when you have the clip's internal ID from another Storyteller API response (such as workflow results or analytics data) and need to retrieve the clip metadata.
Path Parameters#
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
string (GUID) | Yes | The internal clip identifier |
Example Response#
Returns the same structure as the list endpoint for a single clip:
{
"id": "98226576-21a4-a1c9-efd6-3a12dc9eef9b",
"externalId": "clip-001",
"internalTitle": "STX Launch Teaser",
"displayTitle": "STX Launch Teaser",
"createdAt": "2025-01-15T10:30:00Z",
"publishedAt": "2025-01-15T12:00:00Z",
"duration": 32000,
"hasAudio": true,
"videoUrl": "https://cdn.yourtenant.net/clips/clip-001/video.mp4",
"thumbnailUrl": "https://cdn.yourtenant.net/clips/clip-001/thumbnail.jpg",
"thumbnailMediumUrl": "https://cdn.yourtenant.net/clips/clip-001/thumbnail-medium.jpg",
"thumbnailLargeUrl": "https://cdn.yourtenant.net/clips/clip-001/thumbnail-large.jpg",
"thumbnailWebpUrl": "https://cdn.yourtenant.net/clips/clip-001/thumbnail.webp",
"playcardUrl": "https://cdn.yourtenant.net/clips/clip-001/playcard.jpg",
"cmsUrl": "https://yourtenant.usestoryteller.com/clips/98226576-21a4-a1c9-efd6-3a12dc9eef9b/form",
"cmsAnalyticsUrl": "https://yourtenant.usestoryteller.com/clips/98226576-21a4-a1c9-efd6-3a12dc9eef9b/analytics",
"universalLinkUrl": "https://yourtenant.shar.estori.es/clips/98226576-21a4-a1c9-efd6-3a12dc9eef9b",
"deepLinkUrl": "yourtenantstories://open/clip/98226576-21a4-a1c9-efd6-3a12dc9eef9b",
"captionRemoteUrl": "https://cdn.yourtenant.net/clips/clip-001/captions.vtt",
"categories": [
{
"title": "Product",
"externalId": "product"
}
],
"collections": [
{
"id": "featured-clips",
"internalTitle": "Featured Clips",
"displayTitle": "Featured Clips"
}
]
}
Usage Examples#
import fetch from 'node-fetch';
export async function getClipById(apiKey, clipId) {
const response = await fetch(`https://integrations.usestoryteller.com/api/clips/by-id/${clipId}`, {
headers: { 'x-storyteller-api-key': apiKey }
});
if (!response.ok) {
throw await response.json();
}
return response.json();
}
import requests
def get_clip_by_id(api_key: str, clip_id: str) -> dict:
response = requests.get(
f"https://integrations.usestoryteller.com/api/clips/by-id/{clip_id}",
headers={'x-storyteller-api-key': api_key},
timeout=30
)
response.raise_for_status()
return response.json()
public async Task<ClipResponse?> GetClipByIdAsync(string apiKey, Guid clipId)
{
using var client = new HttpClient
{
BaseAddress = new Uri("https://integrations.usestoryteller.com/")
};
client.DefaultRequestHeaders.Add("x-storyteller-api-key", apiKey);
var response = await client.GetAsync($"api/clips/by-id/{clipId}");
if (!response.IsSuccessStatusCode)
{
var problem = await response.Content.ReadFromJsonAsync<ProblemDetails>();
throw new InvalidOperationException(problem?.Detail ?? "Unable to fetch clip");
}
return await response.Content.ReadFromJsonAsync<ClipResponse>();
}
curl -X GET "https://integrations.usestoryteller.com/api/clips/by-id/98226576-21a4-a1c9-efd6-3a12dc9eef9b" \
-H "x-storyteller-api-key: YOUR_API_KEY"
Clip Details by External ID (GET /api/clips/{externalId}/details)#
Retrieve comprehensive clip details including caption localisations. This endpoint extends the basic clip response with additional caption localisation information, providing all language-specific caption URLs.
Path Parameters#
| Parameter | Type | Required | Description |
|---|---|---|---|
externalId |
string | Yes | The external clip identifier |
Example Response#
{
"id": "98226576-21a4-a1c9-efd6-3a12dc9eef9b",
"externalId": "clip-001",
"internalTitle": "STX Launch Teaser",
"displayTitle": "STX Launch Teaser",
"createdAt": "2025-01-15T10:30:00Z",
"publishedAt": "2025-01-15T12:00:00Z",
"duration": 32000,
"hasAudio": true,
"videoUrl": "https://cdn.yourtenant.net/clips/clip-001/video.mp4",
"thumbnailUrl": "https://cdn.yourtenant.net/clips/clip-001/thumbnail.jpg",
"thumbnailMediumUrl": "https://cdn.yourtenant.net/clips/clip-001/thumbnail-medium.jpg",
"thumbnailLargeUrl": "https://cdn.yourtenant.net/clips/clip-001/thumbnail-large.jpg",
"thumbnailWebpUrl": "https://cdn.yourtenant.net/clips/clip-001/thumbnail.webp",
"playcardUrl": "https://cdn.yourtenant.net/clips/clip-001/playcard.jpg",
"cmsUrl": "https://yourtenant.usestoryteller.com/clips/98226576-21a4-a1c9-efd6-3a12dc9eef9b/form",
"cmsAnalyticsUrl": "https://yourtenant.usestoryteller.com/clips/98226576-21a4-a1c9-efd6-3a12dc9eef9b/analytics",
"universalLinkUrl": "https://yourtenant.shar.estori.es/clips/98226576-21a4-a1c9-efd6-3a12dc9eef9b",
"deepLinkUrl": "yourtenantstories://open/clip/98226576-21a4-a1c9-efd6-3a12dc9eef9b",
"caption": {
"remoteUrl": "https://cdn.yourtenant.net/clips/clip-001/captions.vtt",
"localisations": [
{
"languageCode": "es",
"remoteUrl": "https://cdn.yourtenant.net/clips/clip-001/captions.es.vtt"
},
{
"languageCode": "fr",
"remoteUrl": "https://cdn.yourtenant.net/clips/clip-001/captions.fr.vtt"
}
]
},
"categories": [
{
"title": "Product",
"externalId": "product"
}
],
"collections": [
{
"id": "featured-clips",
"internalTitle": "Featured Clips",
"displayTitle": "Featured Clips"
}
]
}
Additional Response Fields#
The details endpoint includes these additional fields beyond the basic clip response:
| Field | Type | Description |
|---|---|---|
caption |
object | Caption details including localisations |
caption.remoteUrl |
string | Primary caption file URL (usually WebVTT) |
caption.localisations |
array | Language-specific caption files |
caption.localisations[].languageCode |
string | ISO language code (e.g., "es", "fr") |
caption.localisations[].remoteUrl |
string | CDN-formatted caption URL for the language |
Usage Examples#
import fetch from 'node-fetch';
export async function getClipDetails(apiKey, externalId) {
const response = await fetch(`https://integrations.usestoryteller.com/api/clips/${externalId}/details`, {
headers: { 'x-storyteller-api-key': apiKey }
});
if (!response.ok) {
throw await response.json();
}
return response.json();
}
import requests
def get_clip_details(api_key: str, external_id: str) -> dict:
response = requests.get(
f"https://integrations.usestoryteller.com/api/clips/{external_id}/details",
headers={'x-storyteller-api-key': api_key},
timeout=30
)
response.raise_for_status()
return response.json()
public async Task<ClipDetails?> GetClipDetailsAsync(string apiKey, string externalId)
{
using var client = new HttpClient
{
BaseAddress = new Uri("https://integrations.usestoryteller.com/")
};
client.DefaultRequestHeaders.Add("x-storyteller-api-key", apiKey);
var response = await client.GetAsync($"api/clips/{externalId}/details");
if (!response.IsSuccessStatusCode)
{
var problem = await response.Content.ReadFromJsonAsync<ProblemDetails>();
throw new InvalidOperationException(problem?.Detail ?? "Unable to fetch clip");
}
return await response.Content.ReadFromJsonAsync<ClipDetails>();
}
curl -X GET "https://integrations.usestoryteller.com/api/clips/clip-001/details" \
-H "x-storyteller-api-key: YOUR_API_KEY"
Clip Details by ID (GET /api/clips/by-id/{id}/details)#
Retrieve comprehensive clip details using its internal GUID identifier. This endpoint provides the same detailed information as the external ID version, including caption localisations.
Path Parameters#
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
string (GUID) | Yes | The internal clip identifier |
Example Response#
Returns the same structure as the external ID details endpoint. See the example response in the previous section.
Usage Examples#
import fetch from 'node-fetch';
export async function getClipDetailsById(apiKey, clipId) {
const response = await fetch(`https://integrations.usestoryteller.com/api/clips/by-id/${clipId}/details`, {
headers: { 'x-storyteller-api-key': apiKey }
});
if (!response.ok) {
throw await response.json();
}
return response.json();
}
import requests
def get_clip_details_by_id(api_key: str, clip_id: str) -> dict:
response = requests.get(
f"https://integrations.usestoryteller.com/api/clips/by-id/{clip_id}/details",
headers={'x-storyteller-api-key': api_key},
timeout=30
)
response.raise_for_status()
return response.json()
public async Task<ClipDetails?> GetClipDetailsByIdAsync(string apiKey, Guid clipId)
{
using var client = new HttpClient
{
BaseAddress = new Uri("https://integrations.usestoryteller.com/")
};
client.DefaultRequestHeaders.Add("x-storyteller-api-key", apiKey);
var response = await client.GetAsync($"api/clips/by-id/{clipId}/details");
if (!response.IsSuccessStatusCode)
{
var problem = await response.Content.ReadFromJsonAsync<ProblemDetails>();
throw new InvalidOperationException(problem?.Detail ?? "Unable to fetch clip");
}
return await response.Content.ReadFromJsonAsync<ClipDetails>();
}
curl -X GET "https://integrations.usestoryteller.com/api/clips/by-id/98226576-21a4-a1c9-efd6-3a12dc9eef9b/details" \
-H "x-storyteller-api-key: YOUR_API_KEY"
Error Handling#
All clip endpoints return RFC 7807 Problem Details errors. Example responses:
{
"status": 401,
"type": "https://datatracker.ietf.org/doc/html/rfc7235#section-3.1",
"title": "Unauthorized",
"detail": "No valid API key provided",
"instance": ""
}
{
"status": 404,
"type": "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.4",
"title": "Not Found",
"detail": "Clip with externalId clip-999 not found",
"instance": ""
}
Best Practices#
- Serve media from Storyteller CDN – the API already resolves relative paths; use the URLs as-is to avoid hardcoding tenants.
- Prefer
externalIdfilters when you are reconciling clips with your internal systems. - Surface collection context so operators can see how clips are grouped inside Storyteller.
- Handle Problem Details responses uniformly across all content endpoints.
- Use Clip Analytics for engagement metrics rather than computing totals yourself – see Clip Analytics.
Related Documentation#
- Clip Analytics – Retrieve views, loops, and engagement KPIs
- Stories API – Complementary story metadata
- Collections – Discover related clip groups
- Authentication – Required header reference