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:
titleandiconNamedescribe the metric.total/totalDisplayexpose the aggregated value.percentageexpresses the share of page views (where applicable).datais 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#
- Pair with story details to map
pageIdback to page metadata before rendering reports. - Respect timezones – timestamps arrive in Eastern Time to remain consistent with the CMS dashboards.
- Cache responsibly – analytics are sourced from hourly aggregates; polling every few minutes is sufficient.
- Handle Problem Details errors – missing external IDs or tenant configuration issues return RFC 7807 payloads.
- Separate presentation and storage – store raw JSON if you plan to support new metrics without redeploying code.
Related Documentation#
- Stories API – Fetch story details and ordered pages
- Clip Analytics – Complementary clip performance metrics
- Troubleshooting – Diagnose authentication and data issues