---
title: "What changed on the catalogue, newest first"
method: GET
path: "/api/v1/variants/events"
tags: ["variants"]
---

# What changed on the catalogue, newest first

`GET /api/v1/variants/events`

## Request

- Base URL: `https://api.app.tightly.io`
- URL: `https://api.app.tightly.io/api/v1/variants/events`
- Auth: HTTP bearer

The change log of the catalogue: one entry per recorded change on any variant, each with `id`, `variant_id`, `event_type`, `event_date`, `previous_value`, `new_value`, a typed `metadata` object, and the `product_name`, `variant_name` and `sku` the change happened to. `created_at`, `created_by_user_id` and `location_id` are null where the change came from a sync rather than a person or a place.

`event_type` today is one of price_change, cost_change, stock_change, stockout, restock, status_change, supplier_change, lead_time_change, moq_change, velocity_shift, tag_change, managed_change, po_created, variant_created, variant_image_change, forecast_override (a planner edited this variant's demand plan) and forecast_event_declared (a sales-velocity event covering it was declared, changed or cancelled). The list is open, tolerate a type you do not know rather than failing on it, and takes a comma-separated list to narrow to several at once.

`start_date` and `end_date` are ISO dates and bound the range. `end_date` defaults to today; `start_date` unset reads from the beginning of what is kept.

Paging is `offset` and `limit`, limit at most 100 and 8 by default. `total_count` is the whole matching set, so page on that.

It is the poll to run on a schedule: ask for the types you care about since your last run and apply what comes back, rather than diffing the whole catalogue. For one variant's own timeline, read that variant's events.

Scope: `products:read`.

## Query parameters

- `event_type` string
- `start_date` string, date
- `end_date` string, date
- `limit` integer
- `offset` integer

## Headers

- `Tightly-Version` string

## Response `200`

A page of catalogue changes, with the size of the whole matching set.

- object
  - `data` GetOrgSKUEventsPayload
    - `events` OrgSKUEventItem[], required
      - `created_at` string, nullable
      - `created_by_user_id` string, nullable
      - `event_date` string, required
      - `event_type` string, required
      - `id` string, required
      - `location_id` string, nullable
      - `metadata` object, required
      - `new_value` string, required
      - `previous_value` string, nullable
      - `product_name` string, nullable
      - `sku` string, nullable
      - `variant_id` string, required
      - `variant_name` string, nullable
    - `total_count` integer, required
  - `message` object: severity, service and desc.

## Other responses

- `400`: A date could not be read, or offset or limit is outside its range.
- `401`: No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, because telling a caller which is which maps the surface for them.
- `403`: The key may not reach this operation. `scope_missing` when the key does not hold Products; `ip_not_allowed` when the caller's address is outside the key's allowlist. `plan_excludes` when the organisation's plan does not include the public API itself, which is sold with Essentials and is asked of every key minted under that gate. A key minted before the gate existed is never refused by it, whatever the plan holds. Products is ungated, so that is the only plan reason served here.
- `429`: The key has spent its allowance for this minute. 600 reads and 120 writes a minute, counted per key, refused calls included. `Retry-After` says how long to wait.
- `503`: The database is briefly out of reach, which is what a failover or a full connection pool looks like from outside. Wait the seconds `Retry-After` names and send the request again. A read is safe to repeat. A write is not, because the connection was lost in the middle of it, so read the object back before sending it a second time. Branch on the status rather than on `code`, which is `POSTGRES_UNAVAILABLE` from one of the two seams that raise this and `SERVICE_UNAVAILABLE` from the other.

---

[API](https://skmtc.dev/tightly/apis/tightly-api.md) · [All operations](https://skmtc.dev/tightly/apis/tightly-api/llms.txt) · [OpenAPI document](https://skmtc.dev/tightly/apis/tightly-api/revisions/199f7eb35afb?raw)
