# Audience and lists

Manage marketing contacts, tag and segment them, and import a CSV in three steps.

An audience contact is a marketing record. It does not need an account — a
newsletter signup is a contact with no user behind it. Contacts are what
[email campaigns](/email/overview) send to and what audience workflow triggers
fire on.

```bash
curl -G https://api.console.buildbase.app/api/audience \
  -H "Authorization: $BUILDBASE_TOKEN" \
  --data-urlencode 'filter={"unsubscribed":false}' \
  --data-urlencode '$limit=50'
```

> **Note:**
  Define your audience attributes in the BuildBase console under **Audience →
  Attributes** before importing, so CSV columns have something to map onto.


## Contacts

| Method   | Path                           | Purpose                               |
| -------- | ------------------------------ | ------------------------------------- |
| `GET`    | `/api/audience`                | List contacts, paginated              |
| `GET`    | `/api/audience/:id`            | One contact                           |
| `POST`   | `/api/audience`                | Create                                |
| `PATCH`  | `/api/audience/:id`            | Update core fields                    |
| `PATCH`  | `/api/audience/:id/attributes` | Update custom attributes              |
| `PATCH`  | `/api/audience/:id/tags`       | Replace tags                          |
| `PATCH`  | `/api/audience/:id/block`      | Block — excluded from all sending     |
| `PATCH`  | `/api/audience/:id/unblock`    | Restore                               |
| `PATCH`  | `/api/audience/:id/link-user`  | Attach this contact to a user account |
| `DELETE` | `/api/audience/:id`            | Delete                                |

`link-user` is the bridge between the two identities. Until it is set, a contact
and a user with the same email are unrelated records — linking them is what lets
the console show one timeline for the person.

Per-contact history is available at `/:id/timeline`, `/:id/activities`,
`/:id/attributes-history`, `/:id/lists`, and `/:id/emails`.

## Lists

Audience lists are the unit a campaign sends to, managed at
`/api/audience-lists`. A contact's memberships are readable from
`GET /api/audience/:id/lists`.

Lists are also what workflows act on:
`actions.audience_list_add` and `actions.audience_list_remove` move contacts
between them, and `triggers.audience.added_to_list` fires when they do.

## Importing a CSV

Import is three calls. The CSV is parsed on upload, held in Redis, then
processed by a background job.

**1. Upload.** Send the file as multipart under the field name `file`:

```bash
curl -X POST https://api.console.buildbase.app/api/audience/import/upload \
  -H "Authorization: $BUILDBASE_TOKEN" \
  -F "file=@contacts.csv"
```

```json
{
  "importId": "9f1c…",
  "headers": ["Email", "Full name", "Company"],
  "rowCount": 1284,
  "previewRows": []
}
```

`previewRows` holds the first three parsed rows, so you can show the user what
their mapping will do before committing.

**2. Start.** Map CSV columns to fields and choose what happens on a duplicate:

```bash
curl -X POST https://api.console.buildbase.app/api/audience/import/start \
  -H "Authorization: $BUILDBASE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "importId": "9f1c…",
    "mapping": { "Email": "email", "Full name": "name", "Company": "company" },
    "duplicateStrategy": "update",
    "tags": ["webinar-2026"]
  }'
```

Returns `{ "jobId": "…" }`.

| Field               | Required | Notes                                                    |
| ------------------- | -------- | -------------------------------------------------------- |
| `importId`          | Yes      | From step 1                                              |
| `mapping`           | Yes      | CSV header → field. **Must map both `email` and `name`** |
| `duplicateStrategy` | No       | `update` (default) or `skip`                             |
| `tags`              | No       | Applied to every imported contact                        |

**3. Poll.** The job is asynchronous:

```bash
curl https://api.console.buildbase.app/api/audience/import/status/$JOB_ID \
  -H "Authorization: $BUILDBASE_TOKEN"
```

```json
{ "state": "completed", "progress": 100, "result": {}, "failedReason": null }
```

An unknown job id returns `404`. Fetch a template with the org's attribute
columns already present from `GET /api/audience/import/sample`.

> **Note:**
  **Uploaded CSVs expire after 30 minutes.** The parsed file is held in Redis
  between steps 1 and 2, so a user who leaves the mapping screen open too long
  gets `400` with "Import data expired. Please re-upload the CSV file."

Do not treat `importId` as durable — it is a handle on a short-lived buffer,
not a stored import.



Mapping validation rejects the whole import if `email` or `name` is missing, so
check the mapping before calling `start` rather than after.

## Exporting

`GET /api/audience/export` returns the audience as CSV. It honours the same
`filter` parameter as the list endpoint, so you can export a segment rather than
everything.

## Next Steps

- [Email campaigns](/email/overview) — send to an audience list.
- [Workflows](/workflows/triggers-and-actions) — audience triggers and actions.
- [User attributes](/users/user-attributes) — custom fields on accounts.
