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

# Manage audience profiles

> Store customer-supplied campaign attributes and consent assertions for your team, or import them from CSV.

Audience profiles hold contact details that you supply for campaign audiences. A
profile belongs to your team and is identified by its E.164 phone number. Profile
attributes and consent assertions do not override [STOP opt-outs](/guides/campaigns/opt-outs)
or grant permission to send a campaign.

Creating or updating profiles and starting imports require enrollment in the
audience profiles beta. Reading profiles, polling or cancelling imports, and
deleting profiles stay available if your team leaves the beta, so you can
always inspect and remove the data you stored. Use a team API key with
`campaigns:read` to read profiles and import reports, or `campaigns:manage` to
change them. Project tokens cannot access team profiles.

## Create or update a profile

Send the phone number in the path. Provide `tags` and `attributes` even when
they are empty. Omit `expectedRevision` for a new profile; use the revision from
the latest read when replacing an existing profile.

```bash theme={null}
curl -X PUT "https://api.polymorfa.com/platform/audience-profiles/%2B14155550123" \
  -H "Authorization: Bearer $POLYMORFA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "tags": ["launch"],
    "attributes": { "first_name": "Ada" },
    "provenance": { "source": "api", "reference": "signup-42" }
  }'
```

The response includes the profile's `revision`. A stale revision returns `409`;
read the profile again before applying your intended replacement. A team can
hold at most 100,000 profiles, including deleted profiles pending expiry. A
deleted profile releases capacity only after its deletion record is purged.

To record a consent assertion, include `consent` with `purpose: "campaigns"`,
`source`, a reference to your evidence, `allowed`, and `assertedAt` as Unix
milliseconds. Read assertions with
`GET /platform/audience-profiles/{phone}/consent`. Assertions record what you
supplied; they do not clear an opt-out.

## Delete and recreate a profile

Delete a profile with its current revision:

```bash theme={null}
curl -X DELETE "https://api.polymorfa.com/platform/audience-profiles/%2B14155550123?expectedRevision=2" \
  -H "Authorization: Bearer $POLYMORFA_KEY"
```

Deletion removes the profile's tags, attributes, consent assertions and its
membership in every audience, together with that member's variables. It also
clears the preview row of each of those audiences. It does not change
recipients already added to a campaign, including draft campaigns, CSV files you
uploaded earlier, or your team's opt-outs. Remove a draft campaign's recipient
separately if you no longer want to contact them.

Polymorfa keeps a deletion record with the phone number and a new `revision`
for 30 days, so delayed writes cannot bring the contact back. During those 30
days:

* adding the phone to an audience returns `409` naming its row, numbered as
  in `invalidRows`; nothing in that request is added
* recreating the profile requires `expectedRevision` set to the `revision` the
  delete returned; a put without it returns `409` with a message naming that
  revision

## Import profiles from CSV

Submit one UTF-8 CSV file of at most 10 MiB and 10,000 data rows per job. The
header must include `phone`, `tags`, and `attributes` unless you provide a
column mapping. `tags` contains a JSON array; `attributes` contains a JSON
object. Optional columns are
`expectedRevision`, `provenanceReference`, `consentAllowed`,
`consentAssertedAt`, and `consentReference`. Supply all three consent columns
together when recording an assertion. A row for a phone that already has a
profile, including one deleted in the last 30 days, must carry that profile's
current `expectedRevision`; otherwise the row is reported as a conflict and the
profile is left unchanged. The timestamp uses a date-time that
JavaScript can parse, such as `2026-09-27T00:00:00Z`.

If your CSV uses different names, pass `mapping` in the import request. Map
`phone`, `tags`, and `attributes` to their exact header names; map any optional
fields you want to import. Unmapped columns are ignored. A file can have at
most 32 columns, and each header name can use at most 64 UTF-8 bytes.

```csv theme={null}
phone,tags,attributes,provenanceReference
+14155550123,"[""launch""]","{""first_name"":""Ada""}",signup-42
```

Encode the file as standard padded base64 and send it with a replay key:

```bash theme={null}
base64 < profiles.csv | tr -d '\n' \
  | jq -Rs '{replayKey: "profiles-sep-27", csvBase64: .}' \
  | curl -X POST "https://api.polymorfa.com/platform/audience-profile-imports" \
      -H "Authorization: Bearer $POLYMORFA_KEY" \
      -H "Content-Type: application/json" \
      --data-binary @-
```

The file is piped through `jq` and `curl`, so a file near the 10 MiB limit does
not hit the shell's argument size limit.

For a file with `Mobile`, `Labels`, and `Fields` headers, add this object to
the JSON request:

```json theme={null}
{"mapping":{"phone":"Mobile","tags":"Labels","attributes":"Fields"}}
```

Only one import per team can be queued or running. Repeating the same replay
key with the same file, mapping, and key returns its existing job; reusing it
with different content or mapping returns `409`. An oversized file or row count
returns `413`.

Use `GET /platform/audience-profile-imports/{jobId}` to check the job. The
report includes processed, created, updated, invalid, and conflicting counts,
plus up to 20 row numbers and reason codes for each failure category. It does
not include submitted phone numbers or attribute values. When the job completes,
its CSV is removed. The aggregate report remains available for 30 days.

An import that has not finished seven days after you submit it is cancelled
and its CSV removed; profile changes from batches that already ran remain.

Use `DELETE /platform/audience-profile-imports/{jobId}` to cancel the remaining
work. Profile changes committed by earlier batches remain. Cancellation removes
the CSV and retains the aggregate report for 30 days.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.