Capability · Change feed

Every change, since a timestamp.

Poll GET /changes?since=… for a diff of everything that moved, or register a webhook and get pushed — the watchtower turns 30+ registers into one revalidatable stream.

GET /api/v1/changes?since={ts}

What you get

Built for production drug data

The ingestion watchtower records every field-level change as it stages, validates and promotes data from 30+ public registers. You consume that as a diff: GET /api/v1/changes?since={timestamp} returns everything created, updated or removed since you last synced.

Prefer push? Register a webhook and the same events arrive at your endpoint. Either way you keep a local mirror current without re-crawling 610k rows — and you only pay for what moved.

Diff

Since-timestamp polling

Pass the cursor from your last sync; get back created / updated / removed entries with the changed fields. Idempotent and resumable.
Push

Webhooks

POST /api/v1/webhooks to subscribe, DELETE to unsubscribe. Events are signed so you can verify they came from us.
Watchtower

Schema-drift alerts

When an upstream register changes shape, the watchtower flags it within 24h and freezes the source rather than ingesting malformed data — schema_state rides every response.
Freshness

Stale-data freeze

A source that stops updating is frozen with its last_synced_at, so the feed never presents stale data as fresh.

Try it

A real call, end to end

Pass the cursor from your last sync and get back only what changed — created, updated and removed.

GET /api/v1/changes?since=…
curl 'https://drug-database.com/api/v1/changes\
?since=2026-06-26T00:00:00Z&limit=2' \
  -H 'Authorization: Bearer dd_live_…'
response
{
  "changes": [
    {
      "type": "drug.updated",
      "id": "3f8a1c2d-1b4e-4c7a-9d2f-0a1b2c3d4e5f",
      "fields": ["is_reimbursed", "public_price"],
      "source": "bag_sl",
      "changed_at": "2026-06-27T02:14:00Z"
    },
    {
      "type": "interaction.created",
      "atc_a": "B01AA03",
      "atc_b": "N02BA01",
      "source": "epha",
      "changed_at": "2026-06-27T02:15:10Z"
    }
  ],
  "next_cursor": "2026-06-27T02:15:10Z",
  "_meta": { "has_more": false }
}

The watchtower

  • 554,523 tracked changes and counting, across drugs and interactions.
  • Schema-validated staging: data is checked against an expected schema before it is ever promoted.
  • Schema-drift detection within 24h; a drifting source is frozen, not silently ingested.
  • Every API response carries last_synced_at and schema_state, so freshness is a field, not a guess.
Good to know
  • · The changes feed needs the changes:read scope; webhooks need webhooks:write.
  • · Cursors are timestamps — store next_cursor and pass it as the next since.

FAQ

Common questions

Do an initial import, then poll GET /api/v1/changes?since={cursor} on your schedule. Apply the created/updated/removed entries and store next_cursor as the since for your next call. You never re-crawl the full catalog.
Yes. Register an endpoint with POST /api/v1/webhooks and the same change events are delivered to you, signed so you can verify origin. DELETE /api/v1/webhooks removes the subscription.
It is the ingestion pipeline that stages, schema-validates and diffs every public source before promoting data. It detects upstream schema drift within 24 hours and freezes a source rather than ingest malformed data.
Every response carries last_synced_at and schema_state for the backing sources. A frozen or stale source is reported as such, so you can decide whether to trust or defer.
554,523 field-level changes so far, across drugs and interactions, and the count grows with every ingestion run.

Ready when you are

Put change feed in production this afternoon.

Spin up a key in minutes. The free tier covers prototyping and low-volume production.

Free tier · 100 requests/day · No card required