Skip to content

Story Analytics API#

The Story Analytics endpoint mirrors the insights surfaced inside the Storyteller CMS analytics dashboard. It delivers all-time metrics for a story, complete with per-page totals so you can build external reporting or internal dashboards without duplicating calculation logic.

Endpoints#

By External ID#

GET https://integrations.usestoryteller.com/api/stories/{externalId}/analytics
Parameter Type Required Description
externalId string Path External ID of the story to analyse

By ID#

GET https://integrations.usestoryteller.com/api/stories/by-id/{id}/analytics
Parameter Type Required Description
id string (GUID) Path Internal Storyteller story ID

This endpoint returns all available history. There are no additional query parameters for date ranges in the Integrations API. Apply client-side filtering if you need custom windows.

Example Response#

{
  "charts": [
    {
      "title": "Page Views",
      "iconName": "faFile",
      "total": 18425,
      "totalDisplay": "18,425",
      "percentage": 0,
      "percentageDisplay": "0.00",
      "data": [
        {
          "key": 1,
          "values": {
            "Page Views": 3625,
            "name": "Feb 04 2025, 10:00 AM"
          }
        },
        {
          "key": 2,
          "values": {
            "Page Views": 2741,
            "name": "Feb 04 2025, 11:00 AM"
          }
        }
      ]
    },
    {
      "title": "Click Throughs",
      "iconName": "chevron-up-circle",
      "total": 842,
      "totalDisplay": "842",
      "percentage": 4.57,
      "percentageDisplay": "4.57",
      "data": [
        {
          "key": 1,
          "values": {
            "Click Throughs": 178,
            "name": "Feb 04 2025, 10:00 AM"
          }
        }
      ]
    }
  ],
  "byPage": [
    {
      "pageId": "4a77d2a6-75f0-4b7a-ac2d-1b1cf3f28157",
      "sortOrder": 1,
      "type": "Image",
      "views": 6120,
      "shares": 248,
      "swipeUps": 421,
      "pollVotes": 0,
      "triviaAnswers": 0
    },
    {
      "pageId": "c6697311-3f32-492f-81e3-5de97df7bae9",
      "sortOrder": 2,
      "type": "Poll",
      "views": 5842,
      "shares": 194,
      "swipeUps": 137,
      "pollVotes": 987,
      "triviaAnswers": 0
    }
  ]
}

Charts Returned#

Chart Description Icon
Page Views Total story page impressions per hour (Eastern Time) faFile
Viewers Unique viewers derived from hourly stats user
Shares Share button taps across all pages share2
Click Throughs Combined swipe-up and action button taps chevron-up-circle
Poll Votes Total poll votes on poll pages faFile
Poll Shares Share button taps on poll results pages share2
Trivia Quiz Completions Trivia quiz completion events faFile
Trivia Quiz Shares Shares from trivia result pages share2

Reading the Chart Data#

Each chart entry follows the AnalyticsChartDto structure used across Storyteller:

  • title and iconName describe the metric.
  • total / totalDisplay expose the aggregated value.
  • percentage expresses the share of page views (where applicable).
  • data is an ordered array of hourly buckets. The Integrations API converts timestamps to Eastern Time to match the CMS dashboards.

Per-Page Totals (byPage)#

The byPage array mirrors the “Pages” tab inside the CMS analytics view. Use the pageId to join against the pages collection from GET /api/stories/{externalId}/details.

Field Description
pageId Identifier of the page within the story
sortOrder Ordering (matches the story details payload)
type Page type (Image, Video, Poll, TriviaQuiz, etc.)
views Sum of OpenedPage events for the page
shares Share button taps for the page
swipeUps Combined swipe-up and action button taps
pollVotes Votes captured on poll pages
triviaAnswers Trivia question answers on quiz pages

Usage Examples#

import fetch from 'node-fetch';

export async function getStoryAnalytics(apiKey, externalId) {
  const response = await fetch(`https://integrations.usestoryteller.com/api/stories/${externalId}/analytics`, {
    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

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

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

Best Practices#

  1. Pair with story details to map pageId back to page metadata before rendering reports.
  2. Respect timezones – timestamps arrive in Eastern Time to remain consistent with the CMS dashboards.
  3. Cache responsibly – analytics are sourced from hourly aggregates; polling every few minutes is sufficient.
  4. Handle Problem Details errors – missing external IDs or tenant configuration issues return RFC 7807 payloads.
  5. Separate presentation and storage – store raw JSON if you plan to support new metrics without redeploying code.