---
title: "Create a WhatsApp group"
method: POST
path: "/v1/whatsapp/groups"
tags: ["whatsapp-groups"]
---

# Create a WhatsApp group

`POST /v1/whatsapp/groups`

## Request

- Base URL: `https://us1.platform.bird.com`
- URL: `https://us1.platform.bird.com/v1/whatsapp/groups`
- Auth: one of:
  - HTTP bearer
  - API key in cookie `bird_session`

Creates a WhatsApp group administered by one of your business numbers. The
number must hold Official Business Account status, which WhatsApp grants a
number whose business is verified and whose display name is approved;
without it the request returns a `412` `WhatsAppGroupsNotEligible`.
`GET /v1/whatsapp/numbers` reports it per number as
`is_official_business_account`, so read it there rather than discovering it
from the refusal. A group
holds 8 people besides your business, and a number can own 10,000 groups;
a number already at that limit returns a `409` `WhatsAppGroupLimitReached`.

The `202` response is the accepted group, with `status` `pending`. WhatsApp
confirms the group moments later, and that is when it issues the invite link
and the group becomes messageable, so the response carries no
`invite_link`. Read the group back until its `status` is `active`. If WhatsApp refuses, the
group ends at `failed` with `last_operation.last_error` set, and creating
another group is the way forward.

Nobody is added at create time, and there is no way to add someone later:
people join by opening the group's invite link. Send the link with the group
invite template, and choose at create time whether opening it joins the
group outright or raises a join request you approve.

## Headers

- `Idempotency-Key` string
- `X-Workspace-Id` string
- `X-Organization-Id` string

## Request body

- WhatsAppGroupCreate: The group to create. WhatsApp issues the invite link, and people join by opening it, so a create request names no participants.
  - `whatsapp_number_id` string, required
  - `subject` string, required: The group's name, shown to participants and to anyone who opens the invite link. Surrounding whitespace is trimmed.
  - `description` string: The group's description, shown alongside the subject.
  - `join_approval_mode` 'auto_approve' | 'approval_required': How someone opening the invite link gets in: - `auto_approve` means they join the moment they open the link. This is the default when the group is created. - `approval_required` means opening the link raises a join request you approve or reject. Fixed when the group is created.

## Response `202`

Group accepted for creation at WhatsApp.

