Skip to main content

Deltas & Changed Rows

Read a delta between two DB snapshot captures: per-table added/modified/removed counts, partial-vs-complete contract, examined flags, and paged changed rows.

A delta is the computed difference between two consecutive captures of one source: per table, how many rows were added, modified, and removed, plus schema drift (columns added or removed) and a column-transition profile of the modified values. GET /v1/db-snapshots/deltas/:deltaId returns the delta's totals, its per-table stats joined with examination facts from the capture manifest, the snapshot pair it compares, and the column profile.

The single most important field is status. A delta row is created when its capture starts and grows as tables finish, so a delta whose capture has not completed reports status: "partial" — and its counts are then a floor, not a result. By counts alone, "still scanning" is indistinguishable from "examined everything, found nothing"; partial is that distinction made explicit. When you need a number you can act on, follow last_complete_delta_id, which the response carries precisely for that hop.

The same discipline applies per table. A table the scan cadence deferred this capture appears in tables[] with examined: false — its zero counts mean "not looked at", not "unchanged" — along with last_full_scan_at so its staleness is readable. A table that is absent from tables[] was examined and found clean: absent means clean, present-but-unexamined means deferred, and the two are never confusable. mode says how an examined table was read (full, incremental, changefeed, or skipped), and fallback_reason names why a table lost its fastest configured path — diagnostic only.

GET /v1/db-snapshots/deltas/:deltaId/changes pages through the actual changed rows — id, table_key, pk, change_type (added | modified | removed), and full before/after values. The table query parameter is required: a whole-delta page would interleave tables whose row shapes differ, and the row counts you need to decide what to fetch already live on delta.tables[]. limit defaults to 50 and is clamped to 500; the response echoes the paging it actually applied (limit, offset) plus total, the changed-row count for this table only.

Two caps to know: persisted change rows are bounded per table, so a very large diff sets changes_truncated: true on that table's stats while the counts stay exact — you can trust added/modified/removed even when not every row was kept. And the column_profile summarizes modified values as the top discrete old→new transitions per column, collapsing free-text columns into one aggregate line — a quick shape-of-the-change read before you page rows.

Never treat a zero as "no changes" without checking two flags: delta.status ("partial" means the counts are a floor) and the table's examined flag (false means the scan cadence deferred it). An absent table is the only zero that means examined-and-clean.
GET/v1/db-snapshots/deltas/:deltaId

Path parameters

deltaId*uuidThe delta id, from a source's timeline, latest_delta, or last_complete_delta_id.

Response (200) — delta detail (abridged)

{
  "delta": {
    "id": "b4c6e8a0-1d3f-4b5c-9e7a-2f4d6b8c0e1a",
    "connection_id": "8d2f1c4a-6b3e-4f7d-9a1c-5e8b2d4f6a0c",
    "from_snapshot_id": "9e1c3a5f-7b9d-4e2c-8a4f-6b0d2e4c6a8f",
    "to_snapshot_id": "3f9a7b1d-2c4e-4a6f-8b0d-1e3c5a7f9b2d",
    "computed_at": "2026-08-29T02:03:41.508Z",
    "status": "complete",
    "last_complete_delta_id": "b4c6e8a0-1d3f-4b5c-9e7a-2f4d6b8c0e1a",
    "totals": { "added": 12, "modified": 48, "removed": 3, "tables_touched": 4, "tables_total": 21, "tables_skipped": 2, "drift": false },
    "tables": [
      {
        "table_key": "public.customers",
        "added": 2, "modified": 41, "removed": 0,
        "changes_truncated": false,
        "examined": true,
        "last_full_scan_at": "2026-08-27T02:00:11.020Z",
        "mode": "incremental",
        "fallback_reason": null,
        "columns_added": [], "columns_removed": []
      },
      {
        "table_key": "public.audit_log",
        "added": 0, "modified": 0, "removed": 0,
        "changes_truncated": false,
        "examined": false,
        "last_full_scan_at": "2026-08-22T02:00:09.410Z",
        "mode": "skipped",
        "fallback_reason": null,
        "columns_added": [], "columns_removed": []
      }
    ]
  },
  "from_snapshot": { "id": "9e1c3a5f-7b9d-4e2c-8a4f-6b0d2e4c6a8f", "captured_at": "2026-08-28T02:00:03.114Z" },
  "to_snapshot": { "id": "3f9a7b1d-2c4e-4a6f-8b0d-1e3c5a7f9b2d", "captured_at": "2026-08-29T02:00:04.221Z", "status": "ready" },
  "column_profile": [
    { "table_key": "public.customers", "column": "credit_limit", "before": "50000", "after": "75000", "count": 17, "aggregate": false },
    { "table_key": "public.customers", "column": "notes", "count": 24, "aggregate": true }
  ]
}

