# CloudWave Bulk SMS — REST API

Send SMS from your own software using wallet credits on your CloudWave Bulk SMS
account. Base URL: `https://sms.cloudwavebd.com` (or whatever `SMS_BASE_URL` is
set to).

Every endpoint returns JSON. Errors look like:

```json
{ "ok": false, "error": "code", "message": "Human readable message" }
```

---

## Authentication

Every request must include your **API key** and **API secret**. Get them from
the portal: *Sidebar → API & Plugins → Generate API credentials*. You can view
both again at any time with **View Key & Secret** on that page.

Send them as headers (recommended):

```
X-Api-Key:    cw_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
X-Api-Secret: yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
```

HTTP Basic auth also works (API key as the username, secret as the password).

> Keep the secret on your server only — never put it in client-side JavaScript
> or public code.

---

## Endpoints

| Method | Endpoint          | Description                                    |
|--------|-------------------|------------------------------------------------|
| GET    | `/api/balance`    | Your wallet balance                           |
| GET    | `/api/senders`    | Sender IDs you can send from + per-part rates |
| POST   | `/api/send`       | Send an SMS (one or many recipients)          |
| GET    | `/api/history`    | Your campaigns / message statuses             |

---

## GET /api/balance

```bash
curl -H "X-Api-Key: cw_..." -H "X-Api-Secret: ..." \
  https://sms.cloudwavebd.com/api/balance
```

```json
{ "ok": true, "balance": 123.45, "currency": "BDT", "client": { "id": 3, "email": "client@example.com" } }
```

---

## GET /api/senders

```json
{
  "ok": true,
  "default_sender": "8809617611036",
  "senders": [
    { "sender_id": "8809617611036", "provider": "bulksmsbd", "rate_bdt": 0.35, "unicode": true }
  ]
}
```

`rate_bdt` is the price **per SMS part** (BDT). `unicode: true` means Bangla
messages are accepted on that Sender ID.

---

## POST /api/send

Sends one message to one or many Bangladeshi numbers. The wallet is debited
per SMS part — exactly the same rules and cost as sending from the portal.

**Request (JSON):**

```json
{
  "message": "Your SMS text — Bangla is supported",
  "to": "01712345678",
  "sender_id": "8809617611036",
  "campaign": "Optional campaign name",
  "allow_duplicates": false
}
```

- `to` — a single number string **or** an array of number strings. Accepted
  formats: `01712345678`, `8801712345678`, `+8801712345678`. Invalid numbers
  are skipped automatically.
- `sender_id` — optional; any Sender ID from `GET /api/senders`. Defaults to
  the system default (BulkSMSBD).
- `campaign` — optional label shown in History.
- `allow_duplicates` — optional boolean (default `false`). When `false`, a
  number that appears more than once is sent only once. When `true`, every
  entry is sent as given (each one billed).

**cURL:**

```bash
curl -X POST https://sms.cloudwavebd.com/api/send \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: cw_..." \
  -H "X-Api-Secret: ..." \
  -d '{
    "message": "Hello from the API!",
    "to": ["01712345678", "8801812345678"],
    "campaign": "API blast"
  }'
```

**Response (200):**

```json
{
  "ok": true,
  "campaign_id": 42,
  "provider": "bulksmsbd",
  "sender_id": "8809617611036",
  "recipients": 2,
  "parts": 1,
  "encoding": "text",
  "total_cost": 0.7,
  "balance_after": 122.75,
  "sent": 2,
  "failed": 0,
  "skipped_invalid": []
}
```

**Response (202) — held for admin approval**

When a request targets more recipients than the account's approval threshold
(default 100, configured by the operator), the send is **held for admin
approval**. The cost is reserved from your wallet and nothing is sent yet until
an operator approves it in the portal. A rejection refunds the reservation.

```json
{
  "ok": true,
  "status": "pending_approval",
  "campaign_id": 43,
  "provider": "bulksmsbd",
  "sender_id": "8809617611036",
  "recipients": 250,
  "parts": 1,
  "encoding": "text",
  "total_cost": 87.5,
  "balance_after": 35.25,
  "message": "This campaign exceeds the approval threshold and is held for admin approval. …",
  "skipped_invalid": []
}
```

**Error statuses**

| HTTP | `error`              | Meaning                                        |
|------|----------------------|------------------------------------------------|
| 401  | `unauthorized`       | Missing / invalid credentials                  |
| 403  | `suspended`          | API account suspended                          |
| 402  | `send_failed`        | Insufficient wallet balance                    |
| 422  | `invalid_*`          | Bad message, recipients, or sender_id          |
| 503  | `no_sender`          | System has no Sender ID configured             |

---

## GET /api/history

```bash
# Recent campaigns
curl -H "X-Api-Key: cw_..." -H "X-Api-Secret: ..." \
  "https://sms.cloudwavebd.com/api/history"

# One campaign + every message status
curl -H "X-Api-Key: cw_..." -H "X-Api-Secret: ..." \
  "https://sms.cloudwavebd.com/api/history?campaign_id=42"
```

Optional query params: `status` (`queued|sending|sent|delivered|failed`),
`page`, `per_page` (max 100).

---

## Example — PHP

```php
$ch = curl_init('https://sms.cloudwavebd.com/api/send');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => [
        'Content-Type: application/json',
        'X-Api-Key: cw_...',
        'X-Api-Secret: ...',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'message'  => 'Hello from PHP!',
        'to'       => ['01712345678'],
        'campaign' => 'PHP test',
    ]),
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
```

---

## Example — Python

```python
import requests

resp = requests.post(
    "https://sms.cloudwavebd.com/api/send",
    headers={"X-Api-Key": "cw_...", "X-Api-Secret": "..."},
    json={"message": "Hello from Python!", "to": ["01712345678"]},
)
print(resp.json())
```

---

## Limits

- Max **500 recipients** per request.
- Max **4000 characters** per message (messages over ~160/70 chars are billed as
  multiple parts automatically).
- The wallet is checked and debited atomically; a send is only accepted if the
  balance covers it.
