cast-ghl-plugin/CAST_API_REFERENCE.md
Head of Product & Engineering a40a4aa626
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
feat: initial implementation of Cast GHL Provider
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>
2026-04-04 17:27:05 +02:00

13 KiB

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

{
  "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

{
  "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

{
  "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

{
  "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:

{
  "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.

Request Body

Same as Send SMS.

Success Response

Same as 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.

Request Body

Same as Send SMS.

Success Response

Same as 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 in behavior and response shape, but routed through the SIM pool.

Account requirement: Same as Send SIM — the sim channel must be enabled on your account.

Request Headers

Same as Send SMS.

Request Body

Same as Send Bulk SMS.

Success Response

Same as 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:

{
  "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

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

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)

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)

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

$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)

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.