---
title: "Create a recipient"
method: POST
path: "/api/v1/recipients"
tags: ["Recipients"]
---

# Create a recipient

`POST /api/v1/recipients`

## Request

- Base URL: `https://wardenpoint.com` (the document declares no server; this is the origin it was published from)
- URL: `https://wardenpoint.com/api/v1/recipients`
- Auth: API key in header `X-API-Key`

Creates a recipient, optionally together with the contacts to reach them on.

**A recipient without a contact receives NOTHING.** It still appears in listings, still joins groups, still sits in escalation chains — and every alert addressed to it goes nowhere, silently. Send `contacts` in this call and that state never exists: the recipient and its channels are written in one transaction, so a client that dies halfway leaves nothing behind, and a client that forgets the second call cannot happen. If you create without contacts, the response says so — `data.delivery.deliverable` is false and a top-level `warnings` entry names the endpoint that fixes it.

Contacts are a list because one on-call person usually needs more than one channel; each entry takes exactly the same fields as `POST /recipients/{recipient}/contacts` (same rules, same normalisation, same errors), and a recipient holds at most one contact per type, so two entries of the same type are rejected before anything is written. At most one entry may set `is_primary`.

**Retries are safe.** Repeating the call with a name that already exists returns the existing recipient with 200 instead of creating a duplicate. Contacts follow the same rule: an entry whose type the recipient already has, with the same address, is treated as already done and changes nothing. Same type but a DIFFERENT address is a real conflict and answers 409 — the address you asked for is not the address stored, and pretending otherwise would report a success that did not happen; fix it through the contact endpoints. Only the address is compared: `is_primary` and `label` of an existing contact are left alone. For `internal_sip`, repeating `__create_new__` when an extension is already assigned is a no-op rather than a second allocation.

A blank name is never matched against existing recipients — every blank call creates a new row.

## Request body

- object
  - `name` string, nullable: Display name. Omit or leave blank to get a generated 'New recipient #N' name.
  - `contacts` RecipientContactCreateRequest[]: Delivery addresses to attach in the same call, at most 20 and at most one per type. Omit it and the recipient is created with no way to reach them. Each entry is validated and stored exactly as the single-contact endpoint would do it, including E.164 normalisation of phone numbers and internal_sip extension allocation. If any entry is rejected, nothing is written at all — not even the recipient.
    - `contact_type` string, required: Contact type slug: telegram, phone, email, internal_sip, slack, msteams, … A recipient may hold at most one contact per type.
    - `contact_type_id` integer: Numeric contact type id, as used by the dashboard. Prefer contact_type — the numeric id is not exposed to API clients.
    - `value` string: The address itself: a Telegram handle, a phone number (normalised to E.164 on save), an email. May be empty only for externally bound types (slack, telegram_bot_chat_id, msteams). For internal_sip pass an existing free extension number, or "__create_new__" to have one allocated.
    - `is_primary` boolean: Makes this the primary contact and clears the flag on every other contact of the recipient. Defaults to false.
    - `label` string, nullable: Short human note shown next to the contact.

## Response `200`

A recipient with this name already existed and was returned; any contacts sent were already present with the same address, so nothing changed.

- object
  - `data` Recipient: A person or endpoint alerts are delivered to.
    - `uuid` string, uuid: Stable identifier — every recipient endpoint takes this.
    - `name` string: Display name.
    - `is_active` boolean: False means the recipient is skipped by delivery.
    - `call_min_interval_minutes` integer: Minimum interval between voice calls to this person, in minutes. Read-only here — it has its own endpoint and a floor of 5.
    - `created_at` string, date-time
    - `updated_at` string, date-time
    - `delivery` object: Whether an alert addressed to this recipient has anywhere to go. A recipient with no contact — or with contacts only on channels this company has not got working — looks configured, joins groups, sits in escalation chains and receives nothing; this is the field that says so. It answers "is there somewhere to send", not "will we send": a disabled recipient (is_active false) with a working contact is still reported deliverable, because nothing needs fixing beyond re-enabling them.
      - `deliverable` boolean: False means nothing reaches this recipient right now.
      - `reason` 'no_usable_channel' | 'no_contact' | 'no_deliverable_contact', nullable: Why not. `no_usable_channel` — the company has no working delivery channel at all, so EVERY recipient is undeliverable and adding contacts will not help; fix it in the dashboard and check `GET /capabilities`. `no_contact` — the recipient has no contact yet. `no_deliverable_contact` — contacts exist, but all of them sit on channels that are disabled, not included in the plan, not configured, or still waiting for an OAuth binding to complete. Null when deliverable.
      - `message` string, nullable: The same fact in words, in the language of the request. Null when deliverable.
    - `contacts` RecipientContact[]: Present only on single-recipient responses.
      - `uuid` string, uuid
      - `contact_type` string: Slug of the contact type — the same value the write endpoints accept.
      - `value` string: The address itself: a Telegram handle, a phone number in E.164, an email.
      - `is_primary` boolean: The contact preferred when a channel could use more than one of this recipient's addresses. At most one per recipient is primary.
      - `is_active` boolean: Flipped to false automatically after repeated delivery failures.
      - `is_verified` boolean: A bookkeeping flag an operator sets by hand. Nothing in delivery consults it — an unverified contact is used exactly like a verified one, so do not read this as "reachable". `is_active` is the flag that actually gates delivery.
      - `metadata` object, nullable: Free-form data stored with the contact, plus keys we derive ourselves (phone voice zone, the Teams and SIP binding). Returned so a read-modify-write cycle can send it back unchanged.
    - `groups` object[]: Present only on single-recipient responses.
      - `uuid` string, uuid
      - `name` string

## Other responses

- `201`: Recipient created, with any contacts sent alongside it.
- `401`: API key missing, invalid, revoked or expired.
- `402`: The company subscription is inactive.
- `403`: API key lacks the config.write ability.
- `409`: A recipient with this name already exists and already has a contact of a type you sent, with a different address. Nothing was written.
- `422`: Validation failed: a name over 255 chars, an unknown contact type, a type the plan does not include, a missing or malformed contact value, two contacts of the same type, more than one primary, or an internal_sip extension that is missing or already taken. Nothing was written — neither the contacts nor the recipient.
- `429`: The plan's recipient limit is reached.

---

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