Skip to content

Stories API#

Use the Stories endpoints to surface the stories that are already live in your Storyteller tenant. These APIs power external CMS dashboards, content selectors, and workflow metadata flows that need story identifiers, categories, and deep links.

Endpoint Summary#

Endpoint Description
GET /api/stories Retrieve published stories with pagination, search, and publish-status filtering
GET /api/stories/{externalId} Fetch a single story by its external identifier (legacy lightweight response)
GET /api/stories/by-id/{id} Fetch a single story by its internal GUID identifier
GET /api/stories/{externalId}/details Retrieve the full story graph including ordered pages, media assets, captions, and action links
GET /api/stories/by-id/{id}/details Retrieve the full story graph by internal GUID identifier

List Stories (GET /api/stories)#

Query Parameters#

Parameter Type Required Default Notes
searchText string No Case-insensitive match against the story title
externalId string No Exact match on the externalId column
publishStatus string (Published, Draft, Scheduled) No Filter stories that are currently in a specific publish state
publishedSince string (ISO 8601) No Return stories published on or after this date/time. Matches against story-level or category-level publish dates. 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, LastModifiedDesc, CreatedDesc, PublishedDesc

Example Response#

{
  "stories": [
    {
      "id": "9d9a9098-2d9e-4d67-a0bf-0f6eb0d3f2fa",
      "externalId": "homepage-story",
      "title": "Homepage Hero",
      "createdAt": "2025-02-07T11:24:39Z",
      "publishedAt": "2025-02-08T09:00:00Z",
      "appOpenUrl": "https://yourtenant.shar.estori.es/go/9d9a9098-2d9e-4d67-a0bf-0f6eb0d3f2fa",
      "thumbnailUrl": "https://cdn.yourtenant.net/stories/homepage-story/thumbnail.jpg",
      "iconUrl": "https://cdn.yourtenant.net/stories/homepage-story/icon-square.jpg",
      "cmsUrl": "https://yourtenant.usestoryteller.com/stories/9d9a9098-2d9e-4d67-a0bf-0f6eb0d3f2fa/form",
      "cmsAnalyticsUrl": "https://yourtenant.usestoryteller.com/stories/9d9a9098-2d9e-4d67-a0bf-0f6eb0d3f2fa/analytics",
      "categories": [
        {
          "title": "Featured",
          "externalId": "featured"
        },
        {
          "title": "Homepage",
          "externalId": "homepage"
        }
      ]
    }
  ],
  "pageSize": 10,
  "currentPage": 1,
  "totalPages": 3
}

Response Fields#

Field Type Description
id string (GUID) Internal story identifier – use this when a workflow expects a storyId
externalId string Friendly identifier supplied by your systems
title string Story title (may be empty if not set)
createdAt string (ISO 8601) When the story was first created
publishedAt string (ISO 8601) Most recent publish timestamp, falling back to the story-level schedule
appOpenUrl string Deep link that launches the story in Storyteller apps (page segment stripped for safety)
thumbnailUrl string CDN-formatted URL to the story thumbnail
iconUrl string CDN-formatted square icon URL
cmsUrl string Direct link to the story form inside the Storyteller CMS
cmsAnalyticsUrl string CMS analytics dashboard for the story
categories array Assigned categories (see table below)

Category Object

Field Type Description
title string Display name
externalId string External identifier for the category

Pagination Object

Field Type Description
pageSize integer Number of items returned per page
currentPage integer Current page number
totalPages integer Total available pages

Usage Examples#

import fetch from 'node-fetch';

