MojaWave API Reference
The MojaWave API lets you send SMS and transactional email — all from a single, unified REST interface.
Quickstart
Make your first API call in under two minutes. All you need is an API key — grab one from the developer dashboard or use a sandbox key to start immediately.
# Send your first SMS curl -X POST https://api.mojawave.com/v1/sms/send \ -H "Authorization: Bearer sk_live_mw_8472910" \ -H "Content-Type: application/json" \ -d '{ "to": "+255753276939", "from": "MojaWave", "message": "Hello from Mojawave! Your verification code is 1234." }'
import mojawave client = mojawave.Client(api_key="sk_live_mw_8472910") sms = client.sms.send( to="+255712345678", from_="MOJAWAVE", message="Your OTP is 4821. Valid for 5 minutes." ) print(sms.id, sms.status) # 3fa85f64... queued
import { MojaWave } from 'mojawave'; const client = new MojaWave({ apiKey: 'sk_live_mw_8472910' }); const sms = await client.sms.send({ to: '+255712345678', from: 'MOJAWAVE', message: 'Your OTP is 4821. Valid for 5 minutes.', }); console.log(sms.id, sms.status); // 3fa85f64... queued
Response · 200 OK
{
"success": true,
"data": {
"id": "89b82624-f1a2-4f5e-85b5-102e79a06779",
"type": "sms",
"to": "+255753276939",
"status": "sent",
"body": "Hello from Mojawave! Your verification code is 1234.",
"segments": 1,
"credits_cost": 1,
"timeline": {
"queued_at": "2026-04-05T12:03:04.485Z",
"sent_at": "2026-04-05T12:04:04.393Z"
}
}
}
Authentication
MojaWave uses bearer token authentication. Pass your API key in the Authorization header of every request.
Authorization: Bearer sk_live_mw_YOUR_KEY
| Prefix | Environment | Usage |
|---|---|---|
sk_live_mw_ |
Live | Real transactions. Keep this secret. |
sk_test_mw_ |
Sandbox | No real charges. Safe for development. |
Errors & Rate Limits
MojaWave uses standard HTTP status codes. Error responses always include a JSON body with a code and human-readable message.
| HTTP Status | Code | Meaning |
|---|---|---|
| 200 | — | Success |
| 400 | invalid_request | Missing or malformed parameters |
| 401 | unauthorized | Invalid or missing API key |
| 402 | insufficient_balance | Account balance too low |
| 422 | unprocessable | Validation failed on request body |
| 429 | rate_limit_exceeded | Too many requests — back off and retry |
| 500 | server_error | Something went wrong on our end |
Rate Limits
Default limits: 600 requests/min on the live environment and 120 requests/min on sandbox. Limits are per API key and vary by plan. Rate limit headers are included in every response:
X-RateLimit-Limit— your cap per minuteX-RateLimit-Remaining— requests left in current windowX-RateLimit-Reset— Unix timestamp when limit resets
SMS — Send Message
Send a single SMS to any Tanzania mobile number. Messages are delivered via direct telco connections to Vodacom, Tigo, Airtel, and Halotel.
| Parameter | Type | Required | Description |
|---|---|---|---|
| to | string | required | Recipient phone in E.164 format. e.g. +255712345678 |
| from | string | required | Sender ID (max 11 chars alphanumeric) or MOJAWAVE. |
| message | string | required | Message content. Long messages are split into segments automatically. |
| webhook_url | string | optional | URL to receive delivery status webhooks for this message. |
| schedule_at | string | optional | ISO 8601 timestamp for scheduled delivery. |
| metadata | object | optional | Additional custom key-value pairs for your tracking. |
{
"to": "+255753276939",
"from": "MojaWave",
"message": "Hello from Mojawave! Your verification code is 1234.",
"webhook_url": "https://example.com/webhook/sms",
"metadata": {
"customer_id": "cust98765"
},
"tags": ["onboarding", "verification"]
}
{
"success": true,
"data": {
"id": "89b82624-f1a2-4f5e-85b5-102e79a06779",
"type": "sms",
"to": "+255753276939",
"status": "sent",
"segments": 1,
"credits_cost": 1,
"queued_at": "2026-04-05T12:03:04.485Z",
"sent_at": "2026-04-05T12:04:04.393Z"
}
}
webhook_url to receive real-time delivery updates via POST requests.
SMS — Get Message Details
Retrieve full details of a message including delivery timeline and metadata.
| Field | Type | Description |
|---|---|---|
| id | uuid | Unique identifier for the message. |
| status | string | Current status (queued, sent, delivered, failed). |
| credits_cost | float | Credits used for this message. |
| timeline | object | Delivery checkpoints: queued_at, sent_at, delivered_at. |
| failure_reason | string | Human-readable reason if delivery failed. |
{
"success": true,
"data": {
"id": "89b82624-f1a2-4f5e-85b5-102e79a06779",
"type": "sms",
"to": "+255753276939",
"from": "MojaWave",
"status": "delivered",
"segments": 1,
"credits_cost": 1,
"timeline": {
"queued_at": "2026-04-05T12:03:04.485Z",
"sent_at": "2026-04-05T12:04:04.393Z",
"delivered_at": "2026-04-05T12:05:06.751Z"
}
}
}
SMS — Bulk Send
Send the same message to multiple recipients in one API call. Up to 10,000 recipients per request. Bulk jobs are processed asynchronously — a job ID is returned immediately.
{
"name": "Marketing Campaign Q1",
"from": "MOJAWAVE",
"message": "Hello , your code is ",
"recipients": [
{
"to": "+255712345678",
"personalization": {
"name": "John",
"code": "ABC123"
}
},
{
"to": "+255712345678",
"personalization": {
"name": "Jane",
"code": "ABC123"
}
}
],
"webhook_url": "https://example.com/webhooks"
}
Response · 202 Accepted
{
"success": true,
"data": {
"job_id": "ec0fb57c-8b90-4e21-9f96-48235d6a05ac",
"status": "scheduled",
"total_recipients": 2,
"estimated_credits": 2,
"has_personalization": true,
"personalization_fields": ["code", "name"],
"scheduled_at": "2026-04-05T12:07:14.559Z",
"created_at": "2026-04-05T12:06:25.317Z"
}
}
unicode type have a per-segment character limit of 70 vs 160 for plain SMS. Plan your message length accordingly.
SMS — Get Bulk Job Details
Retrieve the status, progress, and statistical summary of a bulk SMS job.
| Field | Type | Description |
|---|---|---|
| status | string | queued, processing, completed, failed |
| progress_percent | float | Job completion percentage (0-100). |
| sent_count | integer | Number of messages successfully sent. |
| total_credits_cost | float | Final total credits consumed by the job. |
{
"success": true,
"data": {
"id": "ec0fb57c-8b90-4e21-9f96-48235d6a05ac",
"name": "Marketing Campaign Q1",
"status": "completed",
"total_recipients": 2,
"sent_count": 2,
"progress_percent": 100.0,
"total_credits_cost": 2,
"completed_at": "2026-04-05T12:08:09.610Z"
}
}
Credits — Check Balance
Retrieve your organization's SMS and Email credit balances in a single call. Accepts either an API key or a user JWT token.
# Using Authorization header curl -X GET https://api.mojawave.com/v1/credits \ -H "Authorization: Bearer mw_live_your_api_key_here" # OR using X-API-Key header curl -X GET https://api.mojawave.com/v1/credits \ -H "X-API-Key: mw_live_your_api_key_here"
import requests response = requests.get( "https://api.mojawave.com/v1/credits", headers={"Authorization": "Bearer mw_live_your_api_key_here"} ) data = response.json() print(data)
| Field | Type | Description |
|---|---|---|
| service_type | string | sms or email |
| balance | integer | Current available credits. |
| total_purchased | integer | Lifetime credits purchased. |
| total_consumed | integer | Lifetime credits consumed. |
| low_balance_threshold | integer | Alert fires when balance drops below this value. |
| is_low_balance | boolean | true if balance is at or below the threshold. |
{
"success": true,
"data": {
"sms": {
"service_type": "sms",
"balance": 5000,
"total_purchased": 10000,
"total_consumed": 5000,
"low_balance_threshold": 500,
"is_low_balance": false
},
"email": {
"service_type": "email",
"balance": 200,
"total_purchased": 1000,
"total_consumed": 800,
"low_balance_threshold": 100,
"is_low_balance": true
}
}
}
Authorization: Bearer mw_live_xxx and X-API-Key: mw_live_xxx headers are accepted. Rate limited to 120 requests/min.
Model Context Protocol (MCP)
The mojawave-mcp package exposes every MojaWave API as a native tool for MCP-compatible AI assistants — Claude (Desktop & Code), ChatGPT (OpenAI Agents SDK), Gemini (Google ADK), Cursor, Windsurf, and any other tool that speaks the Model Context Protocol. Inputs are validated before any request is made, and the client retries 429/5xx responses with backoff automatically.
Installation
pip install mojawave-mcp
Available tools
| Tool | API endpoint | What it does |
|---|---|---|
| send_sms | POST /sms/send | Send a single SMS, optionally scheduled (schedule_at). |
| send_bulk_sms | POST /sms/bulk | Start an async bulk SMS job for up to 10,000 recipients — returns a job_id. |
| get_bulk_sms_job | GET /sms/bulk/{id} | Poll the status and progress of a bulk SMS job. |
| get_message | GET /messages/{id} | Get full details and delivery timeline for a single message. |
| get_credit_balance | GET /credits | Check current SMS and email credit balances. |
| verify_webhook_signature | — | Verify a webhook's X-MojaWave-Signature (HMAC-SHA256). |
Claude Desktop
Add this block to ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows), then restart Claude Desktop.
{
"mcpServers": {
"mojawave": {
"command": "mojawave-mcp",
"env": {
"MOJAWAVE_API_KEY": "sk_live_mw_xxxxxxxxxxxxxxxxxxxx"
}
}
}
}
Claude Code (CLI)
claude mcp add mojawave -- env MOJAWAVE_API_KEY=sk_live_mw_xxx mojawave-mcp
Cursor / Windsurf / any stdio MCP client
Most clients use the same JSON config format as Claude Desktop above — point the client at the mojawave-mcp command with your API key as an environment variable and refer to your client's MCP documentation.
OpenAI Agents SDK (ChatGPT / GPT-4o)
Start the server in SSE mode so OpenAI can reach it over HTTP:
MOJAWAVE_API_KEY=sk_live_mw_xxx mojawave-mcp --transport sse --port 8080
Then connect from Python:
from agents import Agent, Runner
from agents.mcp import MCPServerSse
async def main():
server = MCPServerSse(url="http://localhost:8080/sse")
async with server:
agent = Agent(
name="MojaWave Agent",
model="gpt-4o",
mcp_servers=[server],
)
result = await Runner.run(
agent, "Send an SMS to +255712345678 saying Hello from AI"
)
print(result.final_output)
Google Gemini (Google ADK)
Start the server in SSE mode (same command as above), then connect from Python:
from google.adk.agents import LlmAgent
from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset, SseServerParams
mojawave_tools = MCPToolset(
connection_params=SseServerParams(url="http://localhost:8080/sse")
)
agent = LlmAgent(
model="gemini-2.0-flash",
name="mojawave_agent",
instruction="You can send SMS and check credits via MojaWave.",
tools=[mojawave_tools],
)
Hosted deployment (Docker)
Run the SSE server behind a reverse proxy for production use:
FROM python:3.12-slim RUN pip install mojawave-mcp ENV MOJAWAVE_API_KEY="" EXPOSE 8080 CMD ["mojawave-mcp", "--transport", "sse", "--port", "8080"]
docker build -t mojawave-mcp . docker run -e MOJAWAVE_API_KEY=sk_live_mw_xxx -p 8080:8080 mojawave-mcp
sk_test_mw_…) during development — it returns synthetic responses without sending real messages or charging credits.
Webhooks — Overview & Events
MojaWave sends HTTP POST requests to your callback URL when events occur. All webhook bodies are JSON with a consistent envelope structure.
{
"id": "evt_3r9qhz7b1k",
"type": "message.delivered",
"created_at": "2026-04-05T12:05:01Z",
"livemode": true,
"data": {
"id": "89b82624-f1a2-4f5e-85b5-102e79a06779",
"status": "delivered",
"to": "+255753276939"
// ... full resource object
}
}
Webhooks — Signature Verification
Every webhook includes an X-MojaWave-Signature header containing an HMAC-SHA256 signature. Always verify this before processing events to protect against spoofed requests.
import hmac, hashlib def verify_signature(payload: bytes, sig: str, secret: str) -> bool: expected = hmac.new( secret.encode(), payload, hashlib.sha256 ).hexdigest() return hmac.compare_digest(expected, sig) # In your Flask/Django view: sig = request.headers.get("X-MojaWave-Signature") if not verify_signature(request.get_data(), sig, WEBHOOK_SECRET): return "Forbidden", 403 event = request.get_json() # Safe to process event
import { createHmac, timingSafeEqual } from 'crypto'; function verifySignature(payload, sig, secret) { const expected = createHmac('sha256', secret) .update(payload) .digest('hex'); return timingSafeEqual( Buffer.from(expected), Buffer.from(sig) ); } // Express middleware (raw body required) app.post('/webhooks', express.raw({ type: '*/*' }), (req, res) => { const sig = req.headers['x-mojawave-signature']; if (!verifySignature(req.body, sig, process.env.WEBHOOK_SECRET)) { return res.status(403).send('Forbidden'); } const event = JSON.parse(req.body); // Safe to process });
<?php $payload = file_get_contents('php://input'); $sig = $_SERVER['HTTP_X_MOJAWAVE_SIGNATURE'] ?? ''; $secret = getenv('WEBHOOK_SECRET'); $expected = hash_hmac('sha256', $payload, $secret); if (!hash_equals($expected, $sig)) { http_response_code(403); exit('Forbidden'); } $event = json_decode($payload, true); // Safe to process $event
Sandbox & Testing
The sandbox environment mirrors the live API exactly, but no real transactions or SMS are sent. Use your sk_test_mw_ key and set environment: "sandbox" in your SDK config.
Ready to build?
Get sandbox API keys and integrate in minutes. No credit card required.