Intent Audiences API

Overview

The Audiences API lets you create targeted audiences based on intent signals. You define which topics you care about, and the platform finds contacts actively researching those topics.

flowchart LR
    A["Find Topics<br/>(Taxonomy API)"] --> B[Create Audience]
    B --> C[Poll Status]
    C --> D{Status?}
    D -->|Pending / Syncing| C
    D -->|Completed| E[Preview Sample]
    E --> F[Download Full Results]

    style A fill:#3b82f6,color:#fff
    style F fill:#22c55e,color:#fff
stateDiagram-v2
    [*] --> Pending: Create audience
    Pending --> Syncing: System picks up job
    Syncing --> Completed: Results ready
    Syncing --> Failed: Error occurred
    Completed --> [*]: Download results
    Failed --> [*]: Check error, retry

Base URL: https://api.delivr.ai

Prerequisites:

Authentication: Organization API key. Send X-Api-Key + X-Api-Secret on every request (both required). Create the pair in the dashboard at https://app.delivr.ai/{org_id}/settings/api-keys.

Project scoping: All requests require your project ID, passed via either:

  • X-Project-Id header, OR
  • ?project_id= query parameter
X-Api-Key: YOUR_API_KEY
X-Api-Secret: YOUR_API_SECRET
X-Project-Id: YOUR_PROJECT_ID
Content-Type: application/json

Step 0: Get Available Topics

Before creating an audience, you need topic IDs. Browse the catalog using the Taxonomy API.

Quick Example

curl "https://api.delivr.ai/api/v1/taxonomy/topics?limit=10&search=cloud+computing" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "X-Api-Secret: YOUR_API_SECRET"

Example Response (200 OK)

{
  "response": {
    "topics": [
      {
        "topic_id": "4eyes_115481",
        "category_name": "Business",
        "subcategory_name": "Controls & Standards",
        "name": "Cloud Computing",
        "status": "active",
        "topic_type": "B2B"
      }
    ]
  }
}

Use the topic_id values (e.g. "4eyes_115481") in your audience filter's INTENT rule. See the Taxonomy API for filtering by category, subcategory, and type.

Step 1: Create an Audience

Request

POST /api/v1/audiences?project_id=YOUR_PROJECT_ID
{
  "organization_id": "YOUR_ORGANIZATION_ID",
  "project_id": "YOUR_PROJECT_ID",
  "audience_name": "AI Research Intent",
  "type": "intents",
  "segmentation_type": "Audience",
  "filter": {
    "condition": "and",
    "rules": [
      {
        "fieldName": "INTENT",
        "conditionRules": {
          "operator": "in",
          "value": ["artificial_intelligence", "machine_learning"]
        }
      }
    ]
  }
}

Required Fields

FieldTypeDescription
organization_idstring (UUID)Your organization ID (provided during onboarding)
project_idstring (UUID)Your project ID (provided during onboarding)
audience_namestringA name for your audience
typestringMust be "intents"
segmentation_typestringMust be one of: "Audience", "Persona", "Account". See Understanding Segmentation Types for when to use each.
filterobjectMust contain at least one rule with fieldName: "INTENT"

Optional Fields

FieldTypeDefaultDescription
audience_descriptionstring""Description of the audience
selectstring"*"Which columns to include in results
unloadstring"preview""preview" for sample only, "unload" for full export
intent_window_daysinteger1Number of recent days of intent data to include (1-14). A wider window captures more contacts but may include older signals.
record_limitinteger(none)Maximum number of records to return. Omit for no limit.

unload defaults to preview. A preview build computes size and stats plus a small capped sample. It does not write the full downloadable dataset. To download the whole audience, set "unload": "unload" on create. (You can also drive the download endpoint, which prepares the full files on demand.)

Example Response (201 Created)

