# Code examples

Read and revalidate a feed with TypeScript, curl or Python.

Source: [Code examples](/examples/)

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

## TypeScript [#typescript]

Save this as `example.ts`, then run `node --experimental-strip-types example.ts` with Node.js 22.22+. It uses built-in `fetch` and makes two reads. Use the same request and state handling in a browser application's TypeScript code.

```typescript
// Save as example.ts; run with node --experimental-strip-types example.ts.
// Node.js 22.22+. Public sample; no key or dependencies.
type FeedSnapshot = {
  fixtureId: string;
  feedKey: string;
  snapshotAt: string;
  items: unknown[];
};

const url = 'https://api.live.storyos.ai/v1/fixtures/0042500401/feeds/pulse';
let etag: string | null = null;
let snapshot: FeedSnapshot | undefined;

async function pollOnce(): Promise<boolean> {
  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() as FeedSnapshot;
  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);
```

The type covers the snapshot envelope; item content depends on its definition. See [Feed model](/markdown/feed-model.md) and [Item types](/markdown/item-types.md) for the payload contract.

## curl [#curl]

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

## Python [#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 [#read-a-live-game]

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

```bash
# Published live example; replace this with another NBA game ID as needed. No key.
FIXTURE_ID=0012600066
curl --fail-with-body --silent --show-error \
  "https://api.live.storyos.ai/v1/fixtures/$FIXTURE_ID/feeds/pulse"
```

## Keep request scopes separate [#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.
