# Polling and caching

Revalidate with ETags and keep your state consistent.

Source: [Polling and caching](/polling/)

Continuous polling is supported. Public feed reads have no application rate limit, request quota or minimum polling interval, and you do not need to agree an interval with Storyteller. Poll as frequently as your application needs, including immediately after the previous request completes. Use conditional reads to avoid downloading an unchanged snapshot.

## Conditional reads [#conditional-reads]

JSON responses carry an `ETag`. Keep its exact value, including quotes, and send it as `If-None-Match` on the next request to the same feed and replay query. When the representation is unchanged, the API returns `304 Not Modified` with no body.

```bash
# Run both calls in the same shell. The second should return 304 with no body.
url='https://api.live.storyos.ai/v1/fixtures/0042500401/feeds/pulse'
etag=$(curl --fail --silent --show-error --head "$url" | \
  awk 'tolower($1) == "etag:" {sub(/\r$/, "", $2); print $2}')
curl --silent --show-error --include --header "If-None-Match: $etag" "$url"
```

Handle `304` before parsing JSON. It means your previous snapshot remains current, so it must not clear displayed items. An ETag is an opaque validator; do not compute revisions or timing from it.

## Public and live response caching [#public-and-live-response-caching]

| Response           | Cache-Control                        | How to use it                                                                                                                           |
| ------------------ | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
| Public JSON sample | `public, max-age=0, must-revalidate` | Revalidate before reusing the HTTP representation.                                                                                      |
| Live snapshot      | `no-store`                           | Do not store the HTTP response in shared caches. Maintain your application’s prior integration state and ETag for conditional requests. |
| Errors             | `no-store`                           | Handle the failure while retaining the last valid application snapshot.                                                                 |

The same cache policy and ETag are returned on an unchanged conditional read. Weak validators, comma-separated lists and `*` are accepted by `If-None-Match`; using the exact returned tag is the simplest option.

## Polling state [#polling-state]

The [TypeScript and Python examples](/markdown/examples.md) make two reads. They replace state only after a successful full JSON response and preserve it on `304`. Use the same state handling in your polling loop.

## Scheduling reads and retries [#scheduling-reads-and-retries]

* Keep one request in flight for each feed and replay query. Schedule the next read after the previous request finishes so an older response cannot overwrite a newer snapshot.
* On a network failure or `5xx` response, retain the last valid snapshot and retry with exponential backoff and jitter. Cap the delay to suit your application and return to the usual interval after a successful response. Respect `Retry-After` if an intermediary supplies it.
* Correct a `400` request before retrying. For `404`, check the fixture and feed key; a live feed may not have its first published item yet.