- WhatsAppGroup: A WhatsApp group your business created and administers. People join by opening its invite link, not by being added.
  - `id` string, required
  - `whatsapp_number_id` string, required
  - `waba` string, nullable, required: Meta's identifier for the WhatsApp Business Account recorded when the group was created. This is a historical snapshot, not a live account directory projection. Null for a number we operate on your behalf, whose account is not yours to see.
  - `subject` string, required: The group's name, shown to participants and to anyone who opens the invite link.
  - `description` string, nullable: The group's description, shown alongside the subject. Null when the group has none.
  - `status` 'pending' | 'active' | 'suspended' | 'deleted' | 'failed', required: Where the group stands. Values, in lifecycle order: - `pending` means the request to create the group has been accepted and WhatsApp has not confirmed it yet. The group has no invite link, and it cannot be messaged or changed. - `active` means the group exists at WhatsApp and carries an invite link. Only an active group can be messaged. - `suspended` means WhatsApp has stopped activity in the group, which it does when a group breaks its policies. Sends fail while it lasts, and WhatsApp can lift it on its own. - `deleted` means the group is gone, either because you deleted it or because WhatsApp removed it. Every participant lost access, and the state is terminal. - `failed` means WhatsApp refused to create the group; `last_operation.last_error` says why. The state is terminal, so create another group rather than retrying this one.
  - `join_approval_mode` 'auto_approve' | 'approval_required', required: How someone opening the invite link gets in: - `auto_approve` means they join the moment they open the link. This is the default when the group is created. - `approval_required` means opening the link raises a join request you approve or reject. Fixed when the group is created.
  - `invite_link` string, nullable: The link that lets someone join the group, which is the only way in. A group has one link at a time. Null while the group is `pending`, since WhatsApp issues the link when it confirms the group. Rotating it through `POST /v1/whatsapp/groups/{group_id}/invite-link/rotate` replaces it, and every link the group had before then stops working.
  - `participants` WhatsAppGroupParticipant[]: Who is in the group, as of the last update WhatsApp sent, and the whole set rather than a page: WhatsApp holds a group to a handful of people, so there is never a page's worth to return. The business number that created the group is its admin and is not listed.
    - `bsuid` string, required: Business-scoped user ID, Meta's identifier for this person against your business. The one identifier every participant has: WhatsApp always sends it, and it is stable for as long as they are in the group.
    - `phone_number` string: Phone number in E.164 format. Absent when WhatsApp withholds it, which it does for anyone who has not shared their number with your business, so a group is normally a mix of participants with one and without.
    - `username` string: The WhatsApp username this person chose. Absent when they have none, and not an identifier to address them by: it is theirs to change, so it names them in a list rather than keying anything.
    - `last_operation` WhatsAppGroupOperation: The last change asked of this group or participant, and where it got to. WhatsApp confirms a change on a webhook rather than in its reply, so an operation is `pending` until that arrives. While it is, another change to the same thing is refused with a `409` `WhatsAppGroupUpdateInProgress`; a change to a different participant is not, so several removals can be in flight at once. Absent on something nothing has been asked of yet.
      - `type` 'create' | 'settings_update' | 'delete' | 'remove', required: What was asked of the thing carrying the operation: - `create` and `delete` act on the group itself. - `settings_update` changes the group's subject, description or picture. - `remove` takes one participant out of the group.
      - `status` 'pending' | 'success' | 'failed', required: How the operation ended: - `pending` means WhatsApp accepted the request and has not reported back. Another operation on the same thing is refused while this lasts. - `success` means WhatsApp applied everything asked of it. A change whose success removes its own carrier is never seen in this state: a removed participant and an unpinned message leave their lists, and the entry going away is the confirmation. - `failed` means WhatsApp applied none of it, or only part: `last_error` says why, and on a `settings_update` the per-field `results` say which fields did apply.
      - `requested_at` string, date-time, required: When Bird accepted the request.
      - `settled_at` string, date-time, nullable: When WhatsApp reported the outcome. Null while `pending`.
      - `results` WhatsAppGroupOperationResult[]: Per-field outcomes, on a `settings_update` that has settled. One entry per field the update carried, so a client can put a refusal next to the input it came from. Absent on every other operation type, which change one thing and report it on `status`.
        - `field` 'subject' | 'description' | 'profile_picture_url', required: Which of the group's settings a `settings_update` result reports on.
        - `applied` boolean, required: Whether WhatsApp applied this field. False when it refused this one, whatever it did with the others.
        - `error` WhatsAppGroupError: Why a change to a group did not take effect. Meta documents no code vocabulary for a group refusal, since every sample payload carries an undocumented `code` beside its message, so this relays what it said rather than classifying it, the way a template submission failure does.
          - `description` string, required: WhatsApp's own explanation of the refusal, passed through. Show it to the person who asked for the change; never match on its text. Carries Bird's own words instead when the failure was Bird's verdict, such as a confirmation that never arrived.
          - `meta_error_code` string, nullable: WhatsApp's most specific code for the refusal: its error subcode where it sent one, otherwise its top-level code. Treat it as an opaque string. Null when the failure was Bird's own verdict rather than a WhatsApp refusal.
      - `last_error` WhatsAppGroupError: Why a change to a group did not take effect. Meta documents no code vocabulary for a group refusal, since every sample payload carries an undocumented `code` beside its message, so this relays what it said rather than classifying it, the way a template submission failure does.
        - `description` string, required: WhatsApp's own explanation of the refusal, passed through. Show it to the person who asked for the change; never match on its text. Carries Bird's own words instead when the failure was Bird's verdict, such as a confirmation that never arrived.
        - `meta_error_code` string, nullable: WhatsApp's most specific code for the refusal: its error subcode where it sent one, otherwise its top-level code. Treat it as an opaque string. Null when the failure was Bird's own verdict rather than a WhatsApp refusal.
  - `participant_count` integer, required: How many people are in the group, excluding your business.
  - `pinned_messages` WhatsAppGroupPinnedMessage[]: The group's pins, newest first. WhatsApp holds a few at once, and pinning past that unpins the oldest rather than refusing. No entry here is merely requested. An entry stays listed until it is unpinned, so one whose `pinned_until` has passed is still listed after WhatsApp has taken it off the chat.
    - `message_id` string, required
    - `pinned_until` string, date-time, required: When the pin is due to lapse, projected from the `duration_days` the pin was asked for. An entry stays listed until it is unpinned, so a time in the past means WhatsApp has already taken the message off the chat.
  - `profile_picture_url` string, nullable: Address of the group's picture, as WhatsApp serves it. Null when the group has none.
  - `last_operation` WhatsAppGroupOperation: The last change asked of this group or participant, and where it got to. WhatsApp confirms a change on a webhook rather than in its reply, so an operation is `pending` until that arrives. While it is, another change to the same thing is refused with a `409` `WhatsAppGroupUpdateInProgress`; a change to a different participant is not, so several removals can be in flight at once. Absent on something nothing has been asked of yet.
    - `type` 'create' | 'settings_update' | 'delete' | 'remove', required: What was asked of the thing carrying the operation: - `create` and `delete` act on the group itself. - `settings_update` changes the group's subject, description or picture. - `remove` takes one participant out of the group.
    - `status` 'pending' | 'success' | 'failed', required: How the operation ended: - `pending` means WhatsApp accepted the request and has not reported back. Another operation on the same thing is refused while this lasts. - `success` means WhatsApp applied everything asked of it. A change whose success removes its own carrier is never seen in this state: a removed participant and an unpinned message leave their lists, and the entry going away is the confirmation. - `failed` means WhatsApp applied none of it, or only part: `last_error` says why, and on a `settings_update` the per-field `results` say which fields did apply.
    - `requested_at` string, date-time, required: When Bird accepted the request.
    - `settled_at` string, date-time, nullable: When WhatsApp reported the outcome. Null while `pending`.
    - `results` WhatsAppGroupOperationResult[]: Per-field outcomes, on a `settings_update` that has settled. One entry per field the update carried, so a client can put a refusal next to the input it came from. Absent on every other operation type, which change one thing and report it on `status`.
      - `field` 'subject' | 'description' | 'profile_picture_url', required: Which of the group's settings a `settings_update` result reports on.
      - `applied` boolean, required: Whether WhatsApp applied this field. False when it refused this one, whatever it did with the others.
      - `error` WhatsAppGroupError: Why a change to a group did not take effect. Meta documents no code vocabulary for a group refusal, since every sample payload carries an undocumented `code` beside its message, so this relays what it said rather than classifying it, the way a template submission failure does.
        - `description` string, required: WhatsApp's own explanation of the refusal, passed through. Show it to the person who asked for the change; never match on its text. Carries Bird's own words instead when the failure was Bird's verdict, such as a confirmation that never arrived.
        - `meta_error_code` string, nullable: WhatsApp's most specific code for the refusal: its error subcode where it sent one, otherwise its top-level code. Treat it as an opaque string. Null when the failure was Bird's own verdict rather than a WhatsApp refusal.
    - `last_error` WhatsAppGroupError: Why a change to a group did not take effect. Meta documents no code vocabulary for a group refusal, since every sample payload carries an undocumented `code` beside its message, so this relays what it said rather than classifying it, the way a template submission failure does.
      - `description` string, required: WhatsApp's own explanation of the refusal, passed through. Show it to the person who asked for the change; never match on its text. Carries Bird's own words instead when the failure was Bird's verdict, such as a confirmation that never arrived.
      - `meta_error_code` string, nullable: WhatsApp's most specific code for the refusal: its error subcode where it sent one, otherwise its top-level code. Treat it as an opaque string. Null when the failure was Bird's own verdict rather than a WhatsApp refusal.
  - `suspended_at` string, date-time: When WhatsApp suspended the group. Present only while the group is `suspended`, and gone once WhatsApp lifts the suspension.
  - `created_at` string, date-time, required
  - `updated_at` string, date-time, required

## Other responses

- `400`: Bad request
- `401`: Authentication required
- `403`: Insufficient permissions
- `404`: Resource not found
- `409`: Resource conflict
- `412`: Precondition failed
- `422`: The request has invalid field values, violates a business rule, or carries a query parameter the endpoint does not declare. Field validation errors use `type: validation_error` and include the affected fields in `details`. Business-rule errors identify the failed rule in `type`.
- `429`: Rate limit exceeded
- `500`: Internal server error
- `503`: The service is temporarily unavailable. If `Retry-After` is present, wait for that delay before retrying; otherwise, retry with exponential backoff. Reuse the same idempotency key and request when retrying a mutation.

---

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