---
title: Contacts
description: "What a contact's page shows, what can be changed there, and the API calls that read and edit the same record."
---

Every address in **Contacts** opens the contact's page. So does each person in a campaign's results, and
**Open contact** in the preview's subscriber picker.

## What the page shows

| Part | What it holds |
| --- | --- |
| Header | The address, first name and the day the contact was added |
| Suppressed | Shown when no publication may email the address: why (bounced, marked as spam, unsubscribed everywhere, or by hand), where that came from, and when |
| Publications | Each publication the contact is on: subscribed or not, how they joined (signed up, imported, or moved from another tool), the source and date of that consent, tags, and who ended the subscription |
| Fields | `first_name` and every attribute, by the name a merge tag uses: `city` fills `{{city}}` |
| Emails | The latest 50 emails sent to the contact, newest first, with what happened to each |

Who ended a subscription is one of:

| Value | Meaning |
| --- | --- |
| `link` | The unsubscribe link in an email, or the mailbox's own unsubscribe button |
| `import` | A CSV import or an API call marked them unsubscribed |
| `profile:<address>` | Someone in the workspace, on the contact's page |
| none | The subscription ended before this was recorded |

What happened to an email is the worst of what Amazon reported: rejected, marked as spam, bounced, unsubscribed
through it, a temporary bounce, delivered, or sent and not reported yet. An email can also be queued, skipped (the person
unsubscribed or was suppressed before their turn) or not sent. Test sends go to the people who write the
campaign, so they are not listed. Opens and clicks are not tracked.

## What can be changed

Admins and members can change these; contributors see the page but not the buttons.

- **Fields.** Field names are lowercase letters, numbers and `_`, starting with a letter, up to 40 characters.
  Values are up to 200 characters; a contact has at most 100 fields. A saved field keeps its name, because
  campaigns may already use it; remove it and add a new one to rename it. If someone else saved the contact after
  you opened it, your save is refused so their change is not lost.
- **Unsubscribe** from one publication. It cannot be undone from Pneumail: only the person can sign up again.
- **Suppress** the address, so no publication in the workspace emails it. Suppressions are only added here.

## Reading and editing a contact with the API

`{contact}` is the contact's id (`ct_...`) or address. The id keeps the address out of request logs.

```
GET /api/v1/contacts/{contact}
Authorization: Bearer <key with contacts:read>
```

```json
{
  "ok": true,
  "contact": {
    "id": "ct_...",
    "email": "ana@example.org",
    "firstName": "Ana",
    "attributes": { "city": "Tacoma", "seats": 3 },
    "createdAt": "2026-03-14T10:00:00.000Z",
    "version": "1792310400000000",
    "subs": [
      { "publicationId": "pub_...", "status": "unsubscribed", "consent": "signup", "source": "form:/updates",
        "consentedAt": "2026-03-14T10:00:00.000Z", "unsubscribedAt": "2026-10-02T09:00:00.000Z",
        "unsubscribedBy": "profile", "tags": ["resident"] }
    ],
    "suppression": null,
    "sends": [
      { "id": "snd_...", "kind": "campaign", "campaignId": "cmp_...", "subject": "October news", "publicationId": "pub_...",
        "status": "sent", "at": "2026-10-21T10:00:00.000Z", "deliveredAt": "2026-10-21T10:00:02.000Z", "bounce": null,
        "complainedAt": null, "unsubscribedAt": null, "rejected": false, "error": null }
    ],
    "moreSends": false
  }
}
```

The API says `profile` where the page names the person who unsubscribed someone, and `Contact page` for a
suppression added there, so a key never learns who in the workspace did it.

```
PATCH /api/v1/contacts/{contact}
Authorization: Bearer <key with contacts:write>

{ "version": "1792310400000000", "firstName": "Ana", "attributes": { "city": "Tacoma", "seats": 4 } }
```

This replaces the first name and every field, with the same rules as the page: send the full set, and a field
left out is removed. `version` comes from the last GET; if the contact changed since, the answer is 409 and nothing
is saved. An unknown contact is 404.

To unsubscribe someone through the API, import them with `status: "unsubscribed"`. To suppress an address, add it
to `/api/v1/suppressions`.
