---
title: "Request SMS opt-in confirmation"
description: "Text the contact \"Reply YES to get our marketing texts\" (double opt-in). `POST /api/v1/contacts/{id}/sms-opt-in-request`"
url: https://www.unitpost.com/docs/sms/post-contacts-id-sms-opt-in-request
section: "SMS API reference"
updated: 2026-10-13
---
# Request SMS opt-in confirmation

`POST /api/v1/contacts/{id}/sms-opt-in-request`

Text the contact "Reply YES to get our marketing texts" (double opt-in). A YES within 72 hours records a verified marketing opt-in; until then their existing consent stands. It only upgrades consent already on record: a number with none, one that opted out, or one already waiting for a reply is a `409` and nothing is sent. It sends a real text from a number they can reply to and charges your wallet, so pass an `Idempotency-Key`: a retry with the same key returns the same text instead of sending another. `phone` picks which of the contact's numbers (E.164); default its primary one. Any other body key is a `422`. Requires the `sms:send` capability.

## Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | — |
| `Idempotency-Key` | header | string | no | Any unique string (1–256 characters, e.g. a UUID). Retrying with the same key and the same body within 24 hours returns the first result instead of sending again; the same key with a different body is `409 invalid_idempotent_request`, and one still in flight is `409 concurrent_idempotent_requests`. |

## Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `phone` | string | no | Which of the contact's numbers to text (E.164). Default: its primary number. |

## Response fields (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `object` | string | yes | — |
| `id` | string | yes | — |
| `to` | string | yes | — |
| `from` | string \| null | yes | — |
| `direction` | enum | yes | One of: `outbound`, `inbound`. |
| `status` | enum | yes | One of: `queued`, `scheduled`, `sending`, `sent`, `delivered`, `failed`, `suppressed`, `canceled`, `received`. |
| `type` | enum | yes | One of: `transactional`, `marketing`. |
| `body` | string \| null | yes | — |
| `segments` | integer | yes | Billed units. Outbound: segments. Inbound: 1 when the received text was charged (1¢ in the US and Canada), else 0. |
| `in_reply_to` | string \| null | yes | Inbound only: your text this reply answers (`sms_…`), when the carrier says which. Null otherwise. |
| `received_at` | string \| null | yes | Inbound only: when the reply reached Unitpost. Order a conversation by this; carriers don't guarantee order. Null on outbound. |
| `looks_like_opt_out` | boolean | yes | Inbound only: the text reads like a request to stop texting ("please stop texting me") but was not an opt-out keyword, so no opt-out was recorded. Decide whether to record one (POST /v1/contacts/{id}/sms-consent, action opt_out). False for an exact keyword such as STOP (already recorded) and on outbound. |
| `country` | string \| null | yes | — |
| `scheduled_at` | string \| null | yes | — |
| `last_error` | string \| null | yes | Legacy free-text error. Prefer `error.code`. |
| `error` | object \| null | yes | Set on `suppressed` messages: the stable skip reason and its sentence. Null otherwise. |
| `created_at` | string | yes | — |

## Responses

| Status | Description |
| --- | --- |
| 200 | The confirmation text. |
| 400 | The body isn't JSON, or the `Idempotency-Key` header is not 1–256 characters. |
| 401 | Missing or invalid API key. |
| 403 | Insufficient scope, or SMS isn't on your plan. |
| 404 | Contact or number not found, or SMS isn't available on this workspace. |
| 409 | No consent on record, opted out, already waiting for a reply, no sender they can reply to, or an idempotency conflict. |
| 422 | A `phone` that isn't a valid number, or an unknown key. |

## Example request

```bash
curl -X POST 'https://www.unitpost.com/api/v1/contacts/con_123/sms-opt-in-request' \
  -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",
  "id": "id_123",
  "to": "customer@example.com",
  "from": "you@yourdomain.com",
  "direction": "outbound",
  "status": "queued",
  "type": "transactional",
  "body": "string",
  "segments": 0,
  "in_reply_to": "string",
  "received_at": "2026-01-01T00:00:00.000Z",
  "looks_like_opt_out": false,
  "country": "string",
  "scheduled_at": "2026-01-01T00:00:00.000Z",
  "last_error": "string",
  "error": {
    "code": "out_of_reach",
    "message": "string"
  },
  "created_at": "2026-01-01T00:00:00.000Z"
}
```

## Links

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