Catalog delta sync
Efficiently keep your own copy of the scent catalog in sync with changedSince.
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/scents?changedSince=...GET /api/v1/scent/brands?changedSince=...GET /api/v1/scent/variants?changedSince=...
How it works
Section titled “How it works”changedSince takes an ISO 8601 timestamp and returns only rows changed strictly after that
instant. A value without an offset is read as UTC, and a date-only value (2026-08-01) means
2026-08-01T00:00:00Z; a value that doesn’t parse returns 400 VALIDATION_INVALID_DATE (it is never
silently ignored, so a typo can’t turn a delta sync into a full download). When set, results are
ordered by change time ascending (oldest-changed first) instead of the endpoint’s normal default
ordering, so you can page through a large batch of changes in a stable order.
GET /api/v1/scent/variants?changedSince=2026-08-01T00:00:00Z&pageSize=100The partner catalog rows don’t carry a change timestamp of their own, so your cursor is the time you started the previous sync, not a value read from the rows.
Recommended sync loop
Section titled “Recommended sync loop”- On first sync, note the current UTC time, then omit
changedSinceentirely and page through the full catalog. The time you noted is your cursor. - On every subsequent sync, note the current UTC time again, then call the same endpoint with
changedSinceset to your previous cursor — minus a few seconds of overlap, so a row committed while your last sync was running is never missed. Re-applying a row you already have is harmless if your local write is an upsert byid. - Page through until you get a partial or empty page (
page >= pagination.totalPages), keeping the samechangedSinceon every page — the endpoint does not advance your cursor for you. - When the whole run succeeded, store the time you noted in step 2 as the new cursor.
async function syncSince(cursor) { const startedAt = new Date(); const since = new Date(cursor.getTime() - 5_000); // 5 s of overlap let page = 1; while (true) { const res = await client.get('/api/v1/scent/variants', { params: { changedSince: since.toISOString(), page, pageSize: 100 }, }); const { items, pagination } = res.data.payload; for (const item of items) await upsertLocalVariant(item); // keyed by item.id if (page >= pagination.totalPages) break; page++; } return startedAt; // the next cursor}pageSize accepts 1 to 100 (default 20); anything else returns 400 VALIDATION_INVALID_PAGE_SIZE.

