---
title: "List a product's change events"
method: GET
path: "/products/{id}/events"
---

# List a product's change events

`GET /products/{id}/events`

## Request

- Base URL: `https://www.merchkit.com/api/v1`
- URL: `https://www.merchkit.com/api/v1/products/{id}/events`
- Auth: HTTP bearer

Per-entity change events from the append-only log — identical shape and envelope to the workspace feed (`GET /events`), scoped to one product. Newest-first; same `next_since` polling recipe.

Scope: `read:events`.

## Query parameters

- `since` string, date-time
- `limit` integer

## Response `200`

Change events for this product, newest first.

- object
  - `data` Event[]
    - `id` string, uuid, required
    - `type` 'entity.created' | 'entity.updated' | 'entity.deleted', required
    - `entity_id` string, uuid, required
    - `entity_type` string, nullable, required: Null once the entity has been deleted.
    - `attribute_key` string, nullable, required: The changed attribute for entity.updated; null otherwise.
    - `value` unknown, required
    - `actor` object, required
      - `kind` 'user' | 'api_key' | 'system', required
    - `created_at` string, date-time, required
  - `pagination` EventPagination: Event feeds page differently: newest-first with a `next_since` checkpoint instead of total/offset.
    - `limit` integer
    - `has_more` boolean: True when the window exceeded one page — recover by state (`filter[updated_at][gte]=<checkpoint>`), not by paging.
    - `next_since` string, date-time, nullable: The newest `created_at` on the page — your next `since` checkpoint (overlap by 1s and dedupe on event id).

## Other responses

- `404`: Product not found.

---

[API](https://skmtc.dev/merchkit/apis/merchkit-api.md) - [All operations](https://skmtc.dev/merchkit/apis/merchkit-api/llms.txt) - [OpenAPI document](https://skmtc.dev/merchkit/apis/merchkit-api/revisions/3062ca814757?raw)
