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
| Operation | Endpoint | Scope |
|---|---|---|
| Create list | POST /lists | cdp:lists:write |
| List all lists | GET /lists | cdp:lists:read |
| Get list | GET /lists/{list_id} | cdp:lists:read |
| Get list members | GET /lists/{list_id}/members | cdp:lists:read |
| Update list | PATCH /lists/{list_id} | cdp:lists:write |
| Delete list | DELETE /lists/{list_id} | cdp:lists:write |
| Add members | POST /lists/{list_id}/members:upsert | cdp:lists:write |
| Remove members | POST /lists/{list_id}/members:remove | cdp: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/jsonRequest body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Human-readable list name. 1–200 chars. |
description | string | No | Free-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=50Query parameters
| Name | Type | Description |
|---|---|---|
status | string | Filter by status: active, paused, or archived. Omit for all states. |
page | integer | Page number (1-based). Default: 1. |
pageSize | integer | Results 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
| Field | Description |
|---|---|
definitionVersion | Monotonically incrementing integer. Increments on every membership mutation. Use as a cache key. |
computeStatus | Current membership computation state. See table below. |
lastMaterializedAt | ISO 8601 timestamp of the most recent successful membership computation. null if the list has never been materialized. |
sourceImportJobId | The import job that created or last populated this list, if populationSource is "import". null for manually-managed lists. |
queryTree | Internal rule definition. null for static lists. |
computeStatus values
| Status | Meaning |
|---|---|
idle | No computation in progress. Membership reflects the last successful run. |
preparing | Computation queued and about to start. |
computing | Actively evaluating membership. |
live | Computation completed successfully. lastMaterializedAt is current. |
failed | Last 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=100Query parameters
| Name | Type | Description |
|---|---|---|
page | integer | Page number (1-based). Default: 1. |
pageSize | integer | Number 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:upsertRequest body
| Field | Type | Required | Description |
|---|---|---|---|
contactKeys | array of string | Yes | 1–10,000 contact key values per request. |
normalizationMode | string | No | "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:removeSame 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 Status | Code | Meaning |
|---|---|---|
400 | CDP_ETL.VALIDATION.REQUEST_INVALID | Field validation failed (e.g. name too long or empty). |
401 | CDP_ETL.AUTH.UNAUTHORIZED | Missing or invalid bearer token. |
403 | CDP_ETL.AUTH.FORBIDDEN | Token lacks required list scope. |
404 | CDP_ETL.NOT_FOUND | No list with that list_id in this workspace. |
409 | CDP_ETL.* | Operation not allowed in the current state. |
422 | HTTPValidationError | Path 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
- Audiences API — rule-based dynamic cohorts
- Records API — write the records lists reference
- Rate Limits