export async function listStories({
  apiKey,
  searchText,
  publishStatus,
  publishedSince,
  currentPage = 1,
  pageSize = 20
}) {
  const params = new URLSearchParams({ currentPage, pageSize });
  if (searchText) params.set('searchText', searchText);
  if (publishStatus) params.set('publishStatus', publishStatus);
  if (publishedSince) params.set('publishedSince', publishedSince);

  const response = await fetch(`https://integrations.usestoryteller.com/api/stories?${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_stories(
    api_key: str,
    search_text: str | None = None,
    publish_status: 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 publish_status:
        params['publishStatus'] = publish_status
    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/stories',
        headers={'x-storyteller-api-key': api_key},
        params=params,
        timeout=30
    )
    response.raise_for_status()
    return response.json()
using System.Net.Http.Json;

public sealed class StoryClient
{
    private readonly HttpClient _httpClient;

    public StoryClient(string apiKey)
    {
        _httpClient = new HttpClient
        {
            BaseAddress = new Uri("https://integrations.usestoryteller.com/")
        };
        _httpClient.DefaultRequestHeaders.Add("x-storyteller-api-key", apiKey);
    }

    public async Task<ListStoriesResponse> GetStoriesAsync(
        string? searchText = null,
        string? publishStatus = null,
        DateTime? publishedSince = null,
        int page = 1,
        int pageSize = 20)
    {
        var query = new Dictionary<string, string>
        {
            ["currentPage"] = page.ToString(),
            ["pageSize"] = pageSize.ToString()
        };
        if (!string.IsNullOrWhiteSpace(searchText)) query["searchText"] = searchText;
        if (!string.IsNullOrWhiteSpace(publishStatus)) query["publishStatus"] = publishStatus;
        if (publishedSince.HasValue) query["publishedSince"] = publishedSince.Value.ToString("o");

        var url = QueryHelpers.AddQueryString("api/stories", query);
        var response = await _httpClient.GetFromJsonAsync<ListStoriesResponse>(url);
        return response ?? new ListStoriesResponse();
    }
}
# Get stories published since January 15, 2025
curl -X GET "https://integrations.usestoryteller.com/api/stories?currentPage=1&pageSize=20&publishedSince=2025-01-15T00:00:00Z" \
  -H "x-storyteller-api-key: YOUR_API_KEY"

Get Story by External ID (GET /api/stories/{externalId})#

This legacy endpoint returns the same structure as the list endpoint for a single story. It is retained for backward compatibility, but new integrations should use the details endpoint below when they require page structures or deep metadata.

Get Story by ID (GET /api/stories/by-id/{id})#

Fetches a single story using its internal GUID identifier. This is useful when you have the story's internal ID from another Storyteller API response (such as workflow results or analytics data) and need to retrieve the story metadata.

Path Parameters#

Parameter Type Required Description
id string (GUID) Yes The internal story identifier

Example Response#

Returns the same structure as the list endpoint for a single story:

{
  "id": "9d9a9098-2d9e-4d67-a0bf-0f6eb0d3f2fa",
  "externalId": "homepage-story",
  "title": "Homepage Hero",
  "createdAt": "2025-02-07T11:24:39Z",
  "publishedAt": "2025-02-08T09:00:00Z",
  "appOpenUrl": "https://yourtenant.shar.estori.es/go/9d9a9098-2d9e-4d67-a0bf-0f6eb0d3f2fa",
  "thumbnailUrl": "https://cdn.yourtenant.net/stories/homepage-story/thumbnail.jpg",
  "iconUrl": "https://cdn.yourtenant.net/stories/homepage-story/icon-square.jpg",
  "cmsUrl": "https://yourtenant.usestoryteller.com/stories/9d9a9098-2d9e-4d67-a0bf-0f6eb0d3f2fa/form",
  "cmsAnalyticsUrl": "https://yourtenant.usestoryteller.com/stories/9d9a9098-2d9e-4d67-a0bf-0f6eb0d3f2fa/analytics",
  "categories": [
    {
      "title": "Featured",
      "externalId": "featured"
    }
  ]
}

Usage Examples#

import fetch from 'node-fetch';

export async function getStoryById(apiKey, storyId) {
  const response = await fetch(`https://integrations.usestoryteller.com/api/stories/by-id/${storyId}`, {
    headers: { 'x-storyteller-api-key': apiKey }
  });

  if (!response.ok) {
    throw await response.json();
  }

  return response.json();
}
import requests

def get_story_by_id(api_key: str, story_id: str) -> dict:
    response = requests.get(
        f"https://integrations.usestoryteller.com/api/stories/by-id/{story_id}",
        headers={'x-storyteller-api-key': api_key},
        timeout=30
    )
    response.raise_for_status()
    return response.json()