{
  "id": 8268,
  "organization_id": "YOUR_ORGANIZATION_ID",
  "project_id": "YOUR_PROJECT_ID",
  "audience_name": "AI Research Intent",
  "audience_description": "",
  "segmentation_type": "Audience",
  "status": "Pending",
  "type": "intents",
  "unload": "preview",
  "filter": { "..." },
  "sql_filter_statement": {
    "where": "topic_id in ('artificial_intelligence', 'machine_learning')"
  },
  "select": "*",
  "size": null,
  "account_count": null,
  "date_updated_size": null,
  "output_path": null,
  "date_updated_path": null,
  "error": null,
  "task_id": null,
  "created_at": "2026-02-01T10:00:00Z",
  "updated_at": "2026-02-01T10:00:00Z"
}

Save the id from the response -- you need it for all subsequent requests.

Validation Rules

  • type: "intents" requires at least one rule where fieldName is INTENT, intent, topic, or topic_id
  • Without this, you get: "topic_id or INTENT field with topic IDs is mandatory for intents audience"
  • project_id in the body must match the project ID from your auth context

Step 2: Check Audience Status

After creating, the platform processes your audience in the background. Poll GET /api/v1/audiences/{id} until status reaches a terminal value. Treat Completed (or the legacy Validated) as success and Failed as error. Treat every other value (Pending, Syncing, anything unrecognized) as still in progress and keep polling. Do not hard-code a short timeout that fails the job: builds usually finish in well under two minutes but can run several minutes for large audiences, so poll until terminal (allow at least ~20 minutes) and requeue rather than fail permanently if you hit your own ceiling.

Request

GET /api/v1/audiences/{id}?project_id=YOUR_PROJECT_ID

Example Response (200 OK)

{
  "id": 4769,
  "organization_id": "YOUR_ORGANIZATION_ID",
  "project_id": "YOUR_PROJECT_ID",
  "audience_name": "AI Research Intent",
  "audience_description": "",
  "segmentation_type": "Audience",
  "status": "Completed",
  "type": "intents",
  "unload": "preview",
  "filter": { "..." },
  "sql_filter_statement": { "..." },
  "select": "*",
  "size": 1000,
  "account_count": null,
  "date_updated_size": "2026-01-31T09:19:44.934716Z",
  "output_path": "s3://...",
  "date_updated_path": "2026-01-31T09:19:44.934716Z",
  "error": null,
  "task_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "created_at": "2026-01-12T12:09:49.187774Z",
  "updated_at": "2026-01-31T09:19:44.937174Z"
}

Status Values

StatusMeaningWhat To Do
PendingQueued for processingPoll again in 10 seconds
SyncingProcessing in progressPoll again in 10 seconds
CompletedAudience is readyProceed to sample or download
ValidatedLegacy terminal success, equivalent to CompletedProceed to sample or download
FailedAn error occurredCheck the error field
PausedAuto-renewal is stopped; existing results still downloadableResume if you need fresh data

Typical processing time is 30-120 seconds depending on audience size. size and distinct_profile_count stay null until the build finishes, so a null there is a reliable "not done yet" signal even if status momentarily reads Syncing.

Step 3: Preview Sample Data

Once status is "Completed", preview a sample of the results.

Request

GET /api/v1/audiences/{id}/sample?limit=5&project_id=YOUR_PROJECT_ID

Query Parameters

ParameterTypeDefaultDescription
limitinteger25Number of rows to return (capped server-side at 25)
project_idstring--Your project ID

Example Response (200 OK)

