> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Register PHI-adjacent audiences

> Walkthrough of the PHI-adjacent audience registry: when to designate a list or segment, how the atomic full-replacement PUT works, how the campaign launch precheck reacts, and how to read the audit trail.

# Register PHI-adjacent audiences

The PHI-adjacent audience registry is your organization's list of contact lists and segments whose members carry PHI — for example, patients opted into treatment outreach. The [HIPAA controls](/compliance/hipaa#phi-adjacent-audience-registry) reference documents the two endpoints; this guide walks through running them in production: what to designate, how the write semantics work, what happens at campaign launch, and how to read the audit trail.

The registry is **tenant-owned**. Devotel never designates audiences on your behalf and never scopes PHI for you — the designations are your attestation, they are BAA-gated controls you operate, and they only take effect once HIPAA is in scope for your organization. If you have not already executed the BAA and enabled HIPAA mode, run the [HIPAA onboarding](/guides/hipaa-onboarding) sequence first.

## 1. When to mark an audience PHI-adjacent

Designation follows **provenance**: mark the audiences whose source data contains PHI, regardless of what any individual campaign sends to them. An audience is PHI-adjacent because of where its members came from — a patient appointment-reminder import, a treatment-outreach opt-in list — not because of the copy you happen to write this week. That is why the designation lives on the audience itself rather than on a campaign: whichever campaign picks the audience up, the designation travels with it.

Mark an audience PHI-adjacent when:

* Its members were imported from a system that holds PHI (an EHR export, a patient portal opt-in sync).
* The list or segment is filtered or assembled on PHI-bearing criteria (diagnosis-adjacent tags, treatment cohorts).
* Your compliance officer's data map records the audience as PHI in scope.

