Inkbox

> # Documentation index
> Fetch the complete documentation index at: https://inkbox.ai/sitemap.xml
> Use this file to discover all available pages before exploring further.

---

# SMS Opt-Ins
description: Read SMS consent state and (for orgs on their own active, customer-managed 10DLC campaign) opt recipients in or out programmatically

---


# SMS Opt-Ins

Per-recipient SMS consent state for your organization. Consent is keyed by **(your org, recipient phone number)** — a single recipient can have a different consent state across organizations, but shares one consent state across all phone numbers within your org.

Inkbox automatically updates this registry from inbound `STOP` / `START` keywords. These endpoints let you:

- **Read** the current consent state for one recipient, or list the rows for your org.
- **Write** consent state directly — but only when your org is on its own active, customer-managed [10DLC campaign](/docs/capabilities/phone/10dlc). Orgs on the Inkbox-default campaign share consent state across the campaign pool and can't override it programmatically.

**Auth.** All endpoints require an [admin API key](/docs/api-keys), or you can manage opt-in state from the [Inkbox Console](https://inkbox.ai/console). Writes additionally require your org to be on its own active, customer-managed 10DLC campaign — see [Write access](#write-access) below.

**Available on all three distribution paths:** the examples below show direct HTTP, the [Python SDK](https://pypi.org/project/inkbox/) (`inkbox.sms_opt_ins`), the [TypeScript SDK](https://www.npmjs.com/package/@inkbox/sdk) (`inkbox.smsOptIns`), and the [CLI](https://www.npmjs.com/package/@inkbox/cli) (`inkbox sms-opt-in`).

---

## The opt-in object

```json
{
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "organization_id": "org_2abc123",
    "receiver_number": "+15551234567",
    "status": "opted_in",
    "source": "api",
    "opted_in_at": "2026-05-15T12:00:00Z",
    "opted_out_at": null,
    "created_at": "2026-05-15T12:00:00Z",
    "updated_at": "2026-05-15T12:00:00Z"
}
```

| Field | Type | Description |
| :--- | :--- | :--- |
| `id` | UUID | Unique identifier of the consent row |
| `organization_id` | string | Your organization ID |
| `receiver_number` | string | Recipient phone number in E.164 (for example `+15551234567`) |
| `status` | string | `"opted_in"` or `"opted_out"` |
| `source` | string | How this transition was captured — see [Source values](#source-values) |
| `opted_in_at` | string \| null | ISO 8601 timestamp of the most recent opt-in; `null` when `status` is `"opted_out"` |
| `opted_out_at` | string \| null | ISO 8601 timestamp of the most recent opt-out; `null` when `status` is `"opted_in"` |
| `created_at` | string | ISO 8601 timestamp of the first time consent state was recorded for this recipient |
| `updated_at` | string | ISO 8601 timestamp of the most recent transition |

### Source values

| Value | Meaning |
| :--- | :--- |
| `sms` | Inbound `STOP` / `START` keyword from the recipient |
| `api` | Recorded by your integration calling [Opt in](#opt-in-recipient) / [Opt out](#opt-out-recipient) |

---

## Write access

The write endpoints (`POST .../opt-in` and `POST .../opt-out`) are restricted to organizations on their own active, customer-managed 10DLC campaign. The reason: organizations on the Inkbox-default campaign share consent state with every other org on the same campaign, so a programmatic override from one customer would change the consent surface for others.

Once your own brand and campaign are registered and active (see [10DLC registration](/docs/capabilities/phone/10dlc)), both write endpoints unlock automatically. Until then they return `409` with error `customer_campaign_required` — the read endpoints work either way.

---

## Overrides and precedence

The write endpoints do not enforce a precedence rule between `sms`-sourced and `api`-sourced consent. The latest write is what gates outbound sends, regardless of source. In particular, a `POST .../opt-in` call will succeed even when the row's current state is `(status: "opted_out", source: "sms")` — i.e. a recipient who texted `STOP`. The full transition history is preserved per-row in the audit log, so reviewers can always reconstruct when each source touched a recipient.

Because the API will accept an override of a prior `STOP`, **you own the legitimacy of any opt-in you write through it.** Inkbox expects you to have a fresh, documented consent artifact (signup form, paper waiver, recorded conversation, etc.) for every opt-in you record this way. The same applies to API opt-outs that override an earlier API opt-in.

---

## List opt-ins `GET`


List the consent rows for your organization, ordered by most recently updated first.

### Query parameters

| Parameter | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `status` | string | — | Filter by `"opted_in"` or `"opted_out"`. Omit to return both. |
| `limit` | integer | 50 | Number of results (1--200) |
| `offset` | integer | 0 | Pagination offset |

### Response (200)

```json
[
    {
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "organization_id": "org_2abc123",
      "receiver_number": "+15551234567",
      "status": "opted_out",
      "source": "sms",
      "opted_in_at": null,
      "opted_out_at": "2026-05-15T11:45:00Z",
      "created_at": "2026-05-10T09:00:00Z",
      "updated_at": "2026-05-15T11:45:00Z"
    }
]
```

### Code examples

**cURL**

```bash
curl -X GET "https://inkbox.ai/api/v1/phone/sms-opt-ins?status=opted_out&limit=50" \\
    -H "X-API-Key: YOUR_API_KEY"
```

**JavaScript**

```javascript
import { Inkbox, SmsOptInStatus } from "@inkbox/sdk";

const inkbox = new Inkbox({ apiKey: "YOUR_API_KEY" });
const rows = await inkbox.smsOptIns.list({
    status: SmsOptInStatus.OPTED_OUT,
    limit: 50,
});
```

**Python**

```python
from inkbox import Inkbox, SmsOptInStatus

with Inkbox(api_key="YOUR_API_KEY") as inkbox:
    rows = inkbox.sms_opt_ins.list(status=SmsOptInStatus.OPTED_OUT, limit=50)
```

**CLI**

```bash
inkbox sms-opt-in list --status opted_out --limit 50
```

---

## Get opt-in `GET`


Look up the consent state for one recipient under your organization.

### Path parameters

| Parameter | Type | Description |
| :--- | :--- | :--- |
| `receiver_number` | string | Recipient phone number in E.164 (for example `+15551234567`) |

### Response (200)

Returns the opt-in object for the recipient.

### Error responses

| Status | Description |
| :--- | :--- |
| 400 | `invalid_receiver_number` — `receiver_number` is not valid E.164 |
| 404 | `opt_in_not_found` — no consent row exists for this recipient under your org |

### Code examples

**cURL**

```bash
curl -X GET "https://inkbox.ai/api/v1/phone/sms-opt-ins/+15551234567" \\
    -H "X-API-Key: YOUR_API_KEY"
```

**JavaScript**

```javascript
const row = await inkbox.smsOptIns.get("+15551234567");
```

**Python**

```python
row = inkbox.sms_opt_ins.get("+15551234567")
```

**CLI**

```bash
inkbox sms-opt-in get +15551234567
```

---

## Opt in recipient `POST`


Mark a recipient as opted in. Use this when you've captured consent through your own channel (a signup form, an in-product flow, a paper waiver, etc.) and want the server-side consent gate to reflect that.

Each call appends an audit event with `source: "api"` and updates the row's `status` to `"opted_in"`.

### Path parameters

| Parameter | Type | Description |
| :--- | :--- | :--- |
| `receiver_number` | string | Recipient phone number in E.164 |

### Response (200)

Returns the updated opt-in object.

### Error responses

| Status | Description |
| :--- | :--- |
| 400 | `invalid_receiver_number` — `receiver_number` is not valid E.164 |
| 409 | `customer_campaign_required` — your org is not on its own active, customer-managed 10DLC campaign; see [Write access](#write-access) |

### Code examples

**cURL**

```bash
curl -X POST "https://inkbox.ai/api/v1/phone/sms-opt-ins/+15551234567/opt-in" \\
    -H "X-API-Key: YOUR_API_KEY"
```

**JavaScript**

```javascript
const row = await inkbox.smsOptIns.optIn("+15551234567");
```

**Python**

```python
row = inkbox.sms_opt_ins.opt_in("+15551234567")
```

**CLI**

```bash
inkbox sms-opt-in opt-in +15551234567
```

---

## Opt out recipient `POST`


Mark a recipient as opted out. Use this to honor an opt-out you collected outside of `STOP` (for example, by phone or email) so the server-side send gate immediately refuses further texts to this recipient under your org.

Each call appends an audit event with `source: "api"` and updates the row's `status` to `"opted_out"`.

### Path parameters

| Parameter | Type | Description |
| :--- | :--- | :--- |
| `receiver_number` | string | Recipient phone number in E.164 |

### Response (200)

Returns the updated opt-in object.

### Error responses

| Status | Description |
| :--- | :--- |
| 400 | `invalid_receiver_number` — `receiver_number` is not valid E.164 |
| 409 | `customer_campaign_required` — your org is not on its own active, customer-managed 10DLC campaign; see [Write access](#write-access) |

### Code examples

**cURL**

```bash
curl -X POST "https://inkbox.ai/api/v1/phone/sms-opt-ins/+15551234567/opt-out" \\
    -H "X-API-Key: YOUR_API_KEY"
```

**JavaScript**

```javascript
const row = await inkbox.smsOptIns.optOut("+15551234567");
```

**Python**

```python
row = inkbox.sms_opt_ins.opt_out("+15551234567")
```

**CLI**

```bash
inkbox sms-opt-in opt-out +15551234567
```