public async Task<StoryResponse?> GetStoryByIdAsync(string apiKey, Guid storyId)
{
    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/stories/by-id/{storyId}");
    if (!response.IsSuccessStatusCode)
    {
        var problem = await response.Content.ReadFromJsonAsync<ProblemDetails>();
        throw new InvalidOperationException(problem?.Detail ?? "Unable to fetch story");
    }

    return await response.Content.ReadFromJsonAsync<StoryResponse>();
}
curl -X GET "https://integrations.usestoryteller.com/api/stories/by-id/9d9a9098-2d9e-4d67-a0bf-0f6eb0d3f2fa" \
  -H "x-storyteller-api-key: YOUR_API_KEY"

Story Details by External ID (GET /api/stories/{externalId}/details)#

Use the details endpoint when you need the full story composition – ordered pages, action links, captions, and CDN-aware asset URLs. The response mirrors what the Storyteller CMS surfaces in the story form.

Path Parameters#

Parameter Type Required Description
externalId string Yes The external story identifier

Example Response#

{
  "id": "9d9a9098-2d9e-4d67-a0bf-0f6eb0d3f2fa",
  "externalId": "homepage-story",
  "title": "Homepage Hero",
  "createdAt": "2025-02-07T11:24:39Z",
  "publishedAt": "2025-02-08T09:00:00Z",
  "iconUrl": "https://cdn.yourtenant.net/stories/homepage-story/icon-square.jpg",
  "thumbnailUrl": "https://cdn.yourtenant.net/stories/homepage-story/thumbnail.jpg",
  "appOpenUrl": "https://yourtenant.shar.estori.es/go/9d9a9098-2d9e-4d67-a0bf-0f6eb0d3f2fa",
  "cmsUrl": "https://yourtenant.usestoryteller.com/stories/9d9a9098-2d9e-4d67-a0bf-0f6eb0d3f2fa/form",
  "cmsAnalyticsUrl": "https://yourtenant.usestoryteller.com/stories/9d9a9098-2d9e-4d67-a0bf-0f6eb0d3f2fa/analytics",
  "eyebrow": "Top Stories",
  "urlNames": ["homepage-hero", "daily-top"],
  "categories": [
    { "title": "Featured", "externalId": "featured" },
    { "title": "Homepage", "externalId": "homepage" }
  ],
  "pages": [
    {
      "id": "4a77d2a6-75f0-4b7a-ac2d-1b1cf3f28157",
      "type": "Image",
      "sortOrder": 1,
      "duration": 8000,
      "skippable": true,
      "hasAudio": false,
      "assetImageUrl": "https://cdn.yourtenant.net/stories/homepage-story/pages/01/asset.jpg",
      "assetVideoUrl": null,
      "playcardImageUrl": "https://cdn.yourtenant.net/stories/homepage-story/pages/01/playcard.jpg",
      "playcardVideoUrl": null,
      "actionLink": {
        "id": "e1da88ba-d38c-4ed1-b2ed-22a2cc7abcab",
        "actionType": "Web",
        "title": "Read the full article",
        "webUrl": "https://www.example.com/articles/homepage-hero",
        "iosUrl": null,
        "androidUrl": null,
        "tvosUrl": null,
        "rokuUrl": null,
        "fireTvUrl": null,
        "appStoreId": null,
        "playStoreBundleId": null,
        "sheet": null
      },
      "caption": {
        "remoteUrl": "https://cdn.yourtenant.net/stories/homepage-story/pages/01/captions.vtt",
        "localisations": [
          {
            "languageCode": "es",
            "remoteUrl": "https://cdn.yourtenant.net/stories/homepage-story/pages/01/captions.es.vtt"
          }
        ]
      }
    },
    {
      "id": "c6697311-3f32-492f-81e3-5de97df7bae9",
      "type": "Poll",
      "sortOrder": 2,
      "duration": 10000,
      "skippable": false,
      "hasAudio": true,
      "assetImageUrl": null,
      "assetVideoUrl": "https://cdn.yourtenant.net/stories/homepage-story/pages/02/video.mp4",
      "playcardImageUrl": null,
      "playcardVideoUrl": null,
      "actionLink": null,
      "caption": null
    }
  ]
}

Page Object Reference#

Field Description
type Page type (Image, Video, Poll, TriviaQuiz, etc.)
sortOrder Order within the story (1-based)
duration Display duration in milliseconds
skippable Whether the viewer can skip before the timer elapses
hasAudio Indicates that the page includes audio
assetImageUrl / assetVideoUrl CDN-formatted URLs for the primary media asset
playcardImageUrl / playcardVideoUrl Previews used in some clients
actionLink Optional CTA metadata (see below)
caption Caption resources including localisation URLs