{
  "total_count": 27593,
  "last_processed_at": "2026-02-10T00:00:00Z",
  "rows": [
    {
      "first_name": "jane",
      "job_title": "product analyst",
      "department": "operations",
      "seniority_level": "staff",
      "company_name": "acme corp",
      "company_domain": "acmecorp.com",
      "company_industry": "insurance",
      "company_employee_count": 2400,
      "company_city": "new york",
      "company_state": "new york",
      "company_country": "united states",
      "sha256_lc_hem": "a1b2c3d4e5f6...",
      "has_business_email": true,
      "has_personal_email": true,
      "has_phone": true,
      "has_linkedin": true,
      "score": "high",
      "topic_id": "4eyes_110486",
      "topic_name": "15Five",
      "ts": "2026-01-30T00:00:00"
    }
  ]
}
FieldTypeDescription
total_countintegerTotal number of records matching the audience filters. Use this to gauge audience size before downloading.
last_processed_atstring (ISO 8601)Timestamp of when the audience data was last processed.
rowsarrayRedacted sample rows (up to 25). See below for field details.

total_count and last_processed_at are present when the audience has completed processing. For audiences still in Pending or Syncing status, these fields are omitted.

The sample is a capped, redacted teaser for validating the audience, not a data feed. Each row carries:

  • Identity: first_name and the sha256_lc_hem match key
  • Role: job_title, seniority_level, department
  • Company info: name, domain, industry, size, revenue, location
  • Intent fields (added by processing): score, perc_score, topic_id, topic_name, intent_frequency, ts
  • Presence flags: has_business_email, has_personal_email, has_phone, has_linkedin. These indicate which contact fields exist for the row.

Raw contact data (emails, phones, LinkedIn URLs, personal addresses, full names) is not returned by the sample. Download the audience (Step 4) to get, and be billed for, the full contact records. See Intent Audience Fields for the complete export schema.

Note: The sample endpoint returns up to 25 rows. A limit above 25 is clamped to 25.

Step 4: Download the Full Audience

The download endpoint prepares files and returns signed URLs. If files are not yet prepared, it automatically triggers preparation and returns a status you can poll.

Request

GET /api/v1/audiences/{id}/download?project_id=YOUR_PROJECT_ID

Preview the cost first (optional)

Add dry_run=true to see what a download would bill before you pull anything. It returns the billing meter and the records-to-charge for the audience, triggers no file preparation, and bills nothing:

GET /api/v1/audiences/{id}/download?project_id=YOUR_PROJECT_ID&dry_run=true

Response When Ready (200 OK)

{
  "status": "ready",
  "output_links": {
    "day=20260130/prefix=4eyes_110/score=high": [
      "https://signed-s3-url/count.json?...",
      "https://signed-s3-url/part-0.parquet?..."
    ],
    "day=20260130/prefix=4eyes_500/score=high": [
      "https://signed-s3-url/count.json?...",
      "https://signed-s3-url/part-0.parquet?..."
    ]
  }
}

The output_links keys are partitioned by day, topic prefix, and score. Each partition contains:

  • count.json -- record count for that partition
  • preview.json -- preview data for that partition
  • One or more .parquet data files with the actual audience data (e.g. part-0.parquet, part-1.parquet)

Some partitions may only have count.json (when no matching data was found for that topic/score combination).

Signed URLs are presigned for up to 7 days. Download promptly, and if a link ever returns an expired-signature error, request the download endpoint again for freshly signed URLs.

Response When Not Ready (200 OK)

{
  "status": "unloading",
  "reason": "full_download_requested",
  "stage": "deduplicating",
  "progress": {
    "completed": 18,
    "total": 72
  }
}

While preparation is in flight, stage is one of waiting, building,
deduplicating, or finalizing. The ready response carries stage: "ready",
so handle five values in total, and treat an unrecognized one as "still
preparing". When the active worker reports job counts, progress contains the
completed and total jobs. Both fields may be absent, so clients should still
treat status as the source of truth.

Download Status Values

The typical progression is: unloading -> done -> ready.

StatusMeaningWhat To Do
readyFiles are availableDownload from output_links
unloadingFiles are being preparedPoll again in 3 seconds
donePreparation finishingPoll again in 3 seconds
failedPreparation failedRetry or contact support

