Consuming a Managed data feed
For the engineering team on the receiving end: read a Managed data feed into your own store on your own schedule — a one-time snapshot, then incremental polling. Works for a data lake, a warehouse, or any custom integration.
This guide is for the engineering team on the receiving end — any system that reads a Managed data feed into its own store: a data lake, a data warehouse (Snowflake, BigQuery, Databricks), or a bespoke integration. It covers the pattern: a one-time snapshot to seed, then incremental polling to stay current. You pull on your own schedule with your own tooling; in Live mode we store nothing and your system holds the history.
New to the connector itself? Start with Managed data feed connector.
Would you rather we push each change to an endpoint you run, instead of pulling? See the Custom API endpoint.
What we give you
- A feed web address (base):
https://<host>/api/feed/<your-slug> - A consumer key (a bearer token)
- Which custody mode the feed runs in: Live (zero retention) or Cached
Authenticating
Every request carries the consumer key:
Authorization: Bearer <consumer-key>
If your tooling genuinely cannot send headers we can enable ?api_key=<consumer-key> on the feed — but the header is preferred, because keys in URLs end up in logs.
The two-step pattern
1. Snapshot once (initial load).
GET /api/feed/<slug>
Returns the full current table as { "data": [ ... ], "meta": { ... } }. Load every row in data into your landing zone.
- In Cached mode you can page a large snapshot with
?after=<record_key>&limit=<n>; keep going untilmeta.next_afterisnull. - In Live mode the snapshot is the whole table in one response (no paging) — see Sizing below.
2. Poll for changes (keep current).
GET /api/feed/<slug>/changes
Returns only what changed, each record tagged "op": "upsert" or "op": "tombstone" (a removal). Upsert into your store on _record_key; treat tombstones as soft-deletes.
How you track your position depends on the custody mode:
| Cached | Live (zero retention) | |
|---|---|---|
| Position marker | meta.next_cursor — persist it, send it back next time as ?cursor= |
None — the server tracks the watermark; just call /changes again |
| Replay | Yes — re-send an old cursor to re-read forward | No — you get whatever changed since your last poll; your store holds the history |
| First poll | From the beginning | Auto-returns everything once, then deltas |
| Cadence | As often as you like | Once per interval (default 15 min); a read triggers a live WFX scan |
The record shape
Each item in data is your mapped fields plus a small envelope:
| Field | Meaning |
|---|---|
_record_key |
The unique key to upsert on (product code, or WFX record id — see below) |
_item_code |
WFX product code |
_item_version |
WFX version number |
_active |
false means removed (a tombstone) |
_updated_at |
When the row last changed (null in Live mode — nothing is stored) |
op |
upsert or tombstone (on /changes only) |
Current state vs full history: by default the record key is the product code, so you hold one current row per product. If you need every WFX version as its own row (full history), ask us to set the feed's record key to the WFX record id.
Handling responses
| Status | Meaning | What to do |
|---|---|---|
200 |
OK | Process data |
401 |
Bad or missing key | Check the bearer token |
403 |
Feed not on the package | Contact us |
404 |
Unknown feed address | Check the slug |
429 |
Cool-down or busy (Live only) — includes retry_after seconds |
Wait retry_after, then retry |
422 |
Feed too large to serve Live in one pass | Narrow the pipeline, or switch to Cached |
502 / 503 |
Live read failed / feed not live | Retry with backoff; if it persists, contact us |
Always honour retry_after on a 429.
Cadence (Live mode)
A Live feed serves at most once per its configured interval (default 15 minutes, set per dataset). Back-to-back reads get a 429 with retry_after. Match your polling job to that interval. Cached feeds have no cool-down — only a generous overall rate limit.
Sizing (Live mode)
Live mode reads WFX in full on each call and refuses to return a partial table — if the feed is too large it returns 422 rather than silently truncating. If you hit that, either scope the pipeline (fewer fields, or a product-category filter) or switch the feed to Cached, which holds an encrypted copy and serves instantly.
Examples
A minimal end-to-end with curl:
# 1. Initial load (snapshot)
curl -s -H "Authorization: Bearer $KEY" \
"https://<host>/api/feed/<slug>" | jq '.data'
# 2a. Incremental — Cached: persist the cursor each time
curl -s -H "Authorization: Bearer $KEY" \
"https://<host>/api/feed/<slug>/changes?cursor=$SAVED_CURSOR"
# then store meta.next_cursor for next time
# 2b. Incremental — Live: no cursor, just poll on your interval
curl -s -H "Authorization: Bearer $KEY" \
"https://<host>/api/feed/<slug>/changes"
Most tooling can drive this directly:
- Data lake — a scheduled notebook (Databricks) or a small script that snapshots into the raw zone, then appends
/changeson each run. - Warehouse — a REST source in Azure Data Factory, or a Fivetran / Airbyte custom connector, staging then merging on
_record_key. - Anything else — any scheduled job that can make an authenticated HTTP GET and store JSON.
There is also a /status endpoint (GET /api/feed/<slug>/status) for health and monitoring.
One thing to confirm with us
If your feed is fed by more than one pipeline (multiple datasets), use the dataset-specific address we give you rather than the bare slug — a bare Live address covering several datasets is intentionally rejected. We will tell you which applies to you.