# List a contact's preferences

`GET /v1/contacts/{contact_id}/preferences`

Returns the preferences on record for the contact's current handles: rows keyed to their email address on the email channel, and to their phone number on SMS and WhatsApp. A contact with no handles, or with no statements on record, returns an empty page.

Rows are keyed by handle, not by contact: changing a contact's email address or phone number changes which rows this returns, and the old handle's rows remain in force for anything still sent to it.

## Code samples

### TypeScript

```ts
for await (const preference of bird.contacts.preferences.list(
  "con_01krdgeqcxet5s7t44vh8rt9mg",
)) {
  console.log(preference.channel, preference.status);
}
```

### Python

```py
for preference in client.contacts.preferences.list("con_01krdgeqcxet5s7t44vh8rt9mg"):
    print(preference.channel, preference.status)
```

### Go

```go
for pref, err := range client.Contacts.Preferences.List(context.Background(), "con_01krdgeqcxet5s7t44vh8rt9mg", bird.ContactsPreferencesListParams{}) {
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(*pref.Channel, *pref.Status)
}
```

### PHP

```php
foreach ($bird->contacts->preferences->list('con_01krdgeqcxet5s7t44vh8rt9mg') as $preference) {
    echo $preference->getChannel(), ' ', $preference->getStatus(), "\n";
}
```

### cURL

```sh
curl -X GET "https://us1.platform.bird.com/v1/contacts/{contact_id}/preferences" \
  -H "Authorization: Bearer $TOKEN" \
  --url-query "limit=25"
```

## Example response `200`

```json
{
  "data": [
    {
      "id": "prf_01krdgeqcxet5s7t44vh8rt9mg",
      "channel": "sms",
      "handle": "+15550001234",
      "sender_scope": "+15557654321",
      "topic_id": null,
      "status": "revoked",
      "coverage": "non_transactional",
      "origin": "api_key",
      "source": "signup-form-v2",
      "contact_id": "con_01krdgeqcxet5s7t44vh8rt9mg",
      "created_at": "2026-05-20T09:14:52Z",
      "updated_at": "2026-05-25T16:42:01Z"
    }
  ],
  "next_cursor": "eyJ2IjoxLCJzIjoiXCIyMDI2LTA1LTI1VDE0OjAzOjEwWlwiIiwiaSI6IjAxOTJmM2IxLTRjN2UtN2EyYi05ZDYxLThmM2E1YzJlN2I0MCJ9",
  "prev_cursor": null,
  "refresh_cursor": "eyJ2IjoxLCJzIjoiXCIyMDI2LTA1LTI1VDE2OjQyOjAxWlwiIiwiaSI6IjAxOTJmM2IxLTllMDQtN2NkMy1iODE3LTJhNmY0ZDFjOGUwOSJ9"
}
```

## Path parameters

- `contact_id` (string): ID of the contact.

## Query parameters

- `limit` (integer): Maximum number of items to return per page.
- `starting_after` (string): Cursor from the `next_cursor` field of a previous list response. Returns items immediately after the cursor position in the current sort order.
- `ending_before` (string): Cursor from the `prev_cursor` or `refresh_cursor` field of a previous list response. Returns items immediately before the cursor position in the current sort order. `prev_cursor` returns the preceding page. `refresh_cursor` anchors at the first row of that response, which on a newest-first sort is how to fetch the items that have appeared since.

## Response body

- `data` (array of object, required): Page of preferences, most recently created first.
- `data.id` (string, required)
- `data.channel` (string, required)
- `data.handle` (string, required): Who the statement is about: an email address on the email channel, a phone number in E.164 format on SMS and WhatsApp.
- `data.sender_scope` (nullable string, required): The sender the statement is limited to, or null when it covers the whole channel. On SMS this is the originator the person replied to; on WhatsApp it identifies the business account that messaged them. Email preferences are always channel-wide, so it is always null there.
- `data.topic_id` (nullable string, required): The topic the statement is limited to, or null when it covers every topic. Part of the key that identifies a statement, alongside `sender_scope`.
- `data.status` (string, required)
- `data.coverage` (string, required)
- `data.effective_at` (string, required): When the statement was made, as reported by whoever made it. This is what orders one key's statements: a write dated before this moment is refused rather than applied.
- `data.origin` (string, required)
- `data.source` (nullable string): Free-form note on where the statement came from, as supplied when it was recorded: a form name, an import batch, a campaign. Null when none was given.
- `data.consented_at` (nullable string): When the person consented, as evidenced by whoever asserted the grant. Null on statements that carry no consent evidence, including every opt-out.
- `data.contact_id` (nullable string): The contact whose handle matched when the statement was recorded. Null when no contact matched at that moment; it is not updated when contacts change later.
- `data.created_at` (string, required)
- `data.updated_at` (string, required)
- `next_cursor` (nullable string, required): Cursor for the next page. Pass back as `starting_after` to advance forward. `null` when no next page exists.
- `prev_cursor` (nullable string, required): Cursor for the previous page. Pass back as `ending_before` to step backward. `null` when no previous page exists.
- `refresh_cursor` (nullable string, required): Refresh anchor, the first row of this response. Pass back as `ending_before` to fetch what precedes it in the current sort order. On a newest-first sort those are the items that have appeared since; on any other sort they are the items that sort earlier, so refreshing such a list means re-fetching it instead. Non-`null` whenever `data` is non-empty; `null` only on an empty page. Distinct from `prev_cursor`.