API Reference
Audiences & Lists
Lists

Lists API

Manage static lists of records in the CDP. Lists are named collections of record IDs — used for suppression, seed populations, manual targeting, and reusable static segments that don't need the full audience rule engine.

Base URL: https://api.experiture.ai/public/v1

Authentication: Bearer token with cdp:lists:read or cdp:lists:write. See Authentication.


Overview

OperationEndpointScope
Create listPOST /listscdp:lists:write
List all listsGET /listscdp:lists:read
Get listGET /lists/{list_id}cdp:lists:read
Get list membersGET /lists/{list_id}/memberscdp:lists:read
Update listPATCH /lists/{list_id}cdp:lists:write
Delete listDELETE /lists/{list_id}cdp:lists:write
Add membersPOST /lists/{list_id}/members:upsertcdp:lists:write
Remove membersPOST /lists/{list_id}/members:removecdp:lists:write

Lists vs. Audiences: a list is a static set of IDs you manage yourself. An audience is a rule that dynamically selects records matching a definition. Use lists when membership is externally determined (e.g. an import from a CSV, a suppression list from your ESP).


Create a List

POST /lists
Authorization: Bearer <token>
Content-Type: application/json

Request body

FieldTypeRequiredDescription
namestringYesHuman-readable list name. 1–200 chars.
descriptionstringNoFree-form description. Max 2000 chars.

Example

curl -X POST https://api.experiture.ai/public/v1/lists \
  -H "Authorization: Bearer <your_access_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Email Suppression – Spring 2026",
    "description": "Users who unsubscribed during the spring promo."
  }'

Response — 201 Created

{
  "success": true,
  "data": {
    "id": "lst_01HXYZ",
    "name": "Email Suppression – Spring 2026",
    "description": "Users who unsubscribed during the spring promo.",
    "type": "static",
    "containerType": "list",
    "membershipBehavior": "snapshot",
    "populationSource": "manual",
    "status": "active",
    "memberCount": 0,
    "growth": 0,
    "createdAt": "2026-04-21T15:30:00Z",
    "updatedAt": "2026-04-21T15:30:00Z",
    "computeStatus": "idle"
  },
  "correlationId": "<uuid>"
}

List All Lists

GET /lists?status=active&page=1&pageSize=50

Query parameters

NameTypeDescription
statusstringFilter by status: active, paused, or archived. Omit for all states.
pageintegerPage number (1-based). Default: 1.
pageSizeintegerResults per page, 1–200. Default: 50.

Response includes data.items[], data.page, data.pageSize, and data.total. Compute total pages as ceil(total / pageSize).


Get / Update / Delete

# Fetch
curl https://api.experiture.ai/public/v1/lists/lst_01HXYZ \
  -H "Authorization: Bearer <your_access_token>"
 
# Rename
curl -X PATCH https://api.experiture.ai/public/v1/lists/lst_01HXYZ \
  -H "Authorization: Bearer <your_access_token>" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Email Suppression – Archived" }'
 
# Archive (soft-delete)
curl -X DELETE https://api.experiture.ai/public/v1/lists/lst_01HXYZ \
  -H "Authorization: Bearer <your_access_token>"

Deleting a list is a soft-delete: the list is marked archived, removed from active evaluation, and returned in the normal list response envelope. The underlying records are not deleted.

Get list detail response

GET /lists/{list_id} returns a richer payload than the list collection. The additional fields are:

{
  "success": true,
  "data": {
    "id": "lst_01HXYZ",
    "name": "Email Suppression – Spring 2026",
    "description": "Users who unsubscribed during the spring promo.",
    "type": "static",
    "containerType": "list",
    "membershipBehavior": "snapshot",
    "populationSource": "import",
    "status": "active",
    "memberCount": 1248,
    "growth": 12,
    "createdAt": "2026-04-21T15:30:00Z",
    "updatedAt": "2026-04-25T09:00:00Z",
    "definitionVersion": 3,
    "computeStatus": "live",
    "lastMaterializedAt": "2026-04-25T09:00:05Z",
    "sourceImportJobId": "imp_01HXYZ",
    "queryTree": null
  },
  "correlationId": "<uuid>"
}

Detail-only fields