Action Link Object

Field Description
actionType Destination type such as Web, Sheet, or Collection
webUrl / iosUrl / androidUrl / tvosUrl / rokuUrl / fireTvUrl Platform-specific deep links
sheet Inline sheet metadata (id, title) when applicable

Caption Object

Field Description
remoteUrl Primary caption file (usually WebVTT)
localisations Optional per-language caption sources

Usage Examples#

import fetch from 'node-fetch';

export async function getStoryDetails(apiKey, externalId) {
  const response = await fetch(`https://integrations.usestoryteller.com/api/stories/${externalId}/details`, {
    headers: { 'x-storyteller-api-key': apiKey }
  });

  if (!response.ok) {
    throw await response.json();
  }

  return response.json();
}
import requests

def get_story_details(api_key: str, external_id: str) -> dict:
    response = requests.get(
        f"https://integrations.usestoryteller.com/api/stories/{external_id}/details",
        headers={'x-storyteller-api-key': api_key},
        timeout=30
    )
    response.raise_for_status()
    return response.json()
public async Task<StoryDetails?> GetStoryDetailsAsync(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/stories/{externalId}/details");
    if (!response.IsSuccessStatusCode)
    {
        var problem = await response.Content.ReadFromJsonAsync<ProblemDetails>();
        throw new InvalidOperationException(problem?.Detail ?? "Unable to fetch story");
    }

    return await response.Content.ReadFromJsonAsync<StoryDetails>();
}
curl -X GET "https://integrations.usestoryteller.com/api/stories/homepage-story/details" \
  -H "x-storyteller-api-key: YOUR_API_KEY"

Story Details by ID (GET /api/stories/by-id/{id}/details)#

Retrieve the full story composition using its internal GUID identifier. This endpoint provides the same comprehensive details as the external ID version, including ordered pages, action links, captions, and CDN-aware asset URLs.

Path Parameters#

Parameter Type Required Description
id string (GUID) Yes The internal story 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 getStoryDetailsById(apiKey, storyId) {
  const response = await fetch(`https://integrations.usestoryteller.com/api/stories/by-id/${storyId}/details`, {
    headers: { 'x-storyteller-api-key': apiKey }
  });

  if (!response.ok) {
    throw await response.json();
  }

  return response.json();
}
import requests

def get_story_details_by_id(api_key: str, story_id: str) -> dict:
    response = requests.get(
        f"https://integrations.usestoryteller.com/api/stories/by-id/{story_id}/details",
        headers={'x-storyteller-api-key': api_key},
        timeout=30
    )
    response.raise_for_status()
    return response.json()
public async Task<StoryDetails?> GetStoryDetailsByIdAsync(string apiKey, Guid storyId)
{
    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/stories/by-id/{storyId}/details");
    if (!response.IsSuccessStatusCode)
    {
        var problem = await response.Content.ReadFromJsonAsync<ProblemDetails>();
        throw new InvalidOperationException(problem?.Detail ?? "Unable to fetch story");
    }

    return await response.Content.ReadFromJsonAsync<StoryDetails>();
}
curl -X GET "https://integrations.usestoryteller.com/api/stories/by-id/9d9a9098-2d9e-4d67-a0bf-0f6eb0d3f2fa/details" \
  -H "x-storyteller-api-key: YOUR_API_KEY"

Best Practices#

  1. Prefer the details endpoint when you need page structures, media URLs, or caption metadata.
  2. Cache CDN URLs sensibly – the Integration API already formats relative paths, so you can safely cache for short periods.
  3. Use publishStatus filters to separate Draft, Scheduled, and Published content during editorial workflows.
  4. Store both id and externalId to bridge internal CMS references with downstream workflows.
  5. Handle RFC 7807 errors – all error responses follow Problem Details.
  • Story Analytics – Retrieve all-time viewer, engagement, and per-page statistics
  • Categories API – Build category pickers to accompany the stories list
  • Executing Workflows – Use storyId metadata when running workflows
  • Clips API – Surface complementary clip content alongside stories