Paging Audience Records

Read a built audience as JSON pages instead of downloading files

Overview

GET /api/v1/audiences/{id}/records returns a built audience as JSON, one page at a time. Each result is one row with the same columns the download files carry. Dates render as YYYY-MM-DD, timestamps as RFC 3339 UTC, and missing values as null.

The audience needs a finished full build first. Records never starts a build, and a build bills no records.

  1. Create the audience with "unload": "unload". If it already exists as a preview build, call POST /api/v1/audiences/{id}/unload with its project_id.
  2. Poll GET /api/v1/audiences/{id}/status until status is ready or done. This call bills nothing.
  3. Page the records with the loop below.

Until the build finishes, records returns 409 with "error": "not_ready". If status there is counted, stale, failed or expired, the build is not usable as it is. Call POST /api/v1/audiences/{id}/unload first, then poll status again.

Do not poll the download endpoint to make the build ready. A ready response from GET /api/v1/audiences/{id}/download bills every distinct person in the build. Paging that build is then free, so you pay for the whole audience, not for the pages you read. To see that charge before you commit, call the download endpoint with dry_run=true.

Paging Loop

Follow next_cursor until a response has none. page_size (1-5000, default 1000) is a maximum, so a page can hold fewer rows and still have more after it. Loop on the cursor, not the row count. Send Accept-Encoding: gzip to get compressed responses.

import time
import requests

url = "https://api.delivr.ai/api/v1/audiences/AUDIENCE_ID/records"
headers = {
    "X-Api-Key": "YOUR_API_KEY",
    "X-Api-Secret": "YOUR_API_SECRET",
    "Accept-Encoding": "gzip",
}
params = {"project_id": "YOUR_PROJECT_ID", "page_size": 5000}

records = []
while True:
    r = requests.get(url, params=params, headers=headers)
    if r.status_code == 503:
        time.sleep(int(r.headers.get("Retry-After", 5)))
        continue
    r.raise_for_status()
    page = r.json()
    records.extend(page["results"])
    if "next_cursor" not in page:
        break
    params["cursor"] = page["next_cursor"]

A response looks like this (count is the build's total rows, omitted when unknown):

{
  "audience_id": "123",
  "count": 4221397,
  "results": [{"first_name": "Ana", "job_title": "VP Marketing", "perc_score": 92}],
  "next_cursor": "..."
}

Errors

StatusMeaning
400Invalid page_size, an unknown query parameter, or an invalid cursor.
402The organization cannot be charged, as with the download endpoint.
404Audience not found.
409 not_readyNot built yet. Poll GET /api/v1/audiences/{id}/status, then retry. If status is counted, stale, failed or expired, call POST /api/v1/audiences/{id}/unload first.
409 cursor_staleThe audience was rebuilt after the cursor was issued. Restart without a cursor.
422This audience cannot be served as records. Use the download endpoint.
429 platform_cap_exceeded, kind: limit_reachedUsage limit reached. The same page succeeds after resets_at, which Retry-After gives in seconds.
429 platform_cap_exceeded, kind: request_too_largeThis page alone is larger than the limit. Retry with a smaller page_size, or contact support to raise the limit.
429 platform_cap_exceeded, kind: shut_offUsage is paused for this account. Contact support.
429, any other errorRate limited. Wait for Retry-After when present, then retry.
503 busyWait the number of seconds in Retry-After, then retry. Other 503s: retry with backoff.

Billing

Records are billed per record returned, at the audience's meter (Predictive Signals audiences bill at the Predictive Signals rate).

  • A record read again on the same UTC day is not charged again.
  • In one day, paging a build never costs more than downloading it.
  • If the build's file download was already charged, paging that build is free.
  • A page that fails partway is not charged.

Test API keys page a fixed 7-row sample, marked with the response header X-Delivr-Mode: test, and are never billed. With page_size=3 the pages hold 3, 3 and 1 rows, so you can test your cursor loop. Audience id pending returns 409 not_ready.


Did this page help you?