StorytellerContent API

Code examples

Working curl, JavaScript and Python reads of the public sample.

These public examples read fixture 0042500401, feed pulse. They require no key. Run them as written against the public API.

curl

curl
curl --fail-with-body --silent --show-error 'https://api.live.storyos.ai/v1/fixtures/0042500401/feeds/pulse'

JavaScript

Run in Node.js 22+ or a browser developer console. It makes two bounded requests; it does not choose a polling interval.

javascript
// Node.js 22+, or a browser's developer console. Public sample; no key.
const url = 'https://api.live.storyos.ai/v1/fixtures/0042500401/feeds/pulse';
let etag;
let snapshot;

async function pollOnce() {
  const response = await fetch(url, {
    headers: etag ? { 'If-None-Match': etag } : {},
    cache: 'no-store',
  });
  if (response.status === 304) return false; // Keep the current snapshot.
  if (!response.ok) {
    throw new Error('HTTP ' + response.status + ': ' + await response.text());
  }
  const next = await response.json();
  snapshot = next; // Replace the whole snapshot, even if items is empty.
  etag = response.headers.get('ETag');
  return true;
}

(async () => {
  console.log('Updated:', await pollOnce());
  console.log(snapshot.fixtureId, snapshot.items.length, 'items');
  console.log('Updated:', await pollOnce()); // false when unchanged
})().catch(console.error);

Python

Python 3, using the standard library. urllib treats 304 as an HTTPError, so the example handles that before rethrowing other failures.

python
# Python 3. Standard library only. Public sample; no key.
import json
from urllib.error import HTTPError
from urllib.request import Request, urlopen

url = 'https://api.live.storyos.ai/v1/fixtures/0042500401/feeds/pulse'
etag = None
snapshot = None

def poll_once():
    global etag, snapshot
    headers = {'User-Agent': 'Storyteller-Content-API-Example/1.0', 'Accept': 'application/json'}
    if etag:
        headers['If-None-Match'] = etag
    try:
        with urlopen(Request(url, headers=headers), timeout=15) as response:
            next_snapshot = json.load(response)
            next_etag = response.headers.get('ETag')
    except HTTPError as error:
        if error.code == 304:
            return False  # Keep the current snapshot.
        raise  # Keep state and let the caller handle the error.
    snapshot = next_snapshot  # Includes replacement with an empty items list.
    etag = next_etag
    return True

print('Updated:', poll_once())
print(snapshot['fixtureId'], len(snapshot['items']), 'items')
print('Updated:', poll_once())  # False when unchanged

Read a live game

Set the live game's NBA game ID. No key is needed.

live
# A live game's feed: set FIXTURE_ID to its NBA game ID. No key.
: "${FIXTURE_ID:?Set a live NBA game ID}"
curl --fail-with-body --silent --show-error \
  "https://api.live.storyos.ai/v1/fixtures/$FIXTURE_ID/feeds/pulse"

Keep request scopes separate

The examples keep state for one fixed public URL. Production clients need independent snapshot/ETag state for each exact fixture/feed/query combination. Reset that state when switching replay bounds. A failed request preserves prior state; decide how your application shows stale data and schedules bounded retries.

On this page