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.
- Create the audience with
"unload": "unload". If it already exists as apreviewbuild, callPOST /api/v1/audiences/{id}/unloadwith itsproject_id. - Poll
GET /api/v1/audiences/{id}/statusuntilstatusisreadyordone. This call bills nothing. - 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
readyresponse fromGET /api/v1/audiences/{id}/downloadbills 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 withdry_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
| Status | Meaning |
|---|---|
| 400 | Invalid page_size, an unknown query parameter, or an invalid cursor. |
| 402 | The organization cannot be charged, as with the download endpoint. |
| 404 | Audience not found. |
409 not_ready | Not 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_stale | The audience was rebuilt after the cursor was issued. Restart without a cursor. |
| 422 | This audience cannot be served as records. Use the download endpoint. |
429 platform_cap_exceeded, kind: limit_reached | Usage limit reached. The same page succeeds after resets_at, which Retry-After gives in seconds. |
429 platform_cap_exceeded, kind: request_too_large | This 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_off | Usage is paused for this account. Contact support. |
429, any other error | Rate limited. Wait for Retry-After when present, then retry. |
503 busy | Wait 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.
Updated 7 days ago
