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

# Group chats

> Start group chats, send follow-ups, and manage a group's participants, name and membership.

A group chat is an ordinary chat with `type: "group"`. You start one by sending a first message to two or more recipients, then send follow-ups, read its participants, add or remove people, rename it and take one of your numbers out of it with the endpoints on this page.

Group chats need a Dedicated phone number. On the Free Plan, every group operation returns `400` [`FEATURE_NOT_ENABLED`](/errors/feature-not-enabled). An AI Agent number can't start a group or send to one, including replies in a group a contact added it to (`403` [`CONTACT_MUST_TEXT_FIRST`](/errors/contact-must-text-first)); it still receives that group's messages. See [Plans](/plans.md).

## Start a group chat

Send `POST /v1/chats` with `from` and two or more phone numbers in `to`. `from` is required for a group. Blujay creates the group on that phone number and queues the first message.

```bash curl theme={null}
curl https://api.blujay.io/v1/chats \
  -H "Authorization: Bearer blu_your_api_key" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: kickoff-group-001" \
  -d '{
    "from": "+15555550100",
    "to": ["+15555550123", "+15555550124"],
    "message": {
      "parts": [
        { "type": "text", "text": "Hi both, starting a thread so we can plan Friday together." }
      ]
    }
  }'
```

```json Response (202) theme={null}
{
  "status": "success",
  "data": {
    "chat_id": "chat_grp789",
    "message": {
      "id": "msg_abc123",
      "direction": "outbound",
      "delivery_status": "queued",
      "preferred_service": null,
      "service": null,
      "from": "+15555550100",
      "to": ["+15555550123", "+15555550124"],
      "created_at": "2026-10-14T17:00:00.000Z",
      "parts": [
        { "type": "text", "text": "Hi both, starting a thread so we can plan Friday together." }
      ]
    }
  },
  "trace_id": "trc_8f2a1c0e4b7d",
  "timestamp": "2026-10-14T17:00:00.120Z"
}
```

Keep these rules in mind for a group's first message:

* `to` takes 2 to 31 US phone numbers. Email addresses aren't accepted.
* Group chats don't accept `preferred_service` (`400` [`VALIDATION_ERROR`](/errors/validation-error)). Blujay picks the service for the whole group: iMessage when it can confirm every recipient on iMessage, otherwise SMS/MMS. The chat's `service` reports which one (`iMessage` or `SMS`), or null until Blujay knows.
* If your number already has a group with exactly these recipients, Blujay sends into that group instead of creating a new one.
* The same first-message rules apply as for direct chats: no links, media or contact cards, 300 characters, no effects and no `reply_to`. They apply to every `POST /v1/chats` request, even into an existing group. See [Sending rules](/messaging/sending-rules.md).

Send an `Idempotency-Key` so you can retry safely; see [Idempotency](/idempotency.md).

## Send follow-ups

Send the rest of the conversation to `POST /v1/chats/{chat_id}/messages`, exactly as you would in a direct chat.

```bash curl theme={null}
curl https://api.blujay.io/v1/chats/chat_grp789/messages \
  -H "Authorization: Bearer blu_your_api_key" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: kickoff-group-002" \
  -d '{
    "message": {
      "parts": [{ "type": "text", "text": "Agenda is attached in the next message." }]
    }
  }'
```

Text, link, media and contact card parts all work in groups once someone in the group has replied; until then the pre-reply rules apply to the group as a whole, and anyone's reply counts. A follow-up is rejected with `403` [`RECIPIENT_SUPPRESSED`](/errors/recipient-suppressed) if anyone currently in the group has opted out. See [Opt-outs](/opt-outs.md).

## Know which features need an established iMessage group

Two terms matter on this page:

* An **active** group is one Blujay still treats as current. Blujay checks this before every group operation. There is no `status` field on the chat today; for a group, `phone_numbers[].can_send` and `sendability_reasons` on `GET /v1/chats/{chat_id}` are the closest check.
* An **established iMessage group** is an active group that iMessage has created on your phone number and linked to this chat. A group you start with `POST /v1/chats` becomes established once its first message has gone out over iMessage and iMessage reports the group back to Blujay. A group carried by SMS/MMS never becomes one. While the link is missing, `sendability_reasons` contains `group_not_ready`.

| Feature | Works in | Otherwise |
| - | - | - |
| Text, link, media and contact card follow-ups | Any active group | |
| `GET /v1/chats/{chat_id}/participants` | Any group | |
| Threaded replies (`reply_to`) | Established iMessage groups | `400` [`UNSUPPORTED_CHAT_OPERATION`](/errors/unsupported-chat-operation) |
| Screen and bubble effects (`effect`) | Established iMessage groups | `400` [`FEATURE_NOT_ENABLED`](/errors/feature-not-enabled) |
| Text styles (`text_decorations`) | Direct chats only | `400` [`FEATURE_NOT_ENABLED`](/errors/feature-not-enabled) |
| Typing indicators | Established iMessage groups | Accepted with `204`, nothing shown |
| Add or remove participants, rename, leave | Established iMessage groups | `400` [`UNSUPPORTED_CHAT_OPERATION`](/errors/unsupported-chat-operation) |

