The sync cadence is a property of the upstream pipeline, not something the API
configures or exposes — it has no machine-enforced schedule of its own to verify
against. Do not assume a fixed refresh interval; always check
last_sync_at (see
below) instead.TRUNCATE + Reload semantics
On every sync run, each table is dropped and reloaded in full — there is no row-level incremental update at the table level. This has two direct consequences for integrators:- No partial sync. A table is either fully replaced by the latest sync, or it isn’t
touched at all. There is no mechanism to sync “only the rows that changed” within a
single table —
GET /v1/changes(below) tells you which tables changed, not which rows. - Schema changes are always breaking for cumulative tables. For tables that
accumulate history across reloads (for example
contract_ends), a schema change cannot be applied additively — TRUNCATE + Reload always replaces the entire table content, so there is no way to migrate old rows to a new shape in place. Any schema change to such a table is treated as a breaking change requiring a new API version, with both the old and new table shapes synced in parallel during a deprecation window.
When last_sync_at is set
last_sync_at is set only when a sync completes successfully for that table — it stays
NULL until the first successful sync, and a failed or in-progress sync does not advance
it. This means:
- A table with no successful sync yet will never appear in
GET /v1/changes, regardless of how recently a sync was attempted. last_sync_atreflects table-level completion, not the modification time of any individual row inside that table.
What GET /v1/changes actually checks
GET /v1/changes?since=<timestamp> returns a table only if it has completed a successful sync
with last_sync_at > since — a strict greater-than comparison, not >=.
The ETag response header is derived from the maximum last_sync_at across all of a customer’s
synced tables plus the since value (normalised to UTC) and the category filter. A request
with a different since or category therefore never matches an ETag issued for another one.
Polling with If-None-Match lets you skip a request entirely when nothing has
synced since your last poll (see Delta Sync for the full polling
pattern).
Practical implications for your integration
- Poll
/v1/changes, don’t guess a schedule. Because there is no documented, enforced sync interval, build your integration to react to what/v1/changesreports rather than assuming data updates at a fixed time of day. - A changed table means “re-fetch everything for this table,” not “here are the diffs.”
Row-level change detection does not exist yet — when a table appears in
/v1/changes, the safest action is a full re-fetch of that category or raw endpoint. - Treat
last_sync_atas a freshness signal, not a change log. It tells you the table was successfully reloaded at that time; it does not tell you which rows differ from the previous version.