# Feed model

A complete snapshot of the content you can use now.

Source: [Feed model](/feed-model/)

A `200` is a complete snapshot, not a page, patch or event stream. It has no pagination or continuation cursor.

| Field        | Meaning                                                                                                                                                        |
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `fixtureId`  | Your integration’s fixture identifier; the NBA examples use game IDs.                                                                                          |
| `feedKey`    | The named feed for that fixture. Public samples use `pulse`.                                                                                                   |
| `snapshotAt` | Timestamp associated with the snapshot. Unfiltered live feeds use the latest commit time. Sample feeds use their bundled timestamp; replay has separate rules. |
| `items`      | Every currently consumable item for this response scope, in delivery order.                                                                                    |

## Order and time [#order-and-time]

Items are ordered by the active revision’s `publishedAt`, newest first, then `id` ascending as a stable tie-break. This is delivery order. Use `effectiveAt` when placing an item on the fixture timeline.

| Item time     | Meaning                                                       |
| ------------- | ------------------------------------------------------------- |
| `effectiveAt` | The item’s logical fixture-timeline anchor.                   |
| `asOf`        | The information cutoff for the item.                          |
| `publishedAt` | Publication time of this revision; determines delivery order. |

In the generated NBA samples, all three times are the play’s event time and represent example publication. Live publication need not occur at that same instant.

## Revisions [#revisions]

A rewritten item keeps its `id` and original `effectiveAt`, and increments its positive integer `revision`. Replace the item with the new revision; do not append a duplicate. Its `definitionVersion` describes the shape of its content, independently of its revision. A distinct content item has a distinct ID.

## Withdrawals [#withdrawals]

A withdrawn item is absent from the next successful snapshot. The read response does not include a withdrawal marker or reason. Remove local items that are no longer present. Do this only after a successful full response; a `404` or other error is not an empty snapshot.

Withdrawn items remain excluded even when replay parameters ask for earlier publications. Replay is not an archive of removed material.

## Application state [#application-state]

1. Retain the current snapshot and its ETag for one exact feed request.
2. On `200`, atomically replace them with the new snapshot and ETag.
3. On `304`, keep them.
4. On an error, keep them while handling the error; show any resulting staleness appropriately.

Keep separate state for each fixture, feed and replay query. Reset state when changing that scope. See [Item types](/markdown/item-types.md) for the generic envelope and nested content fields.

## Action numbers and supporting values [#action-numbers-and-supporting-values]

Where a definition includes `content.actionNumber`, it is an integer, not a quoted number. NBA definitions use it to attach an item to a play. A definition without this field is excluded when `atAction` is requested.

NBA statistics carry a machine `value`, a presentation `displayValue`, an explicit `scope` and a statistic `unit`. Use these fields rather than inferring units or comparison scope from copy.

## Entities and assets [#entities-and-assets]

Entities use `id`, `kind` and `displayName`. Assets use `id`, `kind`, `href`, `mediaType` and `altText`. All definition versions share these shapes.

Resolve an asset's `href` against the URL of the feed response that contains it. This supports absolute URLs, API-root paths and relative paths. Portland supplies `../assets/...`, which resolves from `/v1/fixtures/0022501147/feeds/pulse` to `/v1/fixtures/0022501147/assets/...`.

```javascript
// Resolve relative assets using the feed response URL, not just the API origin.
const response = await fetch('https://api.live.storyos.ai/v1/fixtures/0022501147/feeds/pulse');
if (!response.ok) throw new Error('HTTP ' + response.status);
const feed = await response.json();
const asset = feed.items.flatMap(item => item.assets)[0];
const assetUrl = new URL(asset.href, response.url);
const assetResponse = await fetch(assetUrl);
if (!assetResponse.ok) throw new Error('Asset HTTP ' + assetResponse.status);
console.log(assetUrl.href, assetResponse.headers.get('Content-Type'));
```

Use `altText` when displaying an asset. Treat generated copy as text when rendering it.
