Skip to content

Change data feed

The change feed, list_changes, lists every change as an event: writes through the engine, hand edits with the rules they break, schema changes and, when asked for, tool calls. It works without history. Poll it from a position, or wait for the next event with wait_ms; a webhook has the engine post each event to a web address instead.

Change feed and webhooks shows how a program follows the feed.

list_changes returns numbered row changes across all tables, oldest first. It includes engine writes and hand edits (source: external). Keep the returned last and pass it as since next time to get only what’s new. wait_ms waits for the next change, a technique called long polling.

list_changes

{ "tables": ["film"], "limit": 3 }
Answer (74 lines)
{
"events": [
{
"seq": 3,
"at": "2026-10-12T16:52:36.274Z",
"table": "main.public.film",
"kind": "insert",
"op": "insert_row",
"path": "datanotes/main/public/film/Nosferatu.md",
"row_id": "01a13107-c92d-767d-9b60-f4e03575c27e",
"changes": {
"id": { "after": "01a13107-c92d-767d-9b60-f4e03575c27e" },
"title": { "after": "Nosferatu" },
"status": { "after": "to watch" }
},
"source": "engine",
"row": {
"id": "01a13107-c92d-767d-9b60-f4e03575c27e",
"title": "Nosferatu",
"status": "to watch"
},
"actor": { "session": "history", "harness": "docs-example", "auth": "owner" },
"migration_id": "20261012T165236-insert_row-insert-into-film"
},
{
"seq": 4,
"at": "2026-10-12T16:52:36.280Z",
"table": "main.public.film",
"kind": "insert",
"op": "insert_row",
"path": "datanotes/main/public/film/Metropolis.md",
"row_id": "01a13107-c934-747a-90f7-3becda86084f",
"changes": {
"id": { "after": "01a13107-c934-747a-90f7-3becda86084f" },
"title": { "after": "Metropolis" },
"status": { "after": "to watch" }
},
"source": "engine",
"row": {
"id": "01a13107-c934-747a-90f7-3becda86084f",
"title": "Metropolis",
"status": "to watch"
},
"actor": { "session": "history", "harness": "docs-example", "auth": "owner" },
"migration_id": "20261012T165236-insert_row-insert-into-film-2"
},
{
"seq": 5,
"at": "2026-10-12T16:52:36.287Z",
"table": "main.public.film",
"kind": "update",
"op": "update_row",
"path": "datanotes/main/public/film/Nosferatu.md",
"row_id": "01a13107-c92d-767d-9b60-f4e03575c27e",
"changes": {
"status": { "before": "to watch", "after": "watched" },
"rating": { "after": 4 },
"watched_on": { "after": "2026-10-03" }
},
"source": "engine",
"row": {
"id": "01a13107-c92d-767d-9b60-f4e03575c27e",
"title": "Nosferatu",
"status": "watched",
"rating": 4,
"watched_on": "2026-10-03"
},
"actor": { "session": "history", "harness": "docs-example", "auth": "owner" },
"migration_id": "20261012T165236-update_row-update-film"
}
],
"last": 5,
"head": 13
}

insert_row

{ "table": "film", "values": { "title": "His Girl Friday" } }

With since, the answer holds only the newer changes:

list_changes

{ "since": 13 }

Answer:

{
"events": [
{
"seq": 14,
"at": "2026-10-12T16:52:40.870Z",
"table": "main.public.film",
"kind": "insert",
"op": "insert_row",
"path": "datanotes/main/public/film/His Girl Friday.md",
"row_id": "01a13107-db21-7bf5-942a-c5107eb7fed3",
"changes": {
"id": { "after": "01a13107-db21-7bf5-942a-c5107eb7fed3" },
"title": { "after": "His Girl Friday" },
"status": { "after": "to watch" }
},
"source": "engine",
"row": {
"id": "01a13107-db21-7bf5-942a-c5107eb7fed3",
"title": "His Girl Friday",
"status": "to watch"
},
"actor": { "session": "history", "harness": "docs-example", "auth": "owner" },
"migration_id": "20261012T165240-insert_row-insert-into-film"
}
],
"last": 14,
"head": 14
}

Push changes to a web address with webhooks

Section titled “Push changes to a web address with webhooks”

create_webhook makes the engine send row changes in batches to a web address (URL), as POST requests. The engine signs each batch with a hash-based message authentication code (HMAC) of the body, in X-Datanotes-Signature. It retries until the receiver answers with a 2xx status. Only this answer returns the secret: give it to the receiver so it can check the signature.

create_webhook

{ "url": "http://127.0.0.1:39785/datanotes-hook", "tables": ["film"] }

Answer:

{
"id": "wh_bdde32fd8ee4",
"url": "http://127.0.0.1:39785/datanotes-hook",
"tables": ["film"],
"enabled": true,
"cursor": 14,
"created_at": "2026-10-12T16:52:40.874Z",
"failures": 0,
"status": "ok",
"secret": "<shown once>"
}

update_row

{
"table": "film",
"path": "datanotes/main/public/film/His Girl Friday.md",
"set": { "status": "watched" }
}

The receiver got this request:

{
"headers": {
"x-datanotes-signature": "sha256=d0c9b0450dd3eaec5843de6883d0c16ad058985fa459cff136d0c5035a4ccc1e"
},
"body": {
"webhook": "wh_bdde32fd8ee4",
"events": [
{
"seq": 15,
"at": "2026-10-12T16:52:40.883Z",
"table": "main.public.film",
"kind": "update",
"op": "update_row",
"path": "datanotes/main/public/film/His Girl Friday.md",
"row_id": "01a13107-db21-7bf5-942a-c5107eb7fed3",
"changes": { "status": { "before": "to watch", "after": "watched" } },
"source": "engine",
"row": {
"id": "01a13107-db21-7bf5-942a-c5107eb7fed3",
"title": "His Girl Friday",
"status": "watched"
},
"actor": { "session": "history", "harness": "docs-example", "auth": "owner" },
"migration_id": "20261012T165240-update_row-update-film"
}
],
"last": 15
}
}

list_webhooks (no arguments)

Answer:

{
"webhooks": [
{
"id": "wh_bdde32fd8ee4",
"url": "http://127.0.0.1:39785/datanotes-hook",
"tables": ["film"],
"enabled": true,
"cursor": 15,
"created_at": "2026-10-12T16:52:40.874Z",
"last_delivery": "2026-10-12T16:52:40.886Z",
"failures": 0,
"status": "ok"
}
]
}

delete_webhook

{ "id": "wh_bdde32fd8ee4" }

Answer:

{ "deleted": true }