Zug Zug.
Reference

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>/v1

Every 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/tables

Endpoints

Method & pathReturns
GET /v1/tablesEvery table in the workspace.
GET /v1/tables/:slug/fieldsA table's field definitions.
GET /v1/tables/:slug/recordsPublished records. Paginated; supports ?since= and ?cursor=.
GET /v1/tables/:slug/removedRecords 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 …/records with no params, then follow cursor.next until it's null.
  • Since a timestamp?since=<ISO-8601> returns only records at or after that instant (inclusive). Persist the newest updated_at you'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

ResponseMeaning
400 cursor_invalidThe cursor was invalidated (e.g. server-key rotation). Pull again from ?since=.
429 + Retry-AfterRate 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.

On this page