FieldDescription
definitionVersionMonotonically incrementing integer. Increments on every membership mutation. Use as a cache key.
computeStatusCurrent membership computation state. See table below.
lastMaterializedAtISO 8601 timestamp of the most recent successful membership computation. null if the list has never been materialized.
sourceImportJobIdThe import job that created or last populated this list, if populationSource is "import". null for manually-managed lists.
queryTreeInternal rule definition. null for static lists.

computeStatus values

StatusMeaning
idleNo computation in progress. Membership reflects the last successful run.
preparingComputation queued and about to start.
computingActively evaluating membership.
liveComputation completed successfully. lastMaterializedAt is current.
failedLast computation failed. lastMaterializedAt reflects the previous successful run.

Get Members

Page through the contact keys currently in a list. This is the read-side companion to the member upsert/remove endpoints and is the safest way to verify membership after an import or manual update.

GET /lists/{list_id}/members?page=1&pageSize=100

Query parameters

NameTypeDescription
pageintegerPage number (1-based). Default: 1.
pageSizeintegerNumber of member contact keys per page. Range: 1–500. Default: 100.

Example

curl "https://api.experiture.ai/public/v1/lists/lst_01HXYZ/members?page=1&pageSize=100" \
  -H "Authorization: Bearer <your_access_token>"

Response — 200 OK

{
  "success": true,
  "data": {
    "items": [
      { "contactKey": "alice@example.com" },
      { "contactKey": "bob@example.com" }
    ],
    "page": 1,
    "pageSize": 100,
    "total": 1248,
    "membershipVersion": 4
  }
}

membershipVersion increments whenever membership changes. If you're reconciling membership across multiple calls, capture the version returned by your last mutation and compare it to the read response before assuming you've read the newest snapshot.


Add Members

Upsert members by contact key. Idempotent — re-adding an existing member is a no-op.

POST /lists/{list_id}/members:upsert

Request body

FieldTypeRequiredDescription
contactKeysarray of stringYes1–10,000 contact key values per request.
normalizationModestringNo"none" or "email_lower_trim" (default). Normalizes keys before matching.

Example

curl -X POST https://api.experiture.ai/public/v1/lists/lst_01HXYZ/members:upsert \
  -H "Authorization: Bearer <your_access_token>" \
  -H "x-correlation-id: <uuid>" \
  -H "Content-Type: application/json" \
  -d '{
    "contactKeys": ["alice@example.com", "bob@example.com"],
    "normalizationMode": "email_lower_trim"
  }'

Response — 200 OK

{
  "success": true,
  "data": {
    "listId": "lst_01HXYZ",
    "memberCount": 1250,
    "membershipVersion": 3,
    "addedCount": 2,
    "retainedCount": 0,
    "removedCount": 0,
    "updatedAt": "2026-04-21T15:30:00Z"
  },
  "correlationId": "<uuid>"
}

Remove Members

POST /lists/{list_id}/members:remove

Same contactKeys / normalizationMode shape as upsert. Keys not found in the list are silently ignored.

Example

curl -X POST https://api.experiture.ai/public/v1/lists/lst_01HXYZ/members:remove \
  -H "Authorization: Bearer <your_access_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "contactKeys": ["alice@example.com"],
    "normalizationMode": "email_lower_trim"
  }'

Response — 200 OK

{
  "success": true,
  "data": {
    "listId": "lst_01HXYZ",
    "memberCount": 1248,
    "membershipVersion": 4,
    "addedCount": 0,
    "retainedCount": 0,
    "removedCount": 1,
    "updatedAt": "2026-04-21T15:31:00Z"
  },
  "correlationId": "<uuid>"
}

Errors

HTTP StatusCodeMeaning
400CDP_ETL.VALIDATION.REQUEST_INVALIDField validation failed (e.g. name too long or empty).
401CDP_ETL.AUTH.UNAUTHORIZEDMissing or invalid bearer token.
403CDP_ETL.AUTH.FORBIDDENToken lacks required list scope.
404CDP_ETL.NOT_FOUNDNo list with that list_id in this workspace.
409CDP_ETL.*Operation not allowed in the current state.
422HTTPValidationErrorPath or query parameters failed validation.

Constraints

  • Max members per list: 50,000,000 (contact support for higher).
  • Max keys per request: 10,000 (upsert or remove).
  • Max lists per workspace: 10,000.
  • Name uniqueness: list names are unique per workspace.

See Also