> ## Documentation Index
> Fetch the complete documentation index at: https://api.nektir.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Observed changes and history

> Consume product, price, status, presence, and reported-inventory changes without treating observations as transactions.

`GET /v1/changes` returns immutable events created in the same transaction as a Nektir publication. It can be used as a statewide newest-first feed or filtered to one product with `product_id` or `sku`.

```bash theme={"dark"}
curl "https://API_HOST/v1/changes?sku=347210&limit=25"
```

## What an event means

Every event describes a difference between two published Nektir observations. Its `times.retrieved_at` is when Nektir retrieved the public response, while `times.published_at` is when the validated observation became API-visible. `times.source_updated_at` remains `null` unless Utah DABS supplies an update time.

Inventory decreases are **not inferred sales or purchases**. Likewise, `store_inventory_first_observed` establishes a baseline and must not be labeled a restock. A catalog-presence transition does not establish zero inventory.

The `semantics` field is a machine-readable guardrail:

* `reported_observation_change_not_inferred_sale` applies to changed aggregate or store inventory.
* `baseline_observation_not_restock` applies to the first retained store observation.
* `source_presence_change_missing_does_not_imply_zero_inventory` applies to catalog-presence transitions.
* `published_value_change` and `published_presentation_change` describe differences in the Nektir projection.

## Event types

| Type                             | Meaning                                                                                      |
| -------------------------------- | -------------------------------------------------------------------------------------------- |
| `product_first_observed`         | The product first entered tracked Nektir history.                                            |
| `price_changed`                  | The published price changed. Currency remains USD.                                           |
| `status_changed`                 | The source status code or raw status changed.                                                |
| `product_presentation_changed`   | A published name, reviewed brand, category, or package-size field changed.                   |
| `aggregate_inventory_changed`    | A DABS-reported statewide warehouse, store, on-order, or in-stock aggregate changed.         |
| `store_inventory_first_observed` | Nektir established the first retained baseline for a product/store pair.                     |
| `store_inventory_changed`        | A store-level reported state, quantity, or eligibility value changed.                        |
| `catalog_presence_changed`       | Complete-source presence moved among present, missing-review, retired, or reinstated states. |

Use the comma-separated `types` filter when an application needs only selected event classes.

## Stable pagination

Every response is capped at the publication revision in `X-Publication-Revision`. The first page also freezes the largest eligible event ID within that revision. Follow `links.next` or return `meta.page.next_cursor` unchanged. Events published concurrently or afterward are excluded from that pagination walk and appear when the application starts a new walk without the old cursor.

History begins at `meta.history.tracking_started_at`; Nektir does not fabricate a backfill for time before tracking began. `earliest_available_at`, `latest_available_at`, and `retained_event_count` describe events matching the request filters and the first page's frozen upper bound. `retention_days` states the current storage policy. Expired cursors return `410`, just like other collection endpoints.

The current pilot retains change events for **400 days** (about 13 months). The value is returned by the API rather than assumed by clients and can be increased after measured event volume and storage cost are known. Any shorter value shown in generated examples is not the runtime policy.

## Building a poller

1. Poll without a cursor and record event `id` values your application has processed.
2. Follow every `links.next` value to finish the frozen page set.
3. Deduplicate by event `id`; delivery is read-based and should be treated as at least once by consumers.
4. On the next poll, request a new first page. Use `since` as a retrieval-time optimization, not as the deduplication key.

The v1 API does not push webhooks or guarantee that every upstream intermediate state was observable. It reports only states Nektir successfully retrieved, validated, and published.