## Read a group and its participants

`GET /v1/chats/{chat_id}` returns the group. Your own Blujay numbers in the group are listed in `phone_numbers`, and everyone else is in `participants`. Each entry in `phone_numbers` has `can_send`, which tells you whether that number can send to the group right now.

```bash curl theme={null}
curl https://api.blujay.io/v1/chats/chat_grp789 \
  -H "Authorization: Bearer blu_your_api_key"
```

```json Response (200) theme={null}
{
  "status": "success",
  "data": {
    "chat_id": "chat_grp789",
    "type": "group",
    "display_name": "Friday planning",
    "participants": [
      {
        "type": "phone",
        "handle": "+15555550123",
        "address": "+15555550123",
        "status": "active",
        "joined_at": "2026-10-14T17:00:00.000Z",
        "left_at": null,
        "contact_id": "contact_abc123",
        "display_name": "Ava Chen"
      },
      {
        "type": "phone",
        "handle": "+15555550124",
        "address": "+15555550124",
        "status": "active",
        "joined_at": "2026-10-14T17:00:00.000Z",
        "left_at": null,
        "contact_id": "contact_def456",
        "display_name": "Jordan Lee"
      }
    ],
    "supports_typing_indicators": true,
    "phone_numbers": [
      {
        "phone_number_id": "phone_abc123",
        "display_number": "+15555550100",
        "label": "Support",
        "status": "active",
        "is_default": true,
        "can_send": true,
        "sendability_reasons": []
      }
    ],
    "created_at": "2026-10-14T17:00:00.000Z",
    "updated_at": "2026-10-14T17:05:00.000Z"
  },
  "trace_id": "trc_1b7e9c3d5a2f",
  "timestamp": "2026-10-14T17:05:01.000Z"
}
```

When `can_send` is `false`, `sendability_reasons` says why: `number_inactive`, `number_setup_incomplete` or `group_not_ready`.

The chat lists only people who are still in the group. To include people who left, call `GET /v1/chats/{chat_id}/participants`. It returns every participant with `status` set to `active` or `left`, plus `joined_at` and `left_at`. `contact_id` appears only when the participant matches a contact in your workspace.

```bash curl theme={null}
curl https://api.blujay.io/v1/chats/chat_grp789/participants \
  -H "Authorization: Bearer blu_your_api_key"
```

## Add or remove a participant

Add a phone number or email address with `POST /v1/chats/{chat_id}/participants`, and remove one with `DELETE` on the same path. One of your Blujay numbers in the group makes the change, and everyone in the group sees it. Both endpoints honor `Idempotency-Key`. The `202` means the phone accepted the change; a moment later Blujay records it the same way as a change made on the phone, with a `participant.added` or `participant.removed` webhook and an entry in the chat.

```bash curl theme={null}
curl https://api.blujay.io/v1/chats/chat_grp789/participants \
  -H "Authorization: Bearer blu_your_api_key" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: add-participant-001" \
  -d '{ "handle": "+15555550125" }'
```

```json Response (202) theme={null}
{
  "status": "success",
  "data": {
    "chat_id": "chat_grp789",
    "operation": "participant_add",
    "status": "accepted",
    "participant": {
      "handle": "+15555550125",
      "type": "phone",
      "status": "active"
    },
    "from_phone_number_id": "phone_abc123"
  },
  "trace_id": "trc_4d2a8e6f0c1b",
  "timestamp": "2026-10-14T17:10:00.000Z"
}
```

To remove someone, send the same body with `DELETE`:

```bash curl theme={null}
curl -X DELETE https://api.blujay.io/v1/chats/chat_grp789/participants \
  -H "Authorization: Bearer blu_your_api_key" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: remove-participant-001" \
  -d '{ "handle": "+15555550125" }'
```

What to expect:

