Skip to main content

Requirements

  • An active paid plan and SMS credits
  • SMS credits in your workspace (purchase from the billing page)
  • SMPP must be enabled on your Torpedo instance

Basic send

curl -X POST https://api.torpedo.co.mz/api/v1/sms \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+258841234567",
    "body": "Your verification code is 482910. Expires in 10 minutes."
  }'
const res = await fetch('https://api.torpedo.co.mz/api/v1/sms', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.TORPEDO_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    to: '+258841234567',
    body: 'Your verification code is 482910. Expires in 10 minutes.',
  }),
})
const { data } = await res.json()
console.log(data.id) // message ID
console.log(data.status) // 'queued'
import httpx

res = httpx.post(
    'https://api.torpedo.co.mz/api/v1/sms',
    headers={'X-API-Key': TORPEDO_API_KEY},
    json={
        'to': '+258841234567',
        'body': 'Your verification code is 482910. Expires in 10 minutes.',
    },
)
data = res.json()['data']
print(data['id'], data['status'])  # queued
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
)

func main() {
	payload, _ := json.Marshal(map[string]string{
		"to":   "+258841234567",
		"body": "Your verification code is 482910. Expires in 10 minutes.",
	})

	req, _ := http.NewRequest("POST", "https://api.torpedo.co.mz/api/v1/sms", bytes.NewBuffer(payload))
	req.Header.Set("X-API-Key", os.Getenv("TORPEDO_API_KEY"))
	req.Header.Set("Content-Type", "application/json")

	resp, _ := http.DefaultClient.Do(req)
	defer resp.Body.Close()
	fmt.Println(resp.Status)
}
import java.net.http.*;
import java.net.URI;

var body = """
    {"to":"+258841234567","body":"Your verification code is 482910. Expires in 10 minutes."}""";

var request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.torpedo.co.mz/api/v1/sms"))
    .header("X-API-Key", System.getenv("TORPEDO_API_KEY"))
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString(body))
    .build();

var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", Environment.GetEnvironmentVariable("TORPEDO_API_KEY"));

var payload = new { to = "+258841234567", body = "Your verification code is 482910. Expires in 10 minutes." };
var response = await client.PostAsJsonAsync("https://api.torpedo.co.mz/api/v1/sms", payload);
Console.WriteLine(await response.Content.ReadAsStringAsync());
The response is 202 Accepted — delivery is asynchronous.

Phone number format

The to field must be a valid Mozambican mobile number in E.164 format:
+258 8X XXXXXXX
Accepted prefixes: 84, 82, 83, 85, 86, 87 (Vodacom, Movitel, tmcel).
{ "to": "+258841234567" }  ✓
{ "to": "841234567" }      ✗  missing country code
{ "to": "+1234567890" }    ✗  not a Mozambican number

SMS status lifecycle

queued → sent → delivered
              ↘ failed
Poll GET /api/v1/sms/{id} to check the current status.

Idempotency

Pass an Idempotency-Key header to prevent duplicate sends on retries:
curl -X POST https://api.torpedo.co.mz/api/v1/sms \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Idempotency-Key: order-789-sms-confirmation" \
  -H "Content-Type: application/json" \
  -d '{ "to": "+258841234567", "body": "Your order has shipped." }'
If a request with the same key was already accepted, Torpedo returns the original 202 without sending again. Keys are scoped to your workspace.

Credits and quota

Each SMS deducts one credit from your workspace balance. If your balance reaches zero, sends return 402:
{
  "errors": [{ "message": "No SMS credits remaining. Contact support to top up." }]
}
Purchase additional credits from the billing page in your dashboard.

Suppression

Addresses that permanently fail delivery (UNDELIV, REJECTD SMPP error codes) are automatically added to the suppression list. Sending to a suppressed number returns 422:
{
  "errors": [{ "message": "Recipient '+258841234567' is suppressed (reason: hard_bounce)" }]
}
Remove an address from the suppression list via DELETE /api/v1/suppressions/{address}?channel=sms.