---
title: "Add a join keyword"
description: "A text that is exactly this word (any case) gets the double opt-in request (\"Reply YES to get … texts\"). `POST /api/v1/sms/numbers/{id}/join-keywords`"
url: https://www.unitpost.com/docs/sms/post-sms-numbers-id-join-keywords
section: "SMS API reference"
updated: 2026-11-11
---
# Add a join keyword

`POST /api/v1/sms/numbers/{id}/join-keywords`

A text that is exactly this word (any case) gets the double opt-in request ("Reply YES to get … texts"). The YES records marketing consent, makes the texter a contact if they aren't one, and adds them to `segment_id` (a static segment) and/or `topic_id` (an SMS topic). A keyword is never consent by itself and never lifts a STOP: an opted-out texter is told to text START. STOP, HELP, START, YES and their variants are refused; at most 10 per number; only numbers people can text back. Adding the same keyword again answers it (`200`), so a retry is safe. Requires the `sms:manage` capability.

## Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | — |

## Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `keyword` | string | yes | One word of 2–20 letters or digits, such as JOIN. STOP, HELP, START, YES and their variants are refused. |
| `segment_id` | string | no | A static segment (`seg_…`) to add confirmed texters to. |
| `topic_id` | string | no | An SMS topic to subscribe confirmed texters to. |

## Response fields (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `object` | string | yes | — |
| `id` | string | yes | `sjk_…`. |
| `number_id` | string | yes | The number (`snum_…`) people text the keyword to. |
| `keyword` | string | yes | As you wrote it. A text matches it whatever the case and surrounding punctuation. |
| `segment_id` | string \| null | yes | The static segment (`seg_…`) a confirmed texter joins, or null. |
| `topic_id` | string \| null | yes | The SMS topic a confirmed texter is subscribed to, or null. |
| `created_at` | string | yes | — |
| `registration_warning` | string \| null | yes | Set when the number's carrier registration doesn't list texting a keyword as a way to opt in: carriers can flag sign-ups its registration didn't declare, so add keyword opt-in to its use case and resubmit. Null otherwise. |

## Responses

| Status | Description |
| --- | --- |
| 200 | The keyword already existed with these settings. |
| 201 | Added. |
| 400 | The body isn't JSON. |
| 401 | Missing or invalid API key. |
| 403 | Insufficient scope. |
| 404 | Number, segment or topic not found, or SMS is not enabled. |
| 409 | Not a number people can text back, released, 10 keywords already, or the keyword exists with other settings. |
| 422 | A word that's taken or malformed, a dynamic segment, an email topic, or an unknown key. |

## Example request

```bash
curl -X POST 'https://www.unitpost.com/api/v1/sms/numbers/sms_123/join-keywords' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'User-Agent: my-app/1.0' \
  -H 'Content-Type: application/json' \
  -d '{ }'
```

## Example response (200)

```json
{
  "object": "sms_join_keyword",
  "id": "id_123",
  "number_id": "number_123",
  "keyword": "string",
  "segment_id": "segment_123",
  "topic_id": "topic_123",
  "created_at": "2026-01-01T00:00:00.000Z",
  "registration_warning": "string"
}
```

## Links

- HTML reference (with Node.js, Python, Ruby, PHP, Laravel, Go, Java, Rust, .NET and CLI examples): https://www.unitpost.com/docs/sms/post-sms-numbers-id-join-keywords
- OpenAPI 3.1 (full schemas): https://www.unitpost.com/api/v1/openapi.json