* Adding someone who is already in the group returns `200` with `status: "already_active"`. Removing someone who already left returns `200` with `status: "already_removed"`. Neither contacts the phone.
* Removing someone who was never in the group returns `404` [`NOT_FOUND`](/errors/not-found) with the message "This handle is not a participant of the group". Read `GET /v1/chats/{chat_id}/participants` to see who is in it.
* If more than one of your numbers is in the group, set `from_phone_number_id` to choose which one makes the change. Without it you get `400` [`PHONE_NUMBER_REQUIRED`](/errors/phone-number-required).
* A removal that would leave the group with fewer than three members returns `409` [`GROUP_MINIMUM_PARTICIPANTS`](/errors/group-minimum-participants), the same code as leaving in that situation.
* Your own Blujay numbers can't be added or removed here (`400` [`VALIDATION_ERROR`](/errors/validation-error)). To take one of your numbers out of a group, [leave the group](#leave-a-group).
* If your number isn't ready to manage the group right now, you get `503` [`PHONE_NUMBER_UNAVAILABLE`](/errors/phone-number-unavailable). Retry later with the same `Idempotency-Key`.

### Confirming the change

`202` means Blujay accepted the request and handed it to your phone number. The change is confirmed when the `participant.added` or `participant.removed` webhook arrives for that `chat_id` and handle, or when `GET /v1/chats/{chat_id}/participants` shows the new state. If iMessage refuses the change, no failure event is sent today: the participant list stays as it was, and you learn that only by reading it back. Give the change a minute, then read the participants before you retry.

## Rename a group

Set a new group name with `PATCH /v1/chats/{chat_id}` and `display_name` (1 to 100 characters). Everyone in the group sees the new name. You can't clear a group name. Rename ignores `Idempotency-Key`; repeating a rename with the same name is harmless. The `202` means the phone accepted the rename; a moment later Blujay records it the same way as a rename made on the phone, with a `chat.group_name_updated` webhook and a "named the conversation" entry in the chat.

```bash curl theme={null}
curl -X PATCH https://api.blujay.io/v1/chats/chat_grp789 \
  -H "Authorization: Bearer blu_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{ "display_name": "Friday planning" }'
```

```json Response (202) theme={null}
{
  "status": "success",
  "data": {
    "chat_id": "chat_grp789",
    "operation": "group_name_update",
    "status": "accepted",
    "display_name": "Friday planning"
  },
  "trace_id": "trc_9c0d2e4f6a8b",
  "timestamp": "2026-10-14T17:12:00.000Z"
}
```

### Confirming the change

The rename is confirmed by the `chat.group_name_updated` webhook, or by `display_name` on `GET /v1/chats/{chat_id}`. If iMessage refuses the rename, no failure event is sent today; `display_name` keeps the old value. A group icon can't be set through the API: `group_chat_icon` on this endpoint returns `400` [`FEATURE_NOT_ENABLED`](/errors/feature-not-enabled).

## Leave a group

`POST /v1/chats/{chat_id}/leave` takes one of your Blujay numbers out of an established iMessage group. A group must always keep at least one of your numbers, so you can only leave a group that has two or more of your numbers in it. `from_phone_number_id` names the number that should leave. It's optional when only one of your numbers is in the group (then the body can be `{}`), and required otherwise.

```bash curl theme={null}
curl https://api.blujay.io/v1/chats/chat_grp789/leave \
  -H "Authorization: Bearer blu_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{ "from_phone_number_id": "phone_def456" }'
```

```json Response (202) theme={null}
{
  "status": "success",
  "data": {
    "chat_id": "chat_grp789",
    "operation": "chat_leave",
    "status": "accepted",
    "from_phone_number_id": "phone_def456"
  },
  "trace_id": "trc_2e4f6a8b0c1d",
  "timestamp": "2026-10-14T17:15:00.000Z"
}
```

| Error | Status | What to do |
| - | - | - |
| [`PHONE_NUMBER_REQUIRED`](/errors/phone-number-required) | 400 | More than one of your numbers is in the group. Set `from_phone_number_id`. |
| [`LAST_PHONE_NUMBER_IN_GROUP`](/errors/last-phone-number-in-group) | 409 | This is your only number in the group, so it can't leave. |
| [`GROUP_MINIMUM_PARTICIPANTS`](/errors/group-minimum-participants) | 409 | Leaving, or removing a participant, would leave the group with fewer than three members. |
| [`PHONE_NUMBER_HAS_SCHEDULED_MESSAGES`](/errors/phone-number-has-scheduled-messages) | 409 | The number has scheduled texts for this group. Wait for them to send or remove them in the app, then try again. |
| [`PHONE_NUMBER_HAS_MESSAGES_SENDING`](/errors/phone-number-has-messages-sending) | 409 | The number still has messages sending in this group. Retry once they finish. |
| [`UNSUPPORTED_CHAT_OPERATION`](/errors/unsupported-chat-operation) | 400 | The chat isn't an established iMessage group. |

### Confirming the change

Leaving ignores `Idempotency-Key` and has no webhook event. Confirm it by reading the chat: the number disappears from `phone_numbers` on `GET /v1/chats/{chat_id}`. If a leave request times out, read `phone_numbers` before you retry. If iMessage refuses the leave, no failure event is sent; the number stays in `phone_numbers`.