Keep polling until the endpoint returns ready or failed. Every other status
is non-terminal, including converting, no_task, in_progress, completed,
and unrecognized future values. Large audiences can take longer than a few
minutes, so a client-side timeout must not turn healthy work into an error. A UI
may stop making requests when the user closes it, but preparation continues on
the server; resume polling when the user returns.

Downloading the Files

Files are in Parquet format. See Reading Parquet Files for how to convert them to CSV or open in Excel.

Filter the URLs to find .parquet files (skip count.json unless you need record counts):

import requests

response = requests.get(
    "https://api.delivr.ai/api/v1/audiences/AUDIENCE_ID/download",
    params={"project_id": "YOUR_PROJECT_ID"},
    headers={"X-Api-Key": "YOUR_API_KEY", "X-Api-Secret": "YOUR_API_SECRET"},
)
data = response.json()

if data["status"] == "ready":
    for partition, urls in data["output_links"].items():
        for url in urls:
            if url.split("?")[0].endswith(".parquet"):
                # Download parquet file
                r = requests.get(url)
                filename = partition.replace("/", "_") + ".parquet"
                with open(filename, "wb") as f:
                    f.write(r.content)

Available Fields (Schema)

To see all available fields and their data types:

GET /api/v1/audiences/schema?project_id=YOUR_PROJECT_ID

Example Response (200 OK)

{
  "fields": [
    { "name": "address_id", "data_type": "Utf8View", "nullable": true },
    { "name": "age_range", "data_type": "Utf8View", "nullable": true },
    { "name": "business_emails", "data_type": "Utf8View", "nullable": true },
    { "name": "company_address", "data_type": "Utf8View", "nullable": true },
    { "name": "company_city", "data_type": "Utf8View", "nullable": true },
    { "name": "company_country", "data_type": "Utf8View", "nullable": true },
    { "name": "company_domain", "data_type": "Utf8View", "nullable": true },
    { "name": "company_name", "data_type": "Utf8View", "nullable": true },
    { "name": "company_size", "data_type": "Utf8View", "nullable": true },
    { "name": "first_name", "data_type": "Utf8View", "nullable": true },
    { "name": "job_title", "data_type": "Utf8View", "nullable": true },
    { "name": "last_name", "data_type": "Utf8View", "nullable": true },
    { "name": "score", "data_type": "Utf8View", "nullable": true },
    { "name": "topic_id", "data_type": "Utf8View", "nullable": true },
    { "name": "topic_name", "data_type": "Utf8View", "nullable": true }
  ]
}

Use the name values from this response as fieldName in your filter rules.

Note: This is a partial list. The full schema contains additional fields. Call the endpoint to see all available fields.

Filter Reference

The filter field is a tree of rules joined by "and" or "or".

Structure

{
  "condition": "and",
  "rules": [ ... ]
}

Topics (required for intents)

{
  "fieldName": "INTENT",
  "conditionRules": {
    "operator": "in",
    "value": ["topic_id_1", "topic_id_2"]
  }
}

Contact your account team for available topic IDs. Maximum 10 topics per audience.

Signal Strength (optional)

If omitted, all strengths are included.

{
  "fieldName": "score",
  "conditionRules": {
    "operator": "in",
    "value": ["high", "medium"]
  }
}
ValueMeaning
highStrong intent signal
mediumModerate intent signal
lowWeak intent signal (broadest reach)

Field Filters (optional)

Use any field name from the schema endpoint:

{
  "fieldName": "company_employee_count_range",
  "conditionRules": {
    "operator": "in",
    "value": ["1001 to 5000", "5001 to 10000", "10000+"]
  }
}

Large value sets: If you need to filter by more than a few dozen values (e.g., thousands of email hashes or domains), upload them as a list instead of passing them as an array in the filter. Large arrays in the filter body slow down audience creation and may hit request size limits. See Using Lists with Audiences below.

Available Operators

