---
title: "Get Contact Preferences"
method: GET
path: "/v1/contacts/{contact_id}/preferences"
tags: ["notifications"]
---

# Get Contact Preferences

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

## Request

- Base URL: `https://api.elaan.io` (the document declares no server; this is the origin it was published from)
- URL: `https://api.elaan.io/v1/contacts/{contact_id}/preferences`
- Auth: none declared

The contact's whole preference centre in one read: their saved preferred
`language` plus the notification-type × channel matrix (`types`) — every type
they can act on × the channels it can reach, with the effective on/off and
whether it's an explicit override. Powers a client preferences UI without a
second call. Contact token reads its own; a tenant token reads any contact's.

Served here rather than by `contact` because it composes three aggregates
(contact, notification_type, channel_template) and `contact` is contractually
independent of the template module.

NB: the payload is an object `{language, types}`; `types` is the former
top-level array (the `@elaanio/*` SDK's `getPreferences` moved in lockstep).

## Path parameters

- `contact_id` string, required

## Headers

- `authorization` string, nullable

## Response `200`

Successful Response

- ContactPreferencesResponse: The whole contact preference centre in one payload: the saved preferred `language` (null = default) plus the notification `types` matrix. A single contact-token read renders everything the contact previously chose.
  - `language` string, nullable, required
  - `types` TypePreferenceResponse[], required
    - `notification_type_key` string, required
    - `channels` ChannelPreferenceResponse[], required
      - `channel` 'email' | 'inbox' | 'push' | 'web_push', required: A delivery channel a notification can be sent over. This is an enum (not a bare string) so adding SMS / WHATSAPP later is a one-line change and every per-channel mapping (notification-type defaults, contact preferences) extends automatically. Crucially, a newly-added channel is **opted out by default** everywhere: `NotificationType.default_for` returns False for a channel absent from `channel_defaults`, and a contact has no preference until they set one — so an existing install gains a channel that delivers to nobody until a tenant explicitly enables it. That's why adding a member needs no data migration. Subclassing `str` makes members compare/serialize as their value ("email"), which keeps JSON and dict keys clean.
      - `enabled` boolean, required
      - `overridden` boolean, required

## Other responses

- `404`: No contact with this id
- `422`: Validation Error

---

[API](https://skmtc.dev/elaan/apis/elaan-api.md) · [All operations](https://skmtc.dev/elaan/apis/elaan-api/llms.txt) · [OpenAPI document](https://skmtc.dev/elaan/apis/elaan-api/revisions/c96a7e6399aa?raw)
