Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Complete MVP implementation of the Cast GHL Conversation Provider bridge: - Go module setup with chi router and mongo-driver dependencies - Config loading with env var validation and defaults - MongoDB token store with upsert, get, update, delete operations - Cast.ph SMS client with 429 retry logic and typed errors - Phone number normalization (E.164 ↔ Philippine local format) - GHL OAuth 2.0 install/callback/refresh flow - GHL webhook handler with ECDSA signature verification (async dispatch) - GHL API client for message status updates and inbound message stubs - Multi-stage Dockerfile, docker-compose with MongoDB, Woodpecker CI pipeline - Unit tests for phone normalization, Cast client, GHL webhook, and OAuth handlers Co-Authored-By: SideKx <sidekx.ai@sds.dev>
464 lines
13 KiB
Markdown
464 lines
13 KiB
Markdown
# Cast SMS API — Developer Documentation
|
|
|
|
**Base URL:** `https://api.cast.ph`
|
|
**API Version:** v1
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
The Cast API allows you to send SMS, OTP, and SIM messages programmatically. All API requests must be authenticated using an API key provided by Cast.
|
|
|
|
---
|
|
|
|
## Authentication
|
|
|
|
All requests to the SMS and OTP endpoints require an API key passed in the request header.
|
|
|
|
**Header:**
|
|
```
|
|
X-API-Key: cast_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
```
|
|
|
|
API keys have the format `cast_` followed by 64 hexadecimal characters. Keep your API key secret — do not expose it in client-side code or public repositories.
|
|
|
|
---
|
|
|
|
## Endpoints
|
|
|
|
### 1. Send SMS
|
|
|
|
**`POST /api/sms/send`**
|
|
|
|
Sends an SMS message to the specified destination number.
|
|
|
|
#### Request Headers
|
|
|
|
| Header | Required | Value |
|
|
|--------|----------|-------|
|
|
| `X-API-Key` | Yes | Your API key |
|
|
| `Content-Type` | Yes | `application/json` |
|
|
|
|
#### Request Body
|
|
|
|
```json
|
|
{
|
|
"to": "09171234567",
|
|
"message": "Hello, this is a test message.",
|
|
"sender_id": "CAST"
|
|
}
|
|
```
|
|
|
|
| Field | Type | Required | Description |
|
|
|-------|------|----------|-------------|
|
|
| `to` | string | Yes | Recipient phone number. Must be an 11-digit Philippine mobile number (e.g. `09171234567`). |
|
|
| `message` | string | Yes | Message content. Max 300 characters (2 SMS parts). Absolute limit is 450 characters (3 SMS parts). |
|
|
| `sender_id` | string | No | Sender ID to use. Max 11 characters. Must be pre-approved. Defaults to your assigned sender ID if only one is configured. |
|
|
|
|
#### Success Response — `200 OK`
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message_id": "abc123def456",
|
|
"parts": 1
|
|
}
|
|
```
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `success` | bool | `true` if the message was sent. |
|
|
| `message_id` | string | Gateway-assigned message identifier. |
|
|
| `parts` | int | Number of SMS parts (standard SMS = 1 part per 160 characters). |
|
|
|
|
---
|
|
|
|
### 2. Send Bulk SMS
|
|
|
|
**`POST /api/sms/bulk`**
|
|
|
|
Sends the same SMS message to multiple recipients in a single API call. Up to **1,000 destinations** per request.
|
|
|
|
Credits are checked upfront for the full batch before any messages are sent. Each destination is sent individually and logged separately, so partial success is possible if a send fails mid-batch.
|
|
|
|
#### Request Headers
|
|
|
|
| Header | Required | Value |
|
|
|--------|----------|-------|
|
|
| `X-API-Key` | Yes | Your API key |
|
|
| `Content-Type` | Yes | `application/json` |
|
|
|
|
#### Request Body
|
|
|
|
```json
|
|
{
|
|
"to": ["09171234567", "09181234567", "09191234567"],
|
|
"message": "Hello, this is a broadcast message.",
|
|
"sender_id": "CAST"
|
|
}
|
|
```
|
|
|
|
| Field | Type | Required | Description |
|
|
|-------|------|----------|-------------|
|
|
| `to` | array of strings | Yes | List of recipient phone numbers. Min 1, max 1,000. Each must be a valid phone number. |
|
|
| `message` | string | Yes | Message content to send to all recipients. |
|
|
| `sender_id` | string | No | Sender ID to use. Must be pre-approved. Defaults to your assigned sender ID if only one is configured. |
|
|
|
|
#### Success Response — `200 OK`
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"total": 3,
|
|
"sent": 3,
|
|
"failed": 0,
|
|
"results": [
|
|
{ "to": "09171234567", "success": true, "message_id": "abc123", "parts": 1 },
|
|
{ "to": "09181234567", "success": true, "message_id": "def456", "parts": 1 },
|
|
{ "to": "09191234567", "success": true, "message_id": "ghi789", "parts": 1 }
|
|
]
|
|
}
|
|
```
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `success` | bool | `true` only if all destinations were sent successfully. |
|
|
| `total` | int | Total number of destinations in the request. |
|
|
| `sent` | int | Number of messages successfully sent. |
|
|
| `failed` | int | Number of messages that failed. |
|
|
| `results` | array | Per-destination result. Each entry includes `to`, `success`, and on success: `message_id` and `parts`. On failure: `error`. |
|
|
|
|
**Note:** The HTTP status is always `200` when the request itself is valid, even if some individual sends failed. Check `success` and `failed` in the response body to detect partial failures.
|
|
|
|
#### Error Response (request-level) — `402`, `400`, `403`
|
|
|
|
Request-level errors (invalid input, insufficient credits, bad sender ID) return a flat error response — no `results` array:
|
|
|
|
```json
|
|
{
|
|
"success": false,
|
|
"error": "insufficient credits: need 6, have 3"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 3. Send OTP
|
|
|
|
**`POST /api/otp/send`**
|
|
|
|
Sends an OTP (One-Time Password) message. Identical to the SMS endpoint but routed through a dedicated OTP gateway.
|
|
|
|
#### Request Headers
|
|
|
|
Same as [Send SMS](#1-send-sms).
|
|
|
|
#### Request Body
|
|
|
|
Same as [Send SMS](#1-send-sms).
|
|
|
|
#### Success Response
|
|
|
|
Same as [Send SMS](#1-send-sms).
|
|
|
|
---
|
|
|
|
### 4. Send SIM
|
|
|
|
**`POST /api/sim/send`**
|
|
|
|
Sends a message via a SIM gateway (SMPP-to-SIM). Same request and response shape as Send SMS, but routed through the SIM pool.
|
|
|
|
> **Account requirement:** The `sim` channel must be explicitly enabled on your account. Accounts default to SMS and OTP only. Contact Cast to enable SIM access.
|
|
|
|
#### Request Headers
|
|
|
|
Same as [Send SMS](#1-send-sms).
|
|
|
|
#### Request Body
|
|
|
|
Same as [Send SMS](#1-send-sms).
|
|
|
|
#### Success Response
|
|
|
|
Same as [Send SMS](#1-send-sms).
|
|
|
|
#### Additional Error Responses
|
|
|
|
| Status | Error | Cause |
|
|
|--------|-------|-------|
|
|
| `403 Forbidden` | `"sim channel not enabled for this account"` | Your account does not have SIM access |
|
|
| `503 Service Unavailable` | `"sim service is disabled"` | The SIM gateway is not configured on the platform |
|
|
|
|
---
|
|
|
|
### 5. Send Bulk SIM
|
|
|
|
**`POST /api/sim/bulk`**
|
|
|
|
Sends the same message to multiple recipients via the SIM gateway. Identical to [Send Bulk SMS](#2-send-bulk-sms) in behavior and response shape, but routed through the SIM pool.
|
|
|
|
> **Account requirement:** Same as [Send SIM](#4-send-sim) — the `sim` channel must be enabled on your account.
|
|
|
|
#### Request Headers
|
|
|
|
Same as [Send SMS](#1-send-sms).
|
|
|
|
#### Request Body
|
|
|
|
Same as [Send Bulk SMS](#2-send-bulk-sms).
|
|
|
|
#### Success Response
|
|
|
|
Same as [Send Bulk SMS](#2-send-bulk-sms).
|
|
|
|
#### Additional Error Responses
|
|
|
|
| Status | Error | Cause |
|
|
|--------|-------|-------|
|
|
| `403 Forbidden` | `"sim channel not enabled for this account"` | Your account does not have SIM access |
|
|
| `503 Service Unavailable` | `"sim service is disabled"` | The SIM gateway is not configured on the platform |
|
|
|
|
---
|
|
|
|
## Error Handling
|
|
|
|
All errors return a JSON body in this format:
|
|
|
|
```json
|
|
{
|
|
"success": false,
|
|
"error": "error message here"
|
|
}
|
|
```
|
|
|
|
### HTTP Status Codes
|
|
|
|
| Status Code | Meaning |
|
|
|-------------|---------|
|
|
| `200 OK` | Request succeeded |
|
|
| `400 Bad Request` | Invalid or missing request fields |
|
|
| `401 Unauthorized` | Missing or invalid API key |
|
|
| `402 Payment Required` | Insufficient credits |
|
|
| `403 Forbidden` | Access denied (see below) |
|
|
| `429 Too Many Requests` | Rate limit exceeded |
|
|
| `500 Internal Server Error` | Unexpected server error |
|
|
| `503 Service Unavailable` | SMS/SIM service is temporarily disabled |
|
|
|
|
### Common Error Messages
|
|
|
|
| Error Message | Status | Cause |
|
|
|---------------|--------|-------|
|
|
| `"missing X-API-Key header"` | 401 | No API key provided |
|
|
| `"invalid api key"` | 401 | API key not found or invalid |
|
|
| `"api key is revoked"` | 403 | API key has been deactivated |
|
|
| `"api key has expired"` | 403 | API key passed its expiration date |
|
|
| `"user account is inactive"` | 403 | Account has been disabled |
|
|
| `"ip not whitelisted"` | 403 | Request origin IP is not in the allowed list |
|
|
| `"sim channel not enabled for this account"` | 403 | SIM access is not enabled on your account |
|
|
| `"insufficient credits: need X, have Y"` | 402 | Not enough credits to send the message |
|
|
| `"no sender IDs assigned to this user"` | 403 | No sender ID has been configured for your account |
|
|
| `"sender_id 'X' is not allowed for this user"` | 403 | The specified sender ID is not approved for your account |
|
|
| `"to is required"` | 400 | The `to` field is missing |
|
|
| `"to must be 7-15 characters"` | 400 | Phone number is not a valid length |
|
|
| `"message is required"` | 400 | The `message` field is missing |
|
|
| `"message is too long (max 450 characters)"` | 400 | Message exceeds the 3-part SMS limit |
|
|
| `"sender ID is too long (max 11 characters)"` | 400 | Sender ID exceeds the character limit |
|
|
|
|
---
|
|
|
|
## Rate Limits
|
|
|
|
| Limit | Value |
|
|
|-------|-------|
|
|
| Requests per second | 30 |
|
|
| Burst (max simultaneous) | 50 |
|
|
|
|
When you exceed the rate limit, the API returns `429 Too Many Requests` with a `Retry-After: 60` header. Wait the specified number of seconds before retrying.
|
|
|
|
---
|
|
|
|
## SMS Parts & Credits
|
|
|
|
SMS messages are divided into **parts** based on length:
|
|
|
|
| Encoding | Single SMS | Multi-part SMS (per part) |
|
|
|----------|-----------|--------------------------|
|
|
| GSM-7 (standard characters) | 160 chars | 153 chars |
|
|
| Unicode (special characters, emoji) | 70 chars | 67 chars |
|
|
|
|
Credits are deducted **per part** after the message is successfully sent. For example, a 200-character standard message uses 2 parts and costs 2 credits.
|
|
|
|
---
|
|
|
|
## IP Whitelisting
|
|
|
|
If your account has IP whitelisting enabled, only requests from approved IP addresses or CIDR ranges will be accepted. Requests from unlisted IPs will receive a `403 Forbidden` response.
|
|
|
|
Contact Cast to add or update your whitelisted IPs.
|
|
|
|
---
|
|
|
|
## Sender ID
|
|
|
|
A Sender ID is the name or number displayed as the sender on the recipient's device (e.g., `MYAPP`, `BANK`, `639171234567`).
|
|
|
|
- Sender IDs must be **pre-approved** before use.
|
|
- If your account has only one Sender ID, the `sender_id` field in the request is optional — it will be used automatically.
|
|
- If your account has multiple Sender IDs, you **must** specify the `sender_id` in each request.
|
|
|
|
Contact Cast to register a Sender ID.
|
|
|
|
---
|
|
|
|
## Code Examples
|
|
|
|
### cURL — Bulk SMS
|
|
|
|
```bash
|
|
curl -X POST https://api.cast.ph/api/sms/bulk \
|
|
-H "X-API-Key: cast_your_api_key_here" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"to": ["09171234567", "09181234567"],
|
|
"message": "Hello from Cast!",
|
|
"sender_id": "MYAPP"
|
|
}'
|
|
```
|
|
|
|
### cURL — Single SMS
|
|
|
|
```bash
|
|
curl -X POST https://api.cast.ph/api/sms/send \
|
|
-H "X-API-Key: cast_your_api_key_here" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"to": "09171234567",
|
|
"message": "Your verification code is 123456.",
|
|
"sender_id": "MYAPP"
|
|
}'
|
|
```
|
|
|
|
### JavaScript (fetch)
|
|
|
|
```js
|
|
const response = await fetch("https://api.cast.ph/api/sms/send", {
|
|
method: "POST",
|
|
headers: {
|
|
"X-API-Key": "cast_your_api_key_here",
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({
|
|
to: "639171234567",
|
|
message: "Your verification code is 123456.",
|
|
sender_id: "MYAPP",
|
|
}),
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
console.log("Sent! Message ID:", data.message_id, "Parts:", data.parts);
|
|
} else {
|
|
console.error("Failed:", data.error);
|
|
}
|
|
```
|
|
|
|
### Python (requests)
|
|
|
|
```python
|
|
import requests
|
|
|
|
response = requests.post(
|
|
"https://api.cast.ph/api/sms/send",
|
|
headers={
|
|
"X-API-Key": "cast_your_api_key_here",
|
|
"Content-Type": "application/json",
|
|
},
|
|
json={
|
|
"to": "09171234567",
|
|
"message": "Your verification code is 123456.",
|
|
"sender_id": "MYAPP",
|
|
},
|
|
)
|
|
|
|
data = response.json()
|
|
|
|
if data["success"]:
|
|
print(f"Sent! Message ID: {data['message_id']}, Parts: {data['parts']}")
|
|
else:
|
|
print(f"Failed: {data['error']}")
|
|
```
|
|
|
|
### PHP (cURL)
|
|
|
|
```php
|
|
<?php
|
|
|
|
$apiKey = "cast_your_api_key_here";
|
|
$payload = json_encode([
|
|
"to" => "639171234567",
|
|
"message" => "Your verification code is 123456.",
|
|
"sender_id" => "MYAPP",
|
|
]);
|
|
|
|
$ch = curl_init("https://api.cast.ph/api/sms/send");
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
curl_setopt($ch, CURLOPT_POST, true);
|
|
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
|
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
|
"X-API-Key: $apiKey",
|
|
"Content-Type: application/json",
|
|
]);
|
|
|
|
$response = json_decode(curl_exec($ch), true);
|
|
curl_close($ch);
|
|
|
|
if ($response["success"]) {
|
|
echo "Sent! Message ID: " . $response["message_id"] . "\n";
|
|
} else {
|
|
echo "Failed: " . $response["error"] . "\n";
|
|
}
|
|
```
|
|
|
|
### C# (HttpClient)
|
|
|
|
```csharp
|
|
using System.Net.Http;
|
|
using System.Net.Http.Headers;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
|
|
var client = new HttpClient();
|
|
client.DefaultRequestHeaders.Add("X-API-Key", "cast_your_api_key_here");
|
|
|
|
var payload = JsonSerializer.Serialize(new
|
|
{
|
|
to = "09171234567",
|
|
message = "Your verification code is 123456.",
|
|
sender_id = "MYAPP"
|
|
});
|
|
|
|
var content = new StringContent(payload, Encoding.UTF8, "application/json");
|
|
var response = await client.PostAsync("https://api.cast.ph/api/sms/send", content);
|
|
var body = await response.Content.ReadAsStringAsync();
|
|
|
|
using var doc = JsonDocument.Parse(body);
|
|
var root = doc.RootElement;
|
|
|
|
if (root.GetProperty("success").GetBoolean())
|
|
{
|
|
Console.WriteLine($"Sent! Message ID: {root.GetProperty("message_id").GetString()}, Parts: {root.GetProperty("parts").GetInt32()}");
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine($"Failed: {root.GetProperty("error").GetString()}");
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Support
|
|
|
|
For API access, sender ID registration, IP whitelisting, or any technical issues, contact the Cast team.
|