OperatorValue TypeDescription
inarray of stringsMatches any of the values
not inarray of stringsExcludes these values
is / equalsstring or arrayField exactly equals value
not equalsstringField does not equal value
containsstringField contains the text
not containsstringField does not contain the text
startswithstringField starts with the text
endswithstringField ends with the text
is not blank(none)Field has a value
is blank(none)Field is empty or null
is not null / notnull(none)Field is not null
is null / isnull(none)Field is null
>=stringGreater than or equal to
<=stringLess than or equal to
>stringGreater than
<stringLess than

Values can be provided as:

  • A plain string: "value": "some text"
  • An array of strings: "value": ["val1", "val2"]
  • An array of objects: "value": [{"name": "val1"}, {"name": "val2"}] (backend extracts the name, value, or label field)

Field Types and Operators

Fields fall into two categories. Using the wrong operator type is the most common cause of unexpectedly small audiences.

Picklist Fields

Picklist fields have a fixed set of valid values. Use the operators shown for each field.

FieldOperatorsExample Values
seniority_levelin, notincxo, director, manager, staff, vp
departmentin, notinsales, marketing, engineering, finance, operations, executive, information technology, human resources, legal, administrative, customer service, product management, education, healthcare services, media and communication, community and social services (16 values total)
job_title_normalizedin, notin, contains16,000+ normalized titles. Use GET /api/v1/field-catalog/job_title_normalized to browse values. in for exact matching, contains for partial matching.
company_industryin, notintechnology, information and internet, software development, financial services, advertising services, hospitals and health care, insurance, ... (200+ values, call the field catalog endpoint for the full list)
company_revenue_rangein, notinunder 1 million, 1 million to 5 million, 5 million to 10 million, 10 million to 25 million, 25 million to 50 million, 50 million to 100 million, 100 million to 250 million, 250 million to 500 million, 500 million to 1 billion, 1 billion and over
company_employee_count_rangein, notinzero, 1 to 10, 11 to 25, 26 to 50, 51 to 100, 101 to 250, 251 to 500, 501 to 1000, 1001 to 5000, 5001 to 10000, 10000+
age_rangein, notin, is, is not18-24, 25-34, 35-44, 45-54, 55-64, 65 and older
genderin, notin, is, is notf, m, u
income_range_lcin, notinless than $20,000, $20,000 to $44,999, $45,000 to $59,999, $60,000 to $74,999, $75,000 to $99,999, $100,000 to $149,999, $150,000 to $199,999, $200,000 to $249,000, $250,000+
email_validation_statusin, notinvalid, invalid, catchall, unknown

Tip: Use GET /api/v1/field-catalog/{field_key} to get the full list of valid values for any picklist field. See the Field Catalog API for details.

Free-Text Fields

Free-text fields contain arbitrary strings (names, titles, addresses). Supported operators: is, is not, contains, notcontains, startsWith, endsWith, notnull.

FieldRecommended OperatorExample
job_titlecontains"value": "director" matches "Director of Sales", "Marketing Director", etc.
first_name, last_nameis or contains"value": "jane"
company_namecontains"value": "acme"
company_domainis or contains"value": "acme.com"
current_business_emailnotnull(no value needed)
personal_city, personal_stateis or contains"value": "new york"
company_city, company_stateis or contains"value": "san francisco"

job_title vs job_title_normalized: Both fields support contains for partial matching. job_title is raw free-text (e.g., "Sr. Director of Engineering"). job_title_normalized is a standardized picklist (16,000+ values) that also supports in/notin for exact matching. Use job_title with contains for broad matching on raw titles; use job_title_normalized with in for precise matching against known normalized values, or contains for partial matching against normalized titles.

Matching Multiple Titles

For short acronyms (CTO, CEO, CFO, VP, etc.), use job_title_normalized with in. The normalization layer maps variants to canonical values, so you get exact matching without false positives.

Warning: Do not use contains with short acronyms on job_title. Substring matching means "CTO" matches "Art Director" and "VP" matches "Advpush". Always use job_title_normalized with in for acronyms.

