Catalog delta sync
If you mirror the scent catalog (scents, brands, variants) into your own system, don’t re-fetch the
entire catalog on every sync — use the changedSince query parameter, supported identically on
all three list endpoints:
GET /api/v1/scent/catalog/scents?changedSince=...GET /api/v1/scent/catalog/brands?changedSince=...GET /api/v1/scent/catalog/variants?changedSince=...
How it works
Section titled “How it works”changedSince takes an ISO-8601 UTC timestamp and returns only rows changed strictly after
that instant. When set, results are ordered by change time ascending (oldest-changed first)
instead of the endpoint’s normal default ordering — this matters because it means you can safely
paginate through a large batch of changes and stop wherever you like, resuming later from the
timestamp of the last row you actually processed.
GET /api/v1/scent/catalog/variants?changedSince=2026-08-01T00:00:00Z&pageSize=100Recommended sync loop
Section titled “Recommended sync loop”- On first sync, omit
changedSinceentirely and page through the full catalog, recording the response timestamp (or thelastModifiedDate/createdDateof the last row you processed) as your cursor. - On every subsequent sync, call the same endpoint with
changedSinceset to your last cursor. - Process every returned row, then advance your cursor to the latest change time you actually saw in that batch — not simply “now,” since a row could theoretically be committed with a change time slightly behind your sync’s start time under concurrent writes.
- If a batch returns a full page (
pageSizeitems), keep paging with the samechangedSincevalue until you get a partial or empty page — the endpoint does not auto-advance your cursor for you between pages.
async function syncSince(cursor) { let page = 1; let latestSeen = cursor; while (true) { const res = await client.get('/api/v1/scent/catalog/variants', { params: { changedSince: cursor, page, pageSize: 100 }, }); const { items, pagination } = res.data.payload; for (const item of items) { await upsertLocalVariant(item); const changedAt = item.lastModifiedDate ?? item.createdDate; if (changedAt > latestSeen) latestSeen = changedAt; } if (page >= pagination.totalPages) break; page++; } return latestSeen;}A row with no lastModifiedDate
Section titled “A row with no lastModifiedDate”A row that has never been updated since creation has a null lastModifiedDate — the endpoint
falls back to createdDate internally when evaluating changedSince, so a never-updated row is
still correctly included/excluded relative to your cursor. You don’t need to handle this case
specially on your side.