# Managed SMS API and dashboard flow

Managed SMS accepts a recipient, message, and optional sender name. The backend normalizes the number, resolves its country and telco, verifies sender coverage, selects an eligible provider route, counts billable segments, reserves the exact wallet amount, and queues a durable message. Customers never send provider credentials, route IDs, prices, or exchange rates.

## Authentication and permissions

Dashboard endpoints under `/api` use a signed-in user's JWT. Integration endpoints under `/api/v1` use an API key in `Authorization: Bearer YOUR_API_KEY`.

Choose explicit permissions when creating a key:

| Permission | Managed SMS operations |
| --- | --- |
| `sms:send` | Quote, send one message, or send a bulk batch |
| `sms:read` | Read catalog, logs, message reports, and batch reports |
| `sms:cancel` | Cancel an undispatched message and release its reservation |

Keys created without a required permission receive HTTP 403. Existing keys with no permission grants cannot call these managed SMS endpoints.

## Send one message

```bash
curl 'https://api.example.com/api/v1/sms/send' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Idempotency-Key: order-123-sms' \
  -H 'Content-Type: application/json' \
  -d '{
    "phoneNumber": "+254712345678",
    "message": "Your order is ready.",
    "senderID": "GENIUS",
    "allowDefaultSenderFallback": false
  }'
```

Only `phoneNumber` and `message` are required. Omitting `senderID` uses the account default. A local number may supply `countryCode`; otherwise the account country is used. `scheduledAt` accepts an RFC3339 time, and `clientReference` stores your correlation value.

The response is HTTP 202 with a durable batch. Queueing confirms reservation and persistence, not provider acceptance or delivery. Reusing an idempotency key with the same payload returns the original batch. Reusing it with another payload returns HTTP 409.

## Quote and bulk send

POST the same payload to `/api/v1/sms/quote` to preview segments and per-recipient charges. Quotes expire after five minutes.

For bulk sending, POST to `/api/v1/sms/send-bulk` with up to 1,000 recipients:

```json
{
  "senderID": "GENIUS",
  "message": "Hello {{name}}, your order is ready.",
  "recipients": [
    { "phoneNumber": "+254712345678", "variables": { "name": "Amina" } },
    { "phoneNumber": "+256771234567", "variables": { "name": "Daniel" } }
  ]
}
```

Each recipient is rendered, routed, segmented, and priced independently. The whole direct batch is preflighted before funds are reserved. Duplicate rows are retained.

## Billing and reports

Accepted messages reserve funds. Cancellation before dispatch releases them. The first provider submission captures the reservation exactly once. Provider rejection, timeout, or later delivery failure remains charged because upstream submission has begun. A safe backup route does not create a second customer charge.

Use these endpoints with `sms:read`:

- `GET /api/v1/sms/logs`
- `GET /api/v1/sms/reports/{messageID}`
- `GET /api/v1/sms/batches/{batchID}`

Delivery states are `queued`, `scheduled`, `dispatching`, `accepted`, `delivered`, `rejected`, `failed`, `unknown`, and `cancelled`. Billing states are `reserved`, `charged`, `released`, and `adjusted`. Amounts are decimal strings with up to six fractional digits.

Cancel an undispatched message with `POST /api/v1/sms/reports/{messageID}/cancel` and the `sms:cancel` permission.

## Sender registration and wallet funding

Sender purchase and payment endpoints are dashboard operations under `/api`:

1. Upload private documents with `POST /api/sender-documents`.
2. Obtain a 30-minute itemized quote at `POST /api/sender-orders/quote`.
3. Purchase the valid quote through `POST /api/sender-orders` using wallet funds.
4. Track independent telco statuses at `GET /api/sender-orders`.
5. Submit requested information at `POST /api/sender-orders/{id}/information`.

Wallet funding uses `GET /api/payments/options`, `POST /api/payments/quote`, `POST /api/payments/checkout`, and the verified payment-intent status endpoints. A browser return URL never marks a payment paid. M-Pesa and Paystack fulfillment credit the wallet once after server-side verification.

## Stable managed SMS errors

Clients should handle `INVALID_PHONE_NUMBER`, `COUNTRY_UNSUPPORTED`, `TELCO_UNSUPPORTED`, `SENDER_NOT_ASSIGNED`, `SENDER_NOT_APPROVED_FOR_TELCO`, `NO_ELIGIBLE_ROUTE`, `PRICE_UNAVAILABLE`, `FX_UNAVAILABLE`, `INSUFFICIENT_FUNDS`, `QUOTE_EXPIRED`, and `IDEMPOTENCY_CONFLICT`.