{
  "fieldName": "job_title_normalized",
  "conditionRules": {
    "operator": "in",
    "value": ["chief executive officer", "chief financial officer", "vice president"]
  }
}

For longer phrases where substring matching is safe, use contains on job_title with an or group:

{
  "condition": "or",
  "rules": [
    {
      "fieldName": "job_title",
      "conditionRules": { "operator": "contains", "value": "Director of Engineering" }
    },
    {
      "fieldName": "job_title",
      "conditionRules": { "operator": "contains", "value": "Head of Product" }
    }
  ]
}

You can combine both approaches in a single or group to match acronyms precisely and longer titles broadly.

Listing Audiences

Request

GET /api/v1/audiences?project_id=YOUR_PROJECT_ID&type=intents&page=1&page_size=20

Query Parameters

ParameterTypeDefaultDescription
project_idstring--Your project ID (required)
typestring--Filter by type: intents, contact, list. elixir is the former name for contact and is still accepted; either value returns every contact audience
statusstring--Filter by status: Completed, Failed, etc.
pageinteger1Page number
page_sizeinteger20Results per page
sort_bystring--Sort column: audience_name, status, created_at
sort_orderstring--ASC or DESC
namestring--Search by audience name

Example Response (200 OK)

{
  "data": [
    {
      "id": 4879,
      "organization_id": "YOUR_ORGANIZATION_ID",
      "project_id": "YOUR_PROJECT_ID",
      "audience_name": "Automated Customer Service Need",
      "audience_description": "Business owners interested in automating customer service",
      "segmentation_type": "Audience",
      "status": "Completed",
      "type": "intents",
      "size": 4899,
      "created_at": "2026-01-28T18:52:02.850618Z",
      "updated_at": "2026-02-01T00:02:35.076531Z"
    }
  ],
  "page": 1,
  "page_size": 20,
  "total": 15,
  "total_pages": 1
}

Pagination

The response includes pagination metadata:

FieldDescription
dataArray of audience objects
pageCurrent page number
page_sizeNumber of results per page
totalTotal number of matching audiences
total_pagesTotal number of pages

Complete Example

1. Create

curl -X POST "https://api.delivr.ai/api/v1/audiences?project_id=YOUR_PROJECT_ID" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "X-Api-Secret: YOUR_API_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "organization_id": "YOUR_ORGANIZATION_ID",
    "project_id": "YOUR_PROJECT_ID",
    "audience_name": "Enterprise Cloud Computing Intent",
    "audience_description": "Large companies researching cloud infrastructure",
    "type": "intents",
    "segmentation_type": "Audience",
    "filter": {
      "condition": "and",
      "rules": [
        {
          "fieldName": "INTENT",
          "conditionRules": {
            "operator": "in",
            "value": ["cloud_computing", "cloud_infrastructure"]
          }
        },
        {
          "fieldName": "score",
          "conditionRules": {
            "operator": "in",
            "value": ["high", "medium"]
          }
        },
        {
          "fieldName": "company_employee_count_range",
          "conditionRules": {
            "operator": "in",
            "value": ["1001 to 5000", "5001 to 10000", "10000+"]
          }
        }
      ]
    }
  }'

2. Poll Status (repeat until "Completed")

curl "https://api.delivr.ai/api/v1/audiences/AUDIENCE_ID?project_id=YOUR_PROJECT_ID" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "X-Api-Secret: YOUR_API_SECRET"

3. Preview Sample

curl "https://api.delivr.ai/api/v1/audiences/AUDIENCE_ID/sample?limit=5&project_id=YOUR_PROJECT_ID" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "X-Api-Secret: YOUR_API_SECRET"

4. Download

# Poll until status is "ready"
curl "https://api.delivr.ai/api/v1/audiences/AUDIENCE_ID/download?project_id=YOUR_PROJECT_ID" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "X-Api-Secret: YOUR_API_SECRET"