Paged changed rows

GET/v1/db-snapshots/deltas/:deltaId/changes

Query parameters

table*stringThe table key to page (schema.table; bare name on MySQL). Missing → 400 ("`table` is required.").
limitintegerPage size; clamped to 500. The response echoes the limit actually applied. Default: 50
offsetintegerRows to skip. Rows are ordered by table_key, change_type, pk for stable paging. Default: 0

curl + response

curl -s "https://api.talonic.com/v1/db-snapshots/deltas/b4c6e8a0-1d3f-4b5c-9e7a-2f4d6b8c0e1a/changes?table=public.customers&limit=2" \
  -H "Authorization: Bearer tlnc_your_api_key"

{
  "data": [
    {
      "id": "5c7e9a1b-3d5f-4c8e-a0b2-4d6f8a0c2e4a",
      "table_key": "public.customers",
      "pk": "10442",
      "change_type": "modified",
      "before": { "id": 10442, "credit_limit": 50000, "status": "active" },
      "after": { "id": 10442, "credit_limit": 75000, "status": "active" }
    },
    {
      "id": "1e3a5c7d-9b1f-4a6c-8e0a-2c4e6a8d0b2f",
      "table_key": "public.customers",
      "pk": "10981",
      "change_type": "added",
      "before": null,
      "after": { "id": 10981, "credit_limit": 20000, "status": "active" }
    }
  ],
  "table": "public.customers",
  "total": 43,
  "limit": 2,
  "offset": 0
}

Errors

Error responses

400VALIDATION_ERRORMissing table on /changes, or a master-view credential.
401unauthorizedMissing or invalid API key.
404RESOURCE_NOT_FOUNDUnknown delta id — including a failed capture's placeholder delta, which reads as not found rather than as data.

Frequently asked questions

When exactly is a delta partial, and what should my integration do about it?+
A delta is partial while the capture writing it has not reached ready — its counts grow table by table and are a floor. Automations should either wait for the source's capturing flag to clear, or follow last_complete_delta_id (returned on the delta itself and on the source) to the newest delta whose counts are final.
A table shows all zeros — did nothing change?+
Check examined first. examined: false means the scan cadence deferred the table this capture: its zeros mean "not looked at", and last_full_scan_at tells you how stale it is. Only a table that is absent from tables[] entirely was examined and found clean — the API is designed so a deferred zero and a clean zero can never be confused.
Why must I pass ?table on the changes endpoint?+
A whole-delta page would interleave rows from tables with different shapes, which is useless to render and awkward to process. The per-table counts on delta.tables[] already tell you which tables have changes and how many, so the intended flow is: read the delta, pick a table, page its rows.
Can I trust the counts when changes_truncated is true?+
Yes. The per-table cap bounds how many change rows are persisted with before/after values, not how the diff is counted — added/modified/removed stay exact. Truncation only means you cannot page every individual row of a very large diff; the record-history endpoint can still pull specific rows' events.
What does drift mean in the totals?+
Schema drift: the capture observed columns added or removed on at least one table relative to the previous capture. The per-table columns_added and columns_removed arrays name them. Drift is worth alerting on — an upstream schema change is the most common cause of downstream matching and mapping surprises.