Do not designate an audience "just in case." A designation attaches the [BAA launch gate](#4-how-the-launch-precheck-uses-the-registry) to every campaign that uses the audience — designating audiences that do not carry PHI blocks launches for no compliance reason.

**Who can attest:** both endpoints require the `owner` or `admin` role — the same gate the [BAA endpoints](/compliance/baa) use. A `developer` or `viewer` receives `403`. Keep the designation decision with your HIPAA compliance officer; the platform records *who* changed the registry on every write (see [Audit trail](#5-audit-trail)).

## 2. Choosing list vs segment ids

The registry holds audience ids — each entry is either a **contact-list id** or a **segment id**, passed as a plain string. The campaign launch precheck only resolves `list`- and `segment`-type audiences against the registry; audiences assembled per contact (all contacts, CSV upload, manual entry) are evaluated recipient-by-recipient at send time instead, so they have no registry id to designate.

To rederive the id for a designation:

```bash theme={null}
# Contact lists
GET /api/v1/contacts/lists

# Segments
GET /api/v1/contacts/segments
```

Copy the `id` field of the list or segment you are designating. Ids are 1–128 characters after trimming; anything longer or empty is rejected with `422` on write. The registry holds at most **500** ids per organization — a `PUT` carrying more returns `422`.

> Designate the **source** id, not a downstream copy. If a PHI-bearing list feeds a derived segment, decide whether the derived segment also contains PHI and designate it explicitly — the precheck checks the id the campaign actually references, nothing else.

## 3. The atomic PUT swap

The registry has **one** write operation: a full-replacement `PUT`. There is no `PATCH`, no per-id `DELETE` — every write replaces the entire designated set in a single atomic statement, so a concurrent `GET` never sees a partially applied update.

```bash theme={null}
PUT /api/v1/compliance/hipaa/phi-audiences
{
  "audience_ids": ["list_9f2c1a", "seg_4b7e20", "list_31dc88"]
}
```

The response echoes the stored set:

```json theme={null}
{
  "data": {
    "audience_ids": ["list_9f2c1a", "seg_4b7e20", "list_31dc88"],
    "replaced": true
  }
}
```

The body is **idempotent**: sending the same full set twice produces the same stored registry and two distinct audit rows. An empty array clears every designation:

```bash theme={null}
PUT /api/v1/compliance/hipaa/phi-audiences
{
  "audience_ids": []
}
```

Because the write is a swap, every client must follow **read-modify-write**: `GET` the current registry, add or remove your id in the result, and `PUT` the whole set back. Never build the body from local state alone — you would silently drop designations another operator added.

To lift one designation, `PUT` the registry without that id. To re-designate, `PUT` it with the id added back. Individual ids that survive a swap are unchanged; only membership in the set matters.

## 4. How the launch precheck uses the registry

Two gates protect PHI at different points, and the registry feeds the first:

1. **Launch precheck (campaign-level, hard gate).** Before a campaign leaves draft/scheduled, the precheck resolves its audience id against the registry. A designated id plus a BAA that is not `executed` and in-term refuses the launch with `422 HIPAA_BAA_REQUIRED` — before a single recipient is enrolled. If the compliance state cannot be read, the precheck fails closed with `500 HIPAA_BAA_GATE_DB_FAIL` rather than silently admitting the audience.
2. **Per-recipient send gate (message-time, unchanged).** The existing send gate still applies to every individual send and does not consult the registry — legacy one-off sends are governed by it alone.

A blocked launch surfaces with the refusal below. The `details.reason` tells you exactly which BAA state de-blocks it:

```json theme={null}
{
  "error": {
    "code": "HIPAA_BAA_REQUIRED",
    "status": 422,
    "message": "The designated PHI-adjacent audience for this campaign requires an executed Business Associate Agreement (BAA) before outbound sends are permitted.",
    "details": {
      "reason": "pending",
      "audience": { "type": "list", "id": "list_9f2c1a" }
    }
  }
}
```

| `reason`     | What it means                                         | How to de-block                                                 |
| ------------ | ----------------------------------------------------- | --------------------------------------------------------------- |
| `not_signed` | PHI is attested in scope but no BAA was ever executed | Execute the BAA — [BAA flow](/compliance/baa)                   |
| `pending`    | BAA execution was started but not completed           | Finish the execute step (`POST /api/v1/compliance/baa/execute`) |
| `expired`    | The executed BAA passed its one-year term             | Re-execute the BAA                                              |

There are **two** ways to de-block, and they are compliance decisions, not platform decisions:

* **Resolve the BAA** — execute or re-execute it so the gate passes. This is the right path when the audience genuinely carries PHI.
* **Remove the designation** — `PUT` the registry without the audience id. This is the right path *only* when the audience was designated in error. Lifting a designation to route around the gate is visible in your own audit log.

In the dashboard's campaign wizard, choosing a designated audience shows an advisory warning at the audience step. The warning does not block the **Next** button — the designation may be lifted or the BAA executed before launch — but the hard gate at launch always applies.

## 5. Audit trail

Two record classes land in your organization's audit log:

* **`hipaa.phi_audiences.set`** — one row per `PUT`, recording the acting user, the organization, and the full post-write id set. This is your versioning story: the registry has no separate revision resource — the sequence of audit rows *is* the version history. To reconstruct what was designated at a point in time, walk the `set` rows back; to revert, `PUT` a prior row's id set.
* **`HIPAA_BAA_REQUIRED` launch refusals** — each blocked launch is logged with the refusing reason and the audience under evaluation. These rows double as your incident queue: a refusal means either compliance work is pending (BAA not executed) or a designation and a campaign disagree.

Review both classes on a cadence that matches your compliance program — weekly is a workable default for an active healthcare workspace. Export the audit log alongside your PHI access log when assembling evidence for an external audit; the [evidence binder](/compliance/evidence-binder) HIPAA pack rolls BAA posture and PHI access logging into a signed download.

**Incident runbook for an unexpected refusal:**

1. Read the refusal's `details.reason` and `details.audience.id`.
2. Check `GET /api/v1/compliance/baa/` — if the BAA is `pending`/`expired`/not executed, resolve it through the [BAA flow](/compliance/baa).
3. If the BAA is healthy, check whether the audience should be designated at all: `GET /api/v1/compliance/hipaa/phi-audiences` and compare against your data map. Lift an erroneous designation with a swap (`PUT` without the id).
4. Record the outcome in your own incident register — the audit rows above are the evidence you cite.

## 6. Troubleshooting concurrent-overwrite conflicts

The registry `PUT` itself never returns `409` — the single-statement atomic swap means a write always commits, and the last writer wins. The conflict risk is **lost updates between operators**, not rejected writes:

* Operator A and operator B both `GET` the registry.
* A adds `list_aaa` and `PUT`s. B — working from the pre-A snapshot — adds `list_bbb` and `PUT`s.
* B's swap silently drops `list_aaa`.

Mitigations:

* **Read immediately before writing.** Keep the read-modify-write window short; do not carry a fetched registry across an editing session — re-`GET` when you are ready to `PUT`.
* **Verify after writing.** `GET` once more and confirm your id is present and no unrelated designation was lost. If something disappeared, the audit log's `hipaa.phi_audiences.set` rows show whose write overwrote it and what set to restore.
* **Serialize registry edits organizationally.** Because designation is a compliance attestation, route edits through one role (the compliance officer) rather than spreading them across operators — a procedural fix that removes the race entirely.

If you see `422` instead of success, the cause is validation, not conflict: more than **500** ids, an empty id after trimming, or an id over 128 characters. Trim and retry with the full set.

## See also

* [HIPAA compliance controls](/compliance/hipaa) — the full control reference the registry feeds
* [BAA — Business Associate Agreement](/compliance/baa) — the lifecycle the launch precheck enforces
* [HIPAA onboarding: from BAA to audit-ready](/guides/hipaa-onboarding) — the sequence that brings a healthcare workspace to audit-ready before you designate audiences
* [Send gates](/compliance/send-gates) — the per-recipient gate that complements the launch precheck