# Download parquet files from the signed URLs in output_links
curl -o audience_data.parquet "SIGNED_URL_FROM_RESPONSE"

Other Operations

ActionMethodEndpointResponse
UpdatePUT/api/v1/audiences/{id}?project_id=...200 with updated audience
PausePOST/api/v1/audiences/{id}/pause?project_id=...200 with updated audience
ResumePOST/api/v1/audiences/{id}/resume?project_id=...200 with updated audience
RefreshPOST/api/v1/audiences/{id}/refresh?project_id=...202 with updated audience
ClonePOST/api/v1/audiences/{id}/clone?project_id=...201 with new audience
DeleteDELETE/api/v1/audiences/{id}?project_id=...204 No Content

The Update endpoint (PUT) does not accept a status field. To stop or resume auto-renewal, use the dedicated pause and resume actions below. Pause/resume preserve the audience's schedule (stashed in previous_renew_time) and surface the state via paused_at + pause_reason on the audience response.

Pause an Audience

Stop the audience from auto-renewing on its scheduled renew_time. The audience's results, filter, and stored schedule are preserved; only the renewal loop stops picking it up.

POST /api/v1/audiences/{id}/pause?project_id=YOUR_PROJECT_ID

Response (200 OK) is the updated audience with paused_at set to the current time and pause_reason = "manual". Returns 409 Conflict if the audience is already paused.

Refresh an Audience

Re-run the audience against current data without recreating it. This resets the audience to Pending so the worker rebuilds it on the next pass.

POST /api/v1/audiences/{id}/refresh?project_id=YOUR_PROJECT_ID

Response (202 Accepted) is the updated audience, now back in the Pending status. Poll status as you would after a create.

Use this rather than PUT when the filter has not changed. An update that submits an identical filter is treated as a no-op and will not trigger a rebuild, so it looks like nothing happened; refresh always re-runs.

StatusWhen
400The audience type does not support background refresh.
404No audience with that id in the project.
409The audience is already processing (Syncing), or it is paused. A paused audience returns a pause_reason; resume it first.

Resume an Audience

Clear the pause and restore the previously stashed renewal schedule.

POST /api/v1/audiences/{id}/resume?project_id=YOUR_PROJECT_ID

Response (200 OK) is the updated audience with paused_at and pause_reason cleared and renew_time restored from previous_renew_time.

Returns 409 Conflict when the audience is not paused, or when it is paused for a non-manual reason. In those cases the body includes a machine-readable code:

codeMeaning
org_inactiveThe parent organization is inactive. Reactivate it before resuming.
project_inactiveThe parent project is inactive. Reactivate it before resuming.
(absent)The audience is not currently paused.

Errors

All error responses return a JSON object with an error field:

HTTP StatusExample error Message
400"project_id is required"
400"topic_id or INTENT field with topic IDs is mandatory for intents audience"
400"failed to transform filter: ..."
401"invalid or expired token: ..."
404"Audience not found"
500"Failed to retrieve audience"

Note: project_id is required on all endpoints. Omitting it returns a 400 error.

Check File Status

Check whether an audience's output files are ready for download without triggering file preparation. This is a lightweight alternative to the download endpoint when you only need to know if files exist.

Request

GET /api/v1/audiences/{id}/status?project_id=YOUR_PROJECT_ID

Example Response (200 OK)

{
  "status": "ready"
}

Status Values

StatusMeaningWhat To Do
readyFiles exist with valid download URLsProceed to the download endpoint
unloadingFiles are being preparedPoll again in 3-5 seconds
no_taskAudience has never been exportedCall the download endpoint to trigger file preparation
failedFile preparation failedRetry or contact support

When to Use

Use the status endpoint instead of the download endpoint when you want to check file readiness without side effects. The download endpoint (GET /api/v1/audiences/{id}/download) triggers file preparation if files don't exist yet. The status endpoint only checks -- it never triggers preparation.

