# Lists and broadcasts API

Broadcasts use lists + a broadcast job on the `broadcast` route.

## Lists

```bash
curl -sS -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"Newsletter"}' \
  "$BASE/api/practices/$PRACTICE_ID/lists"

curl -sS -H "Authorization: Bearer $TOKEN" \
  "$BASE/api/practices/$PRACTICE_ID/lists"
```

List payloads include `subscriber_count` and `active_count`.

## Subscribers

```bash
# DOI (default) — pending + confirm email
curl -sS -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"email":"a@example.com"}' \
  "$BASE/api/practices/$PRACTICE_ID/lists/$LIST_ID/subscribers"

# already-consented import
curl -sS -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"email":"a@example.com","status":"active"}' \
  "$BASE/api/practices/$PRACTICE_ID/lists/$LIST_ID/subscribers"

curl -sS -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"emails":["a@example.com","b@example.com"]}' \
  "$BASE/api/practices/$PRACTICE_ID/lists/$LIST_ID/subscribers/import"

curl -sS -H "Authorization: Bearer $TOKEN" \
  "$BASE/api/practices/$PRACTICE_ID/lists/$LIST_ID/subscribers"

curl -sS -X DELETE -H "Authorization: Bearer $TOKEN" \
  "$BASE/api/practices/$PRACTICE_ID/lists/$LIST_ID/subscribers/$SUB_ID"
```

Without `status: active` / `skip_doi`, create starts double opt-in. Public confirm: `GET /api/public/confirm?token=…`.

Public subscribe (`POST /api/public/subscribe`) also starts DOI and does **not** return `confirm_url`.

## Broadcasts

```bash
curl -sS -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "list_id": "'"$LIST_ID"'",
    "name": "April newsletter",
    "subject": "Praxis news",
    "text_body": "Hello",
    "html_body": "<p>Hello</p>",
    "route": "broadcast"
  }' \
  "$BASE/api/practices/$PRACTICE_ID/broadcasts"

curl -sS -H "Authorization: Bearer $TOKEN" \
  "$BASE/api/practices/$PRACTICE_ID/broadcasts"
```

The create response includes the broadcast ledger row and a `job_id`. Broadcasts require marketing eligibility (DKIM, warm-up, Impressum, consent).

Optional create fields: `segment_id`, `ab_enabled`, `ab_metric` (`open` or `click`), `ab_sample_pct`, `ab_test_hours`, `variants`.

A/B first wave sends only the sample. After the test window (or `POST …/broadcasts/{id}/ab-winner`) the remainder receives the winner.

| Method | Path | Purpose |
| --- | --- | --- |
| `GET` | `/api/practices/{id}/broadcasts` | List |
| `GET` / `PATCH` | `/api/practices/{id}/broadcasts/{broadcastID}` | Read or update a draft |
| `POST` | `…/start` | Queue now |
| `POST` | `…/schedule` | Body `scheduled_at` (RFC3339) |
| `POST` | `…/cancel` | Cancel |
| `POST` | `…/test` | Test send |
| `GET` | `…/stats` | Delivery stats |
| `GET` | `…/links` | Link clicks |
| `GET` / `PUT` | `…/variants` | A/B variants |
| `DELETE` | `/api/practices/{id}/lists/{listID}` | Delete a list |
| `DELETE` | `…/lists/{listID}/subscribers/{subID}` | Remove a subscriber |

Merge tags and `{{#if}}` / `{{#unless}}` apply per recipient from the CRM contact.

Automations: `GET/POST /api/practices/{id}/automations`. See the [Automations](/automations) guide.

BI CSV: `GET /api/practices/{id}/exports/bi?kind=stats|broadcasts|bounces|scores|audit`.
