Every response carries last_synced_at and a per-source schema_state under _meta. When an upstream registry changes shape, we keep the last-good data and flag it stale — so you never silently ship a broken refill. 554,523 tracked changes, queryable via /v1/changes.
raw01
sources/raw/{source}/{run_id}
pull, retain 12 months
staging02
{source}_staging
typed staging table
validation03
schema validate
XSD · JSON-schema · regex
promotion04
drugs · interactions · atc
production read path
Validated before it reaches you
Every source is schema-validated at staging — column checks, XSD, and JSON-schema assertions per feed. Failed rows land in quarantine; promotion is blocked above a 1% fail-rate. Nothing reaches the read path unvalidated, so the answer you get has already passed its checks.
Schema-drift detection
When an upstream registry silently changes shape, the run is flagged `schema_drift`: we keep the last-good snapshot serving, alert on call, and surface `schema_state: "stale"` in every API response — so you know before you ship a broken refill.
The /v1/changes diff feed
Every promoted row writes to `changes` (before, after, source, occurred_at) — 554,523 tracked changes and counting. Ask "what changed this week" via `GET /v1/changes?since=…&source=…`, or subscribe by webhook and react the moment a registry moves.
Stale-data honesty
Every response carries `last_synced_at` and `schema_state` per source under `_meta`. The previous good snapshot is still served, but flagged — never silently. Knowing exactly how fresh each answer is means you decide what to trust.
28 sources across 19 jurisdictions, each on its own cadence. Detection is the easy part. The interesting question is what we do in the four hours after the alert fires — and what your code sees while we're working.
01
Detect
Per-source validator trips on column rename, type change, XML element drift, JSON-schema fail. The run gets `schema_drift` status, promotion is blocked, last-good stays live.
02
Alert
PagerDuty + Slack fire within the cron tick. The runbook URL goes in the body. `_meta.schema_state` flips to `stale` in every response touching that source.
03
Triage
On-call inspects `clinical_drug_data_sync_runs.payload` for the diff. Additive changes go through `bag_sl_add_observed_column` with no redeploy.
04
Recover
Destructive changes get a parser patch + a forced re-run. SLA: best-effort 24h. Customers see the state transition in the change feed and the API response.
Why staging-first
Every source has an edge function that pulls the upstream artefact, parses it, and writes to a staging table before any production row moves. drugs, interactions, pharmacode_aliases see only promoted rows.
Every promotion writes one row per affected natural-key. Customers consume via webhook (Growth+) orGET /v1/changes?since=…. Most-requested feature from EHR vendors who currently re-import the catalogue monthly because they cannot tell what moved.
CREATE TABLE changes (
id uuid PRIMARY KEY,
table_name text NOT NULL,
natural_key jsonb NOT NULL,
drug_id uuid REFERENCES drugs(id),
change_type text CHECK (
change_type IN ('insert','update','delete')),
before jsonb,
after jsonb,
source text NOT NULL,
occurred_at timestamptz NOT NULL DEFAULT now()
);