Added

API Updates - August 2026

Audience definitions: AND/OR grouping across list filters

List-based filters in an audience definition now carry real boolean grouping. The condition marker on a list group is read when the definition is transformed into a build, so a single audience can express combinations such as two lists matched with OR and a third required with AND. Previously the marker was accepted but ignored, and every list filter was effectively combined the same way.

POST /api/v1/audiences
PUT  /api/v1/audiences/{id}
GET  /api/v1/audiences/{id}

Parameters:

  • condition (string) - boolean operator applied to a list filter group in the audience definition. Set it per group to control how that group combines with the rest of the definition.

The value round-trips: what you send on create or update is what you read back on GET /api/v1/audiences/{id}. The full definition shape, including where list groups sit, is returned by GET /api/v1/audiences/schema.

Example:

curl -X PUT https://api.delivr.ai/api/v1/audiences/aud_123 \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Enterprise buying committee",
    "definition": {
      "list_filters": [
        { "condition": "OR",  "list_id": "lst_a" },
        { "condition": "OR",  "list_id": "lst_b" },
        { "condition": "AND", "list_id": "lst_c" }
      ]
    }
  }'

If you have been working around the previous behavior by building one audience per list and merging the results downstream, you can now collapse that into a single definition.

Audience downloads: preparation stages and progress

GET /api/v1/audiences/{id}/download now distinguishes an audience that is still being prepared from one whose build failed. Active build states are treated as pollable rather than terminal, and the response reports a normalized preparation stage plus job progress while the build runs. Terminal failure reasons are preserved, so a polling client can tell "still preparing, here is how far along" apart from "this run failed, and here is why."

GET /api/v1/audiences/{id}/download

Requested full-download intent is now persisted server side. If your client disconnects mid-poll, preparation continues, and a later call to the same endpoint picks up the in-flight run instead of starting over.

Client guidance:

  • Treat an in-progress preparation response as a signal to keep polling, not as an error.
  • Read the reported stage and progress for user-facing status rather than inferring progress from elapsed time.
  • Treat only a terminal failure response as a stop condition, and surface its reason to the caller.

Example:

curl https://api.delivr.ai/api/v1/audiences/aud_123/download \
  -H "Authorization: Bearer YOUR_TOKEN"

Related: GET /api/v1/audiences/{id}/status remains available if you want build state without requesting the download itself.

Company counts on company-grain audiences

An audience built at company grain now populates account_count. The field was already present on audience reads, but nothing wrote to it, so integrators had to infer the counting unit from the audience type and read the generic size field. It is now produced by the build.

GET /api/v1/audiences
GET /api/v1/audiences/{id}

Response:

  • account_count (integer) - number of companies in the audience. Populated for company-grain audiences, alongside the existing size field.

Example:

curl https://api.delivr.ai/api/v1/audiences/aud_123 \
  -H "Authorization: Bearer YOUR_TOKEN"

If you render "N companies" in your own UI today by branching on audience type, you can read account_count directly instead.

Audience type: contact replaces elixir

The audience type formerly written as elixir is now stored and returned as contact. The change is platform-wide, so the value is consistent everywhere an audience type appears.

POST /api/v1/audiences
PUT  /api/v1/audiences/{id}
GET  /api/v1/audiences
GET  /api/v1/audiences/{id}

Writes: both spellings are accepted. Existing integrations that send elixir keep working unchanged.

Reads: contact is the canonical value. Audiences created before this change also read back as contact.

Action required: if you compare the audience type against a string literal or switch on it, add contact to your handling. Equality checks against elixir alone will stop matching on read.

Budget enforcement on audience downloads

GET /api/v1/audiences/{id}/download now consults the organization's configured monthly spend ceiling before serving a download. A request that would exceed the ceiling is refused rather than served.

GET /api/v1/audiences/{id}/download

Response (402 Payment Required): the shared budget refusal body, naming the reason the request was refused and the limits that applied. This matches the refusal shape already returned by the API key and export surfaces, so a single handler can cover all three.

Example:

curl -i https://api.delivr.ai/api/v1/audiences/aud_123/download \
  -H "Authorization: Bearer YOUR_TOKEN"

Budget enforcement on metered data endpoints

The metered data endpoints now consult the same budget decision and answer with the same refusal body when the ceiling is reached.

GET /api/v1/lookup
GET /api/v3/events/web_event
GET /api/v3/events/resolution_hem
GET /api/v3/events/resolution_hem_pte
GET /api/v3/events/resolution_full_profile

Response (402 Payment Required): the shared budget refusal body, carrying the refusal reason and the limits that applied.

If you meter your own consumption against a configured ceiling, expect 402 on these routes once the ceiling is hit, rather than a successful response. Handle it as a distinct, non-retryable outcome: retrying without a budget change will keep returning 402. It is not a rate limit, and backoff will not clear it.

Example:

curl -i "https://api.delivr.ai/api/v3/events/web_event" \
  -H "Authorization: Bearer YOUR_TOKEN"

Bug fixes

Project-scope membership on events API JWT auth. The events API now honors project-level membership when authenticating with a JWT. A user whose membership exists only at project scope receives data from GET /api/v3/events/web_event and the other tier reads instead of 401. If you previously granted organization-level membership solely to work around this, that is no longer required.

record_limit is applied to audience builds. The record_limit set on an audience was being dropped during the build. It is now applied once at the end of the build, so GET /api/v1/audiences/{id}/download returns the top records up to the limit instead of the full record set. Expect smaller downloads for any audience that already had a limit configured, which is the documented behavior.

Cancelled builds no longer hang downloads. A download whose underlying build was cancelled now triggers a rebuild instead of polling indefinitely against an output prefix that will never be written. Clients that previously stalled on GET /api/v1/audiences/{id}/download will now see preparation restart and complete.

Soft-deleted lists are excluded from campaign list reads. GET /api/v1/campaigns/{id}/lists no longer returns soft-deleted lists as attached to the campaign.

Export budget refusals keep their detail. An export run refused for exceeding a configured spend limit now returns the budget refusal with its reason code and limits intact, instead of collapsing into a generic 500. Export clients polling run state can now distinguish a spend refusal from a server-side failure.

POST /api/v1/exports
GET  /api/v1/exports/{id}