MojaWave
Login Get Started →
Documentation
v1.0 · REST API

MojaWave API Reference

The MojaWave API lets you send SMS and transactional email — all from a single, unified REST interface.

Base URL: https://api.mojawave.com/v1
SMS API
Send single, bulk, and OTP messages across TZ networks.
4 endpoints →
Webhooks
Real-time event delivery with HMAC-SHA256 verification.
4 event types →
Sandbox
Test your integration with no real transactions or charges.
Test numbers →

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

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

Never expose your API key in client-side code. Use environment variables and server-side requests only.
Authorization header
Authorization: Bearer sk_live_mw_YOUR_KEY
API Key Types
PrefixEnvironmentUsage
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 StatusCodeMeaning
200Success
400invalid_requestMissing or malformed parameters
401unauthorizedInvalid or missing API key
402insufficient_balanceAccount balance too low
422unprocessableValidation failed on request body
429rate_limit_exceededToo many requests — back off and retry
500server_errorSomething 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 minute
  • X-RateLimit-Remaining — requests left in current window
  • X-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.

POST /v1/sms/send
Request Body Parameters application/json
ParameterTypeRequiredDescription
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.
Example Request Body
{
  "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"]
}
201 CreatedMessage Sent
{
  "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"
  }
}
Delivery receipts: Set a 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.

GET /v1/messages/{message_id}
Response Schema (data object)
FieldTypeDescription
iduuidUnique identifier for the message.
statusstringCurrent status (queued, sent, delivered, failed).
credits_costfloatCredits used for this message.
timelineobjectDelivery checkpoints: queued_at, sent_at, delivered_at.
failure_reasonstringHuman-readable reason if delivery failed.
200 OKMessage Details
{
  "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.

POST /v1/sms/bulk
Request body
{
  "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

JSON
{
  "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"
  }
}
Bulk sends using 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.

GET /v1/sms/bulk/{jobId}
FieldTypeDescription
statusstringqueued, processing, completed, failed
progress_percentfloatJob completion percentage (0-100).
sent_countintegerNumber of messages successfully sent.
total_credits_costfloatFinal total credits consumed by the job.
JSON Response
{
  "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.

GET /v1/credits
# 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)
Response Schema (per service object)
FieldTypeDescription
service_typestringsms or email
balanceintegerCurrent available credits.
total_purchasedintegerLifetime credits purchased.
total_consumedintegerLifetime credits consumed.
low_balance_thresholdintegerAlert fires when balance drops below this value.
is_low_balancebooleantrue if balance is at or below the threshold.
200 OKCredit Balances
{
  "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
    }
  }
}
Both Authorization: Bearer mw_live_xxx and X-API-Key: mw_live_xxx headers are accepted. Rate limited to 120 requests/min.

AI Integration · MCP

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

bash
pip install mojawave-mcp

Available tools

ToolAPI endpointWhat it does
send_smsPOST /sms/sendSend a single SMS, optionally scheduled (schedule_at).
send_bulk_smsPOST /sms/bulkStart an async bulk SMS job for up to 10,000 recipients — returns a job_id.
get_bulk_sms_jobGET /sms/bulk/{id}Poll the status and progress of a bulk SMS job.
get_messageGET /messages/{id}Get full details and delivery timeline for a single message.
get_credit_balanceGET /creditsCheck current SMS and email credit balances.
verify_webhook_signatureVerify 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.

json
{
  "mcpServers": {
    "mojawave": {
      "command": "mojawave-mcp",
      "env": {
        "MOJAWAVE_API_KEY": "sk_live_mw_xxxxxxxxxxxxxxxxxxxx"
      }
    }
  }
}

Claude Code (CLI)

bash
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:

bash
MOJAWAVE_API_KEY=sk_live_mw_xxx mojawave-mcp --transport sse --port 8080

Then connect from Python:

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:

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:

dockerfile
FROM python:3.12-slim
RUN pip install mojawave-mcp
ENV MOJAWAVE_API_KEY=""
EXPOSE 8080
CMD ["mojawave-mcp", "--transport", "sse", "--port", "8080"]
bash
docker build -t mojawave-mcp .
docker run -e MOJAWAVE_API_KEY=sk_live_mw_xxx -p 8080:8080 mojawave-mcp
Use a test key (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.

Webhook envelope
{
  "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
  }
}
message.sent
Message was accepted by the carrier.
message.delivered
Message was delivered to the recipient.
message.failed
Message delivery failed permanently.
credits.low
Credit balance dropped below threshold.

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.

Verify signatures using the raw request body before any JSON parsing. Parsing first may alter whitespace and invalidate the signature check.
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.

Sandbox webhooks fire within 2–5 seconds of the API call. You can use webhook.site or ngrok to receive them locally during development.

Ready to build?

Get sandbox API keys and integrate in minutes. No credit card required.