## Receive group messages and changes

When someone replies in a group, the `message.received` webhook identifies them with a `sender` object instead of the top-level `contact_id` that direct replies carry. `sender` has `type` (`phone` or `email`) and `address`, plus `contact_id` when the sender matches a contact in your workspace. Reading the same message with `GET /v1/messages/{message_id}` returns the same `sender` object.

```json message.received theme={null}
{
  "api_version": "v1",
  "webhook_version": "2026-10-03",
  "event_type": "message.received",
  "event_id": "evt_abc123",
  "created_at": "2026-10-14T17:20:00.100Z",
  "trace_id": "trc_5a7b9c1d3e5f",
  "workspace_id": "workspace_abc123",
  "subscription_id": "whsub_abc123",
  "data": {
    "message_id": "msg_def456",
    "chat_id": "chat_grp789",
    "phone_number_id": "phone_abc123",
    "sender": {
      "type": "phone",
      "address": "+15555550123",
      "contact_id": "contact_abc123"
    },
    "text": "Friday at 10 works for me.",
    "status": "received",
    "direction": "inbound",
    "requested_service": null,
    "service": "imessage",
    "sent_at": null,
    "delivered_at": null,
    "read_at": null,
    "failed_at": null,
    "failure": null,
    "degradations": [],
    "parts": [{ "type": "text", "text": "Friday at 10 works for me." }],
    "reply_to": null
  }
}
```

To follow membership and name changes, including ones made outside the API, subscribe to `participant.added`, `participant.removed` and `chat.group_name_updated`. Deliveries can arrive out of order, so treat `GET /v1/chats/{chat_id}` and `GET /v1/chats/{chat_id}/participants` as the current state. See [Webhook events](/webhooks/events.md) and [Receive messages](/messaging/receive-messages.md).

> [!WARNING]
>
> **Common mistakes**
>
> * `from` is required for a group and is a string. Leaving it out returns `400` [`VALIDATION_ERROR`](/errors/validation-error).
> * A second `POST /v1/chats` into an existing group is still a first message: no links or media, even after replies. Use `POST /v1/chats/{chat_id}/messages`.
> * `202` on add, remove, rename or leave means accepted, not done. Confirm with the webhook or a read.
> * A group chat has no `preferred_service`; its `service` reports what the group runs on. Don't wait for `delivered` on group messages; see [Track a message](/messaging/lifecycle.md).
> * Retrying a failed group message returns `409` [`RETRY_NOT_ALLOWED`](/errors/retry-not-allowed).

## Limitations

* Dedicated numbers only. Free Plan: `400` [`FEATURE_NOT_ENABLED`](/errors/feature-not-enabled). AI Agent: `403` [`CONTACT_MUST_TEXT_FIRST`](/errors/contact-must-text-first) on create and send. See [Plans](/plans.md).
* 2 to 31 recipients on create; phone numbers only (`400` [`VALIDATION_ERROR`](/errors/validation-error)).
* No `preferred_service`; Blujay picks for the whole group and the chat doesn't report the service.
* Pre-reply rules apply to the group until anyone in it replies. See [Sending rules](/messaging/sending-rules.md).
* Participant changes, rename, leave, threaded replies, effects and typing need an established iMessage group (`400` [`UNSUPPORTED_CHAT_OPERATION`](/errors/unsupported-chat-operation) or `400` [`FEATURE_NOT_ENABLED`](/errors/feature-not-enabled)). Text styles never work in groups.
* Group icon isn't supported (`400` [`FEATURE_NOT_ENABLED`](/errors/feature-not-enabled)).
* A group must keep at least three members (`409` [`GROUP_MINIMUM_PARTICIPANTS`](/errors/group-minimum-participants) on remove and on leave) and at least one of your numbers (`409` [`LAST_PHONE_NUMBER_IN_GROUP`](/errors/last-phone-number-in-group)).
* Your own numbers can't be added through the participants endpoint. There's no API to add a second Blujay number to a group.
* A failed add, remove, rename or leave has no failure event today.
* Group messages can't be retried (`409` [`RETRY_NOT_ALLOWED`](/errors/retry-not-allowed)), and `delivered`/`read` may never be reported for them.

## Next steps

> **[Threaded replies](/messaging/replies.md)**
>
> Reply to a specific message in a direct chat or an established iMessage group.

> **[Effects and text styles](/messaging/effects.md)**
>
> Add screen and bubble effects to group follow-ups.

> **[Webhook events](/webhooks/events.md)**
>
> See every event, including group participant and name changes.

> **[Idempotency](/idempotency.md)**
>
> Retry group sends and participant changes without doing them twice.


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