Pull API
Read published records over HTTP — the recommended way to feed dbt, Fivetran, or any pipeline.
The Pull API serves your published records as JSON, so your warehouse team can ingest them through the pipeline they already trust. It's the default half of pull-first publishing.
Base URL
https://<host>/api/t/<workspace-slug>/v1Every path is scoped to one workspace. The Integrations → Pull API page in the app
shows the exact base URL and a ready-to-copy curl for each table.
Authentication
Every request needs a service-account bearer token (zzsa_…), created on the
Integrations → Service accounts page. Tokens are read-only and scoped to a single
workspace.
curl -H "Authorization: Bearer zzsa_YOUR_TOKEN" \
https://<host>/api/t/acme/v1/tablesEndpoints
| Method & path | Returns |
|---|---|
GET /v1/tables | Every table in the workspace. |
GET /v1/tables/:slug/fields | A table's field definitions. |
GET /v1/tables/:slug/records | Published records. Paginated; supports ?since= and ?cursor=. |
GET /v1/tables/:slug/removed | Records removed since a point in time (tombstones). |
GET /v1/tables
{
"tables": [
{ "slug": "country", "label": "Country", "record_count": 16, "last_published_at": "2026-01-01T12:00:00Z" }
]
}GET /v1/tables/:slug/fields
{
"table_slug": "country",
"label": "Country",
"fields": [
{ "name": "iso3", "type": "text", "description": "ISO-3166 alpha-3" }
]
}GET /v1/tables/:slug/records
{
"records": [
{
"key": "br",
"label": "Brazil",
"fields": { "iso3": "BRA", "region": "Latin America" },
"updated_at": "2026-01-01T12:00:00Z"
}
],
"cursor": { "next": "eyJ…signed…" },
"meta": { "table_slug": "country", "page_size": 500 }
}Incremental pulls
Records are ordered by (updated_at, key), and pages carry a signed cursor:
- First pull — call
…/recordswith no params, then followcursor.nextuntil it'snull. - Since a timestamp —
?since=<ISO-8601>returns only records at or after that instant (inclusive). Persist the newestupdated_atyou've seen and pass it next run. - Resume a page —
?cursor=<value>continues exactly where the previous page ended.
Only ISO-8601 values are accepted for since (e.g. 2026-01-01T00:00:00Z).
# Pull everything new since your last successful pull
curl -H "Authorization: Bearer zzsa_YOUR_TOKEN" \
"https://<host>/api/t/acme/v1/tables/country/records?since=2026-01-01T00:00:00Z"Deletions don't appear in /records. Poll /removed (also ?since=/?cursor=) to
tombstone records that were retired — a webhook changes_truncated event is the signal
to reconcile against it.
Errors & limits
| Response | Meaning |
|---|---|
400 cursor_invalid | The cursor was invalidated (e.g. server-key rotation). Pull again from ?since=. |
429 + Retry-After | Rate limited. Default 600 req/min per credential (ZUGZUG_PULL_API_RPM). |
Consuming it in dbt
Point a scheduled job or your extract tool at …/records, land the JSON in a landing table,
and model it like any other source. Because it's plain HTTP + cursors, every publish
flows through the same dev → prod and review path as the rest of your warehouse.