The status endpoint provides coarse readiness only. After a user requests a
download, poll the download endpoint when the UI needs the normalized stage
and optional job progress. Do not impose a fixed client-side timeout; stop
polling only when the response is ready or failed, or pause requests while
the UI is closed and resume them when it reopens.

This is useful for:

  • Showing a progress indicator in the UI after triggering a download
  • Polling for readiness before enabling a "Download" button
  • Checking if a previously exported audience still has valid files

Using Lists with Audiences

Lists let you upload your own data (hashed emails, domains, phone numbers) and use them as inclusion or exclusion filters when exporting audience data.

Base URL: https://api.delivr.ai

List Types

TypeDescriptionExample Values
hemSHA256-hashed emailse3b0c44298fc1c149afbf4c8996fb924...
contactContact identifiersEmail addresses
accountAccount identifiersCompany domains
domainDomain namesacme.com
zipcodeZip/postal codes90210
phonePhone numbers+15551234567

Quick Example: Upload a HEM Suppression List

1. Create the list

curl -X POST "https://api.delivr.ai/api/v1/lists" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "X-Api-Secret: YOUR_API_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "organization_id": "YOUR_ORG_ID",
    "project_id": "YOUR_PROJECT_ID",
    "name": "Suppression List Q2",
    "type": "hem",
    "source": "upload"
  }'

Save the id from the response.

2a. Add values (small lists, under 100k items)

curl -X POST "https://api.delivr.ai/api/v1/lists/LIST_ID/values/batch" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "X-Api-Secret: YOUR_API_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "values": [
      "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
      "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2"
    ]
  }'

Response:

{
  "inserted_count": 2,
  "skipped_count": 0,
  "total_count": 2
}

2b. Upload a file (large lists, up to 50 MB)

# Step 1: Get a presigned upload URL
curl -X POST "https://api.delivr.ai/api/v1/lists/LIST_ID/imports/presign" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "X-Api-Secret: YOUR_API_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"filename": "suppression.csv", "size_bytes": 5000000}'

# Step 2: Upload the file to S3 using the presigned URL and fields
curl -X POST "UPLOAD_URL_FROM_RESPONSE" \
  -F "Content-Type=text/csv" \
  -F "key=S3_KEY_FROM_FIELDS" \
  -F "policy=POLICY_FROM_FIELDS" \
  -F "x-amz-algorithm=ALGORITHM_FROM_FIELDS" \
  -F "x-amz-credential=CREDENTIAL_FROM_FIELDS" \
  -F "x-amz-date=DATE_FROM_FIELDS" \
  -F "x-amz-signature=SIGNATURE_FROM_FIELDS" \
  -F "[email protected]"

# Step 3: Trigger the import
curl -X POST "https://api.delivr.ai/api/v1/lists/LIST_ID/imports" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "X-Api-Secret: YOUR_API_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"s3_key": "S3_KEY_FROM_PRESIGN"}'

# Step 4: Poll until completed
curl "https://api.delivr.ai/api/v1/lists/imports/JOB_ID" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "X-Api-Secret: YOUR_API_SECRET"

The CSV file should have one value per line (with or without a header row). The import processes values in batches of 50,000 and reports progress via progress_pct (0-100).

Using Lists in Exports

Once uploaded, reference a list by its ID in an export's audienceFilters:

{
  "sourceType": "pixel",
  "sourceId": "YOUR_PIXEL_ID",
  "audienceFilters": [
    {
      "listId": "LIST_ID",
      "mode": "include",
      "hemFormat": "sha256_lc"
    }
  ]
}
FieldValuesDescription
listIdUUIDThe list ID from step 1
modeinclude or excludeInclude only matching contacts, or exclude them
hemFormatsha256_lc, sha256_uc, md5_lc, md5_uc, sha1_lc, sha1_ucHash format of the values in your list

Next Steps


Did this page help you?