# Allotropic SMS API reference

REST API for sending transactional and promotional SMS, checking status, credits and reports.

**Base URL:** `https://sms.allotropicltd.com/api/public/v1`

All requests and responses are JSON. Responses use `{ "success": true, "data": … }` or `{ "success": false, "error": { "code", "message" } }`.

## Authentication

Create a key on the Developers page and send it as a bearer token on every request. Keys are shown once; store them securely and revoke them if exposed.

```
Authorization: Bearer alt_live_xxxxxxxx
```

## Phone numbers

Local numbers default to Tanzania (+255): 0712345678 becomes 255712345678.

Numbers in other countries must be international: 2547…, +44…, 00971…. Invalid and duplicate numbers are returned in `rejected` and are not charged.

## Billing and message parts

1 credit = 1 SMS part. Standard (GSM-7) messages fit 160 characters in one part, or 153 per part when longer. Unicode messages (emoji, non-Latin scripts) fit 70, or 63 per part when longer.

Credits are reserved when a request is accepted. Messages that fail permanently, and cancelled scheduled messages, are refunded automatically.

## Sent vs delivered

`queued`: accepted and waiting to be sent (including scheduled and retrying messages).

`sent`: handed to the mobile network. This is not proof the handset received it.

`delivered`: the network confirmed delivery to the handset. Delivery confirmations are being rolled out; until then most messages will finish as `sent`.

`failed`: could not be sent after retries; credits refunded. `cancelled`: a scheduled message cancelled before sending.

## Scheduling and retries

Pass `schedule_at` (ISO 8601, UTC recommended). Temporary failures retry automatically with growing delays (2, 4, 8, 16 minutes) up to 5 attempts.

## Idempotency

Send an `Idempotency-Key` header (any unique string) on send calls. Repeating a request with the same key returns the original campaign with `duplicate: true` and is not charged again.

## Rate limits and pagination

Each API key may make 120 requests per minute; beyond that you receive HTTP 429. Lists use `page` and `per_page` (max 500) and return `meta.total`.

## Endpoints

### Send SMS

`POST https://sms.allotropicltd.com/api/public/v1/sms/send`

Send the same message to one or many numbers (up to 10,000). `to` accepts a string, a comma-separated string or an array.

- `sender_id` (string, required, max 13): an approved sender ID on your account
- `to` (string | string[], required): local Tanzanian (0712345678) or international (255712345678 / +255712345678) numbers
- `message` (string, required, max 1600 characters)
- `schedule_at` (ISO 8601, optional): send later; must be at least 30 seconds in the future
- `reference` (string, optional, max 120): your campaign label

Request:

```json
{
  "sender_id": "MYBRAND",
  "to": ["255712345678", "0754000000"],
  "message": "Your verification code is 4821",
  "schedule_at": "2026-12-01T08:00:00Z",
  "reference": "December promo"
}
```

Response:

```json
{
  "success": true,
  "data": {
    "campaign_id": "6f1c2a8e-…",
    "status": "scheduled",
    "accepted": 2,
    "rejected": [],
    "credits_used": 2,
    "balance": 998,
    "duplicate": false
  }
}
```

### Send personalised bulk

`POST https://sms.allotropicltd.com/api/public/v1/sms/bulk`

Different text per recipient in one request (up to 10,000 items).

- `sender_id` (string, required)
- `messages` (array, required): items of `{ "to": string, "message": string }`
- `schedule_at`, `reference`: as for Send SMS

Request:

```json
{
  "sender_id": "MYBRAND",
  "messages": [
    { "to": "255712345678", "message": "Hi Neema, your order has shipped" },
    { "to": "255754000000", "message": "Hi Juma, your order has shipped" }
  ]
}
```

Response:

```json
{ "success": true, "data": { "campaign_id": "…", "status": "completed", "accepted": 2, "rejected": [], "credits_used": 2, "balance": 996, "duplicate": false } }
```

### Message or campaign status

`GET https://sms.allotropicltd.com/api/public/v1/sms/{id}`

Pass a message ID or a campaign ID returned by a send call. Campaigns include up to 1,000 messages.

Response:

```json
{ "success": true, "data": { "id": "…", "campaign_id": "…", "to": "255712345678", "sender_id": "MYBRAND", "message": "…", "parts": 1, "encoding": "GSM7", "status": "sent", "error": null, "created_at": "…", "sent_at": "…", "delivered_at": null } }
```

### Reports

`GET https://sms.allotropicltd.com/api/public/v1/reports`

Paginated message list, newest first.

- `status`: queued | sent | delivered | failed | cancelled
- `to`: phone number contains digits
- `sender_id`, `campaign_id`
- `from`, `to_date`: ISO dates
- `page` (default 1), `per_page` (default 100, max 500)

Response:

```json
{ "success": true, "data": [ … ], "meta": { "page": 1, "per_page": 100, "total": 532 } }
```

### Balance

`GET https://sms.allotropicltd.com/api/public/v1/balance`

Remaining SMS credits.

Response:

```json
{ "success": true, "data": { "credits": 998 } }
```

### Sender IDs

`GET https://sms.allotropicltd.com/api/public/v1/sender-ids`

Active sender IDs approved for your account.

Response:

```json
{ "success": true, "data": [ { "sender_id": "MYBRAND", "default": true } ] }
```

Example:

```bash
curl -X POST https://sms.allotropicltd.com/api/public/v1/sms/send \
  -H "Authorization: Bearer alt_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1042" \
  -d '{"sender_id":"MYBRAND","to":"255712345678","message":"Your code is 4821"}'
```

## Webhooks

Set an https URL on the Developers page. We POST final status changes (sent, failed) in batches. Respond with any 2xx within 5 seconds; delivery is best effort and not retried.

```
POST https://your-app.example.com/sms-status
Content-Type: application/json
User-Agent: AllotropicSMS-Webhook/1.0

{ "event": "message.status", "data": [ { "id": "…", "to": "255712345678", "status": "sent", "error": null } ] }
```

## Errors

| HTTP | Code | Meaning |
| --- | --- | --- |
| 400 | INVALID_JSON | Body is not valid JSON |
| 401 | UNAUTHORIZED | Missing, invalid or revoked API key |
| 402 | INSUFFICIENT_CREDITS | Not enough credits for this request |
| 403 | INVALID_SENDER_ID | Sender ID is not approved for your account |
| 403 | ACCOUNT_SUSPENDED | Account is suspended |
| 404 | NOT_FOUND | Message or campaign not found |
| 422 | VALIDATION_ERROR | Request body or query is invalid |
| 422 | NO_VALID_RECIPIENTS | None of the numbers are valid |
| 429 | RATE_LIMITED | More than 120 requests per minute per key |
| 500 | INTERNAL | Unexpected error: retry with the same Idempotency-Key |
