Group Membership


Add or remove a contact from a group. Membership is the bridge between a contact and your messaging — a contact must have an active membership in at least one group before any messaging endpoint will accept them as a recipient.

Endpoints

MethodPathPurpose
POST/api/v1/contacts/by-phone/{phoneNumber}/groups/{groupId}Add a contact to a group
DELETE/api/v1/contacts/by-phone/{phoneNumber}/groups/{groupId}Remove from a group

The URL encodes the relationship: phoneNumber identifies the contact (E.164), groupId identifies the group (grp_* prefix from a Groups response).

POST — Add a contact to a group

Adds the contact to the group. The optInMode in the body chooses the enrollment flow:

curl -X POST \
  https://platform.textingline.com/api/v1/contacts/by-phone/+18005551234/groups/grp_DsAdb4EBYBh3z25sBA1q \
  -u "your_key_id:your_key_secret" \
  -H "Content-Type: application/json" \
  -d '{
    "optInMode": "CONFIRM"
  }'

Request body

FieldTypeRequiredDescription
optInModestringNoOne of DIRECT (default), CONFIRM, or SILENT. See Opt-in modes below.

The body may be omitted entirely; the default optInMode: "DIRECT" is used.

Opt-in modes

ModeWhat happens
DIRECTA direct opt-in: you opt the contact in on their behalf, based on consent you already collected (web form, kiosk, paper form). The contact joins the group immediately and receives the group's opt-in message if one is configured.
CONFIRMThe platform asks the contact to confirm: it sends them an opt-in SMS, and they join the group once they reply YES. Use this when you don't have consent on file. See Confirm-mode opt-in flow.
SILENTA quiet opt-in: the contact joins the group immediately and hears nothing — no confirmation request, no opt-in message. Use this for migrations and back-office syncs where the contact was already welcomed elsewhere. Not available on all accounts — requests from accounts without this capability are rejected with 403 (code 33038); contact support to enable it.
📘

Silent means silent to the contact, not to your integration — a SILENT enrollment still fires the contacts.group.opted_in webhook like any other opt-in.

Response

Returns 200 OK with the full contact projection after the change. Inspect groups[] and pendingGroups[] to verify the resulting state.

Idempotency

If the contact is already an active member of the group, the call is a no-op for all modes and returns the current contact state (still 200). The same applies if a CONFIRM-mode enrollment is in flight — re-issuing the POST does not send a second opt-in SMS.

One SILENT-specific behavior: if the contact has a pending confirmation for this group (an unanswered CONFIRM), a SILENT call quietly completes the membership — they become an active member without receiving any message.

Consent gate (cannot be overridden)

Two states block enrollment regardless of optInMode:

StateCodeWhat it means
HARD opt-out33035The contact has explicitly refused messages (texted STOP, opted out at a kiosk, or was flagged via carrier propagation). The operator cannot re-enroll a HARD-opted-out contact — only the contact themselves can re-engage by replying YES to a new opt-in or opting back in via a hosted form.
Pending in other groups33036The contact has pending memberships in groups other than the target group. Resolve those first (wait for the contact to reply YES or DELETE the pending membership) before adding them to a new group.

Both return 409 Conflict with the error code in the body. See Error Codes for the full details.

DELETE — Remove a contact from a group

Removes the contact from the group. Always a soft removal — records MANUAL_REMOVAL / USER_REMOVED on the membership log; the contact and the group both stay intact.

curl -X DELETE \
  https://platform.textingline.com/api/v1/contacts/by-phone/+18005551234/groups/grp_DsAdb4EBYBh3z25sBA1q \
  -u "your_key_id:your_key_secret"

Cannot record a HARD opt-out

DELETE removes the membership but cannot mark the contact as HARD-opted-out. Only the contact themselves can do that (by texting STOP, replying STOP to a message, etc.). If you need to flag a contact as opted out, instruct them to text STOP to your sender number — see Compliance.

Idempotency

If the contact is not a member of the group (whether they never were, or they were already removed), the call is a no-op and returns the current contact state (still 200).

Response

Returns 200 OK with the full contact projection after the change.

Understanding groups[] vs pendingGroups[]

The contact response surfaces both arrays so you can see the full enrollment state at a glance:

{
  "id": "con_BoZHt0bxx5Qg2qN0",
  "phoneNumber": "+18005551234",
  "groups": [
    {
      "id": "grp_DsAdb4EBYBh3z25sBA1q",
      "name": "VIP Customers",
      "optInAt": "2026-04-01T00:00:00.000Z",
      "tags": [...]
    }
  ],
  "pendingGroups": [
    {
      "id": "grp_PqRsT5uVwXyZaB6c7DeF",
      "name": "Spring Promo",
      "pendingSince": "2026-04-15T14:30:00.000Z"
    }
  ]
}
  • groups[] — fully opted-in memberships. The contact can be sent messages addressed to these groups.
  • pendingGroups[]CONFIRM-mode enrollments awaiting reply. The platform has sent the opt-in SMS; until the contact replies YES, they don't receive messages targeted to the group.

A pending membership transitions to active when the contact replies YES. To detect the transition, subscribe to the contacts.group.opted_in webhook — it fires when a contact becomes an active member of a group, including when a double opt-in is confirmed (see Webhooks). If you'd rather not run a webhook endpoint, you can instead poll GET /contacts/by-phone/{phoneNumber} and watch for the group ID to move from pendingGroups[] to groups[]. The matching contacts.group.opted_out event fires when a contact leaves a group (including a STOP reply, which sends one event per group they were in).

Rate limit

EndpointLimitWindow
POST / DELETE /api/v1/contacts/by-phone/{phoneNumber}/groups/{groupId} (write)150 requests60 seconds

These writes share a single contactApiWrite bucket with Contacts POST and PATCH. A burst of contact updates and membership changes counts against one pool. See Rate Limits.

Common errors

CodeHTTPDescription
33003400Invalid phone number.
33034400Invalid grp_* id format.
33038403optInMode: "SILENT" is not available for this account. Contact support to enable it.
33030404Contact not found.
33031404Group not found (or not owned by your account).
33035409Contact is HARD-opted-out. Cannot be re-enrolled by the operator.
33036409Contact has pending memberships in other groups. Resolve those first.

See Error Codes for the full reference.


Did this page help you?