v3 Messaging API · v3.1.0

Communications API Documentation

Integrate SMS, WhatsApp, contacts, and balance workflows with the Momo Business API and complete examples.

OpenAPI JSON
01Authenticate
02Send a request
03Track the result

Authentication

How authentication works (token-based)

GET/api/v3/_auth

Every v3 API request must include an `Authorization: Bearer <token>` header. Tokens are tenant-scoped — a token can only act on resources belonging to the tenant that created it. ## 1. Create a token Log in to your dashboard, open **Settings → API Keys**, click **Create token**, and copy the token value (shown only once). Tokens have an optional expiry; if omitted, they don't expire. ## 2. Make your first request Use the token in the `Authorization` header. Verify it works by hitting `GET /api/v3/me`: ```bash curl https://business.momo.tz/api/v3/me \ -H 'Authorization: Bearer YOUR_TOKEN' ``` ## 3. Token scopes All v3 tokens have full read/write access on the tenant's resources today. Per-endpoint scoping is on the roadmap; in the meantime, treat tokens as full credentials and rotate them if leaked. ## 4. Rate limiting - SMS / WhatsApp send: **60 req/min** per tenant - Read endpoints: **120 req/min** - Campaign creation: **10 req/min** Responses include `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` headers. A `429` means you've hit the cap — back off and retry after the reset. ## 5. Error handling All errors follow the envelope `{ "status": "error", "message": "...", "errors": { ... } }`. Validation errors come back as `422` with `errors` keyed by field name.

CURL
curl -X GET '/api/v3/_auth' \
  -H 'Authorization: Bearer YOUR_API_TOKEN' \
  -H 'Accept: application/json'
NODE
const axios = require('axios')

const response = await axios({
    method: 'get',
    url: '/api/v3/_auth',
    headers: {
      Authorization: 'Bearer YOUR_API_TOKEN',
      Accept: 'application/json'
    }
})

console.log(response.data)
PYTHON
import requests

response = requests.get(
    "/api/v3/_auth",
    headers={
        "Authorization": "Bearer YOUR_API_TOKEN",
        "Accept": "application/json"
    }
)

print(response.json())
PHP
$client = new \GuzzleHttp\Client();

$response = $client->request('GET', '/api/v3/_auth', [
    'headers' => [
        'Authorization' => 'Bearer YOUR_API_TOKEN',
        'Accept'        => 'application/json',
    ],
]);

$data = json_decode($response->getBody(), true);
RUBY
require 'net/http'
require 'json'
require 'uri'

uri = URI('/api/v3/_auth')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'

request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer YOUR_API_TOKEN'
request['Accept'] = 'application/json'

response = http.request(request)
puts JSON.parse(response.body)
GO
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	req, _ := http.NewRequest("GET", "/api/v3/_auth", nil)
	req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
	req.Header.Set("Accept", "application/json")

	client := &http.Client{}
	resp, _ := client.Do(req)
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(string(body))
}

Responses

200 Probe endpoint — responds with the calling tenant's details when the token is valid.
{
    "status": "success",
    "data": {
        "id": 12,
        "uid": "tnt_01JXYZTENANT",
        "name": "Acme Communications",
        "email": "ops@acme.example.com",
        "default_currency": "TZS"
    }
}
401 Token missing, invalid, or expired.
{
    "status": "error",
    "message": "Invalid API token."
}

SMS

Send an SMS

POST/api/v3/sms/send

Queues one or many SMS messages using legacy-compatible fields.

Request body

FieldTypeRequiredDescription
recipientstringYesOne or more recipients, comma-separated (e.g. 255700111222 or 255700111222,255700111223).
recipientsarrayNoAlternative to recipient: array of phone numbers.
sender_idstringNoOptional approved sender ID, tenant-owned SMS-capable number, or active short code. Unknown or ambiguous identities are rejected.
typestringNoMessage type (e.g. plain).
messagestringYesSMS body text.
tenant_channel_idintegerNoDeprecated compatibility input. Ignored; routing is resolved automatically from sender_id and account/system defaults.
schedule_timestringNoOptional ISO datetime for scheduled send.
CURL
curl -X POST '/api/v3/sms/send' \
  -H 'Authorization: Bearer YOUR_API_TOKEN' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
    "recipient": "255700111222",
    "sender_id": "MyBrand",
    "message": "Hello from Momo Business \u2014 your verification code is 4821."
}'
NODE
const axios = require('axios')

const response = await axios({
    method: 'post',
    url: '/api/v3/sms/send',
    headers: {
      Authorization: 'Bearer YOUR_API_TOKEN',
      Accept: 'application/json',
      'Content-Type': 'application/json'
    },
    data: {
    "recipient": "255700111222",
    "sender_id": "MyBrand",
    "message": "Hello from Momo Business \u2014 your verification code is 4821."
}
})

console.log(response.data)
PYTHON
import requests

response = requests.post(
    "/api/v3/sms/send",
    headers={
        "Authorization": "Bearer YOUR_API_TOKEN",
        "Accept": "application/json",
        "Content-Type": "application/json"
    },
    json={
    "recipient": "255700111222",
    "sender_id": "MyBrand",
    "message": "Hello from Momo Business \u2014 your verification code is 4821."
}
)

print(response.json())
PHP
$client = new \GuzzleHttp\Client();

$response = $client->request('POST', '/api/v3/sms/send', [
    'headers' => [
        'Authorization' => 'Bearer YOUR_API_TOKEN',
        'Accept'        => 'application/json',
        'Content-Type'  => 'application/json',
    ],
    'json' => {
    "recipient": "255700111222",
    "sender_id": "MyBrand",
    "message": "Hello from Momo Business \u2014 your verification code is 4821."
},
]);

$data = json_decode($response->getBody(), true);
RUBY
require 'net/http'
require 'json'
require 'uri'

uri = URI('/api/v3/sms/send')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'

request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer YOUR_API_TOKEN'
request['Accept'] = 'application/json'
request['Content-Type'] = 'application/json'
request.body = '{
    "recipient": "255700111222",
    "sender_id": "MyBrand",
    "message": "Hello from Momo Business \u2014 your verification code is 4821."
}'

response = http.request(request)
puts JSON.parse(response.body)
GO
package main

import (
	"bytes"
	"fmt"
	"io"
	"net/http"
)

func main() {
	req, _ := http.NewRequest("POST", "/api/v3/sms/send", bytes.NewBuffer([]byte(`{
    "recipient": "255700111222",
    "sender_id": "MyBrand",
    "message": "Hello from Momo Business \u2014 your verification code is 4821."
}`)))
	req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
	req.Header.Set("Accept", "application/json")
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{}
	resp, _ := client.Do(req)
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(string(body))
}

Responses

201 Messages queued. Each recipient yields one Message row; check `data.messages[].status` for delivery progression.
{
    "status": "success",
    "data": {
        "messages": [
            {
                "id": 101,
                "uid": "msg_01JXYZSMS01",
                "direction": "outbound",
                "channel_type": "sms",
                "sender": "MyBrand",
                "recipient": "255700111222",
                "body": "Hello from API v3",
                "status": "queued"
            }
        ]
    }
}
422 Validation error.
{
    "status": "error",
    "message": "At least one recipient is required.",
    "errors": {
        "recipient": [
            "Provide recipient or recipients."
        ]
    }
}
401 Unauthorized.
{
    "status": "error",
    "message": "Invalid API token."
}

SMS

Create an SMS campaign

POST/api/v3/sms/campaign

Creates one campaign per resolved contact list identifier and queues campaign processing.

Request body

FieldTypeRequiredDescription
contact_list_idstringYes
messagestringYes
sender_idstringNoOptional approved sender ID, tenant-owned SMS-capable number, or active short code.
tenant_channel_idintegerNoDeprecated compatibility input. Ignored; routing is resolved automatically from sender_id and account/system defaults.
schedule_timestringNo
namestringNo
CURL
curl -X POST '/api/v3/sms/campaign' \
  -H 'Authorization: Bearer YOUR_API_TOKEN' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
    "contact_list_id": "grp_01JXYZABC",
    "message": "Campaign message",
    "sender_id": "Brand"
}'
NODE
const axios = require('axios')

const response = await axios({
    method: 'post',
    url: '/api/v3/sms/campaign',
    headers: {
      Authorization: 'Bearer YOUR_API_TOKEN',
      Accept: 'application/json',
      'Content-Type': 'application/json'
    },
    data: {
    "contact_list_id": "grp_01JXYZABC",
    "message": "Campaign message",
    "sender_id": "Brand"
}
})

console.log(response.data)
PYTHON
import requests

response = requests.post(
    "/api/v3/sms/campaign",
    headers={
        "Authorization": "Bearer YOUR_API_TOKEN",
        "Accept": "application/json",
        "Content-Type": "application/json"
    },
    json={
    "contact_list_id": "grp_01JXYZABC",
    "message": "Campaign message",
    "sender_id": "Brand"
}
)

print(response.json())
PHP
$client = new \GuzzleHttp\Client();

$response = $client->request('POST', '/api/v3/sms/campaign', [
    'headers' => [
        'Authorization' => 'Bearer YOUR_API_TOKEN',
        'Accept'        => 'application/json',
        'Content-Type'  => 'application/json',
    ],
    'json' => {
    "contact_list_id": "grp_01JXYZABC",
    "message": "Campaign message",
    "sender_id": "Brand"
},
]);

$data = json_decode($response->getBody(), true);
RUBY
require 'net/http'
require 'json'
require 'uri'

uri = URI('/api/v3/sms/campaign')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'

request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer YOUR_API_TOKEN'
request['Accept'] = 'application/json'
request['Content-Type'] = 'application/json'
request.body = '{
    "contact_list_id": "grp_01JXYZABC",
    "message": "Campaign message",
    "sender_id": "Brand"
}'

response = http.request(request)
puts JSON.parse(response.body)
GO
package main

import (
	"bytes"
	"fmt"
	"io"
	"net/http"
)

func main() {
	req, _ := http.NewRequest("POST", "/api/v3/sms/campaign", bytes.NewBuffer([]byte(`{
    "contact_list_id": "grp_01JXYZABC",
    "message": "Campaign message",
    "sender_id": "Brand"
}`)))
	req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
	req.Header.Set("Accept", "application/json")
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{}
	resp, _ := client.Do(req)
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(string(body))
}

Responses

201 Campaigns created.
{
    "status": "success",
    "data": {
        "campaigns": [
            {
                "id": 15,
                "uid": "cmp_01JXYZ001",
                "name": "API Campaign - VIP List",
                "status": "draft",
                "channel_type": "sms",
                "message": "Campaign message"
            }
        ]
    }
}
422 Validation error.
{
    "status": "error",
    "message": "contact_list_id must contain at least one group id."
}
401 Unauthorized.
{
    "status": "error",
    "message": "Invalid API token."
}

SMS

List SMS messages

GET/api/v3/sms

Returns tenant-scoped SMS message logs with pagination.

Parameters

NameInTypeRequiredDescription
statusquerystringNo
directionquerystringNo
limitqueryintegerNo
per_pagequeryintegerNo
CURL
curl -X GET '/api/v3/sms' \
  -H 'Authorization: Bearer YOUR_API_TOKEN' \
  -H 'Accept: application/json'
NODE
const axios = require('axios')

const response = await axios({
    method: 'get',
    url: '/api/v3/sms',
    headers: {
      Authorization: 'Bearer YOUR_API_TOKEN',
      Accept: 'application/json'
    }
})

console.log(response.data)
PYTHON
import requests

response = requests.get(
    "/api/v3/sms",
    headers={
        "Authorization": "Bearer YOUR_API_TOKEN",
        "Accept": "application/json"
    }
)

print(response.json())
PHP
$client = new \GuzzleHttp\Client();

$response = $client->request('GET', '/api/v3/sms', [
    'headers' => [
        'Authorization' => 'Bearer YOUR_API_TOKEN',
        'Accept'        => 'application/json',
    ],
]);

$data = json_decode($response->getBody(), true);
RUBY
require 'net/http'
require 'json'
require 'uri'

uri = URI('/api/v3/sms')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'

request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer YOUR_API_TOKEN'
request['Accept'] = 'application/json'

response = http.request(request)
puts JSON.parse(response.body)
GO
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	req, _ := http.NewRequest("GET", "/api/v3/sms", nil)
	req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
	req.Header.Set("Accept", "application/json")

	client := &http.Client{}
	resp, _ := client.Do(req)
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(string(body))
}

Responses

200 SMS collection.
{
    "status": "success",
    "data": {
        "items": [
            {
                "id": 101,
                "uid": "msg_01JXYZSMS01",
                "direction": "outbound",
                "channel_type": "sms",
                "recipient": "255700111222",
                "body": "Hello from API v3",
                "status": "queued"
            }
        ],
        "pagination": {
            "current_page": 1,
            "per_page": 20,
            "last_page": 1,
            "total": 1,
            "has_more_pages": false
        }
    }
}
401 Unauthorized.
{
    "status": "error",
    "message": "Missing bearer token."
}

SMS

Get an SMS message

GET/api/v3/sms/{uid}

Fetches one SMS message by public uid with numeric id fallback.

Parameters

NameInTypeRequiredDescription
uidpathstringYes
CURL
curl -X GET '/api/v3/sms/{uid}' \
  -H 'Authorization: Bearer YOUR_API_TOKEN' \
  -H 'Accept: application/json'
NODE
const axios = require('axios')

const response = await axios({
    method: 'get',
    url: '/api/v3/sms/{uid}',
    headers: {
      Authorization: 'Bearer YOUR_API_TOKEN',
      Accept: 'application/json'
    }
})

console.log(response.data)
PYTHON
import requests

response = requests.get(
    "/api/v3/sms/{uid}",
    headers={
        "Authorization": "Bearer YOUR_API_TOKEN",
        "Accept": "application/json"
    }
)

print(response.json())
PHP
$client = new \GuzzleHttp\Client();

$response = $client->request('GET', '/api/v3/sms/{uid}', [
    'headers' => [
        'Authorization' => 'Bearer YOUR_API_TOKEN',
        'Accept'        => 'application/json',
    ],
]);

$data = json_decode($response->getBody(), true);
RUBY
require 'net/http'
require 'json'
require 'uri'

uri = URI('/api/v3/sms/{uid}')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'

request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer YOUR_API_TOKEN'
request['Accept'] = 'application/json'

response = http.request(request)
puts JSON.parse(response.body)
GO
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	req, _ := http.NewRequest("GET", "/api/v3/sms/{uid}", nil)
	req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
	req.Header.Set("Accept", "application/json")

	client := &http.Client{}
	resp, _ := client.Do(req)
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(string(body))
}

Responses

200 Single SMS message.
{
    "status": "success",
    "data": {
        "id": 101,
        "uid": "msg_01JXYZSMS01",
        "direction": "outbound",
        "channel_type": "sms",
        "recipient": "255700111222",
        "body": "Hello from API v3",
        "status": "delivered"
    }
}
404 Message not found.
{
    "status": "error",
    "message": "Message not found."
}

SMS

View one campaign

GET/api/v3/campaign/{uid}/view

Retrieves one SMS campaign by uid.

Parameters

NameInTypeRequiredDescription
uidpathstringYes
CURL
curl -X GET '/api/v3/campaign/{uid}/view' \
  -H 'Authorization: Bearer YOUR_API_TOKEN' \
  -H 'Accept: application/json'
NODE
const axios = require('axios')

const response = await axios({
    method: 'get',
    url: '/api/v3/campaign/{uid}/view',
    headers: {
      Authorization: 'Bearer YOUR_API_TOKEN',
      Accept: 'application/json'
    }
})

console.log(response.data)
PYTHON
import requests

response = requests.get(
    "/api/v3/campaign/{uid}/view",
    headers={
        "Authorization": "Bearer YOUR_API_TOKEN",
        "Accept": "application/json"
    }
)

print(response.json())
PHP
$client = new \GuzzleHttp\Client();

$response = $client->request('GET', '/api/v3/campaign/{uid}/view', [
    'headers' => [
        'Authorization' => 'Bearer YOUR_API_TOKEN',
        'Accept'        => 'application/json',
    ],
]);

$data = json_decode($response->getBody(), true);
RUBY
require 'net/http'
require 'json'
require 'uri'

uri = URI('/api/v3/campaign/{uid}/view')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'

request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer YOUR_API_TOKEN'
request['Accept'] = 'application/json'

response = http.request(request)
puts JSON.parse(response.body)
GO
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	req, _ := http.NewRequest("GET", "/api/v3/campaign/{uid}/view", nil)
	req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
	req.Header.Set("Accept", "application/json")

	client := &http.Client{}
	resp, _ := client.Do(req)
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(string(body))
}

Responses

200 Campaign details.
{
    "status": "success",
    "data": {
        "id": 15,
        "uid": "cmp_01JXYZ001",
        "name": "API Campaign - VIP List",
        "status": "running",
        "channel_type": "sms",
        "message": "Campaign message"
    }
}
404 Campaign not found.
{
    "status": "error",
    "message": "Campaign not found."
}

WhatsApp

Send a WhatsApp message

POST/api/v3/whatsapp/send

Unified send supporting text, media, template, interactive, and reaction payloads.

Request body

FieldTypeRequiredDescription
recipientstringYes
recipientsarrayNo
messagestringNo
bodystringNo
message_typestringNo
typestringNo
media_urlstringNo
media_typestringNo
templateobjectNo
interactiveobjectNo
reactionobjectNo
in_reply_to_gateway_idstringNo
tenant_channel_idintegerNoDeprecated compatibility input. Ignored; routing is resolved automatically from the endpoint type, sending identity, and account/system defaults.
CURL
curl -X POST '/api/v3/whatsapp/send' \
  -H 'Authorization: Bearer YOUR_API_TOKEN' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
    "recipient": "255700111222",
    "message": "Hello from the API"
}'
NODE
const axios = require('axios')

const response = await axios({
    method: 'post',
    url: '/api/v3/whatsapp/send',
    headers: {
      Authorization: 'Bearer YOUR_API_TOKEN',
      Accept: 'application/json',
      'Content-Type': 'application/json'
    },
    data: {
    "recipient": "255700111222",
    "message": "Hello from the API"
}
})

console.log(response.data)
PYTHON
import requests

response = requests.post(
    "/api/v3/whatsapp/send",
    headers={
        "Authorization": "Bearer YOUR_API_TOKEN",
        "Accept": "application/json",
        "Content-Type": "application/json"
    },
    json={
    "recipient": "255700111222",
    "message": "Hello from the API"
}
)

print(response.json())
PHP
$client = new \GuzzleHttp\Client();

$response = $client->request('POST', '/api/v3/whatsapp/send', [
    'headers' => [
        'Authorization' => 'Bearer YOUR_API_TOKEN',
        'Accept'        => 'application/json',
        'Content-Type'  => 'application/json',
    ],
    'json' => {
    "recipient": "255700111222",
    "message": "Hello from the API"
},
]);

$data = json_decode($response->getBody(), true);
RUBY
require 'net/http'
require 'json'
require 'uri'

uri = URI('/api/v3/whatsapp/send')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'

request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer YOUR_API_TOKEN'
request['Accept'] = 'application/json'
request['Content-Type'] = 'application/json'
request.body = '{
    "recipient": "255700111222",
    "message": "Hello from the API"
}'

response = http.request(request)
puts JSON.parse(response.body)
GO
package main

import (
	"bytes"
	"fmt"
	"io"
	"net/http"
)

func main() {
	req, _ := http.NewRequest("POST", "/api/v3/whatsapp/send", bytes.NewBuffer([]byte(`{
    "recipient": "255700111222",
    "message": "Hello from the API"
}`)))
	req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
	req.Header.Set("Accept", "application/json")
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{}
	resp, _ := client.Do(req)
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(string(body))
}

Responses

201 Messages queued.
{
    "status": "success",
    "data": {
        "messages": [
            {
                "id": 300,
                "uid": "msg_01JXYZWA01",
                "direction": "outbound",
                "channel_type": "whatsapp",
                "recipient": "255700111222",
                "body": "Interactive message",
                "status": "queued",
                "metadata": {
                    "interactive": {
                        "type": "button"
                    }
                }
            }
        ]
    }
}
422 Validation or payload combination error.
{
    "status": "error",
    "message": "Reaction cannot be combined with text, media, template, or interactive payload."
}
401 Unauthorized.
{
    "status": "error",
    "message": "Invalid API token."
}

WhatsApp

List WhatsApp messages

GET/api/v3/whatsapp

Returns tenant-scoped WhatsApp message logs with pagination.

Parameters

NameInTypeRequiredDescription
statusquerystringNo
directionquerystringNo
limitqueryintegerNo
per_pagequeryintegerNo
CURL
curl -X GET '/api/v3/whatsapp' \
  -H 'Authorization: Bearer YOUR_API_TOKEN' \
  -H 'Accept: application/json'
NODE
const axios = require('axios')

const response = await axios({
    method: 'get',
    url: '/api/v3/whatsapp',
    headers: {
      Authorization: 'Bearer YOUR_API_TOKEN',
      Accept: 'application/json'
    }
})

console.log(response.data)
PYTHON
import requests

response = requests.get(
    "/api/v3/whatsapp",
    headers={
        "Authorization": "Bearer YOUR_API_TOKEN",
        "Accept": "application/json"
    }
)

print(response.json())
PHP
$client = new \GuzzleHttp\Client();

$response = $client->request('GET', '/api/v3/whatsapp', [
    'headers' => [
        'Authorization' => 'Bearer YOUR_API_TOKEN',
        'Accept'        => 'application/json',
    ],
]);

$data = json_decode($response->getBody(), true);
RUBY
require 'net/http'
require 'json'
require 'uri'

uri = URI('/api/v3/whatsapp')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'

request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer YOUR_API_TOKEN'
request['Accept'] = 'application/json'

response = http.request(request)
puts JSON.parse(response.body)
GO
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	req, _ := http.NewRequest("GET", "/api/v3/whatsapp", nil)
	req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
	req.Header.Set("Accept", "application/json")

	client := &http.Client{}
	resp, _ := client.Do(req)
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(string(body))
}

Responses

200 WhatsApp collection.
{
    "status": "success",
    "data": {
        "items": [
            {
                "id": 300,
                "uid": "msg_01JXYZWA01",
                "direction": "outbound",
                "channel_type": "whatsapp",
                "recipient": "255700111222",
                "body": "Interactive message",
                "status": "queued"
            }
        ],
        "pagination": {
            "current_page": 1,
            "per_page": 20,
            "last_page": 1,
            "total": 1,
            "has_more_pages": false
        }
    }
}
401 Unauthorized.
{
    "status": "error",
    "message": "Missing bearer token."
}

WhatsApp

Get a WhatsApp message

GET/api/v3/whatsapp/{uid}

Fetches one WhatsApp message by public uid.

Parameters

NameInTypeRequiredDescription
uidpathstringYes
CURL
curl -X GET '/api/v3/whatsapp/{uid}' \
  -H 'Authorization: Bearer YOUR_API_TOKEN' \
  -H 'Accept: application/json'
NODE
const axios = require('axios')

const response = await axios({
    method: 'get',
    url: '/api/v3/whatsapp/{uid}',
    headers: {
      Authorization: 'Bearer YOUR_API_TOKEN',
      Accept: 'application/json'
    }
})

console.log(response.data)
PYTHON
import requests

response = requests.get(
    "/api/v3/whatsapp/{uid}",
    headers={
        "Authorization": "Bearer YOUR_API_TOKEN",
        "Accept": "application/json"
    }
)

print(response.json())
PHP
$client = new \GuzzleHttp\Client();

$response = $client->request('GET', '/api/v3/whatsapp/{uid}', [
    'headers' => [
        'Authorization' => 'Bearer YOUR_API_TOKEN',
        'Accept'        => 'application/json',
    ],
]);

$data = json_decode($response->getBody(), true);
RUBY
require 'net/http'
require 'json'
require 'uri'

uri = URI('/api/v3/whatsapp/{uid}')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'

request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer YOUR_API_TOKEN'
request['Accept'] = 'application/json'

response = http.request(request)
puts JSON.parse(response.body)
GO
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	req, _ := http.NewRequest("GET", "/api/v3/whatsapp/{uid}", nil)
	req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
	req.Header.Set("Accept", "application/json")

	client := &http.Client{}
	resp, _ := client.Do(req)
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(string(body))
}

Responses

200 Single WhatsApp message.
{
    "status": "success",
    "data": {
        "id": 300,
        "uid": "msg_01JXYZWA01",
        "direction": "outbound",
        "channel_type": "whatsapp",
        "recipient": "255700111222",
        "body": "Interactive message",
        "status": "delivered"
    }
}
404 Message not found.
{
    "status": "error",
    "message": "Message not found."
}

Contacts

Create a contact

POST/api/v3/contacts/{group_id}/store

Stores one contact in a group using legacy fields and custom dynamic attributes.

Parameters

NameInTypeRequiredDescription
group_idpathstringYes

Request body

FieldTypeRequiredDescription
PHONEstringYes
country_codestringNo
namestringNo
FIRST_NAMEstringNo
LAST_NAMEstringNo
is_subscribedbooleanNo
CURL
curl -X POST '/api/v3/contacts/{group_id}/store' \
  -H 'Authorization: Bearer YOUR_API_TOKEN' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
    "PHONE": "255700333444"
}'
NODE
const axios = require('axios')

const response = await axios({
    method: 'post',
    url: '/api/v3/contacts/{group_id}/store',
    headers: {
      Authorization: 'Bearer YOUR_API_TOKEN',
      Accept: 'application/json',
      'Content-Type': 'application/json'
    },
    data: {
    "PHONE": "255700333444"
}
})

console.log(response.data)
PYTHON
import requests

response = requests.post(
    "/api/v3/contacts/{group_id}/store",
    headers={
        "Authorization": "Bearer YOUR_API_TOKEN",
        "Accept": "application/json",
        "Content-Type": "application/json"
    },
    json={
    "PHONE": "255700333444"
}
)

print(response.json())
PHP
$client = new \GuzzleHttp\Client();

$response = $client->request('POST', '/api/v3/contacts/{group_id}/store', [
    'headers' => [
        'Authorization' => 'Bearer YOUR_API_TOKEN',
        'Accept'        => 'application/json',
        'Content-Type'  => 'application/json',
    ],
    'json' => {
    "PHONE": "255700333444"
},
]);

$data = json_decode($response->getBody(), true);
RUBY
require 'net/http'
require 'json'
require 'uri'

uri = URI('/api/v3/contacts/{group_id}/store')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'

request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer YOUR_API_TOKEN'
request['Accept'] = 'application/json'
request['Content-Type'] = 'application/json'
request.body = '{
    "PHONE": "255700333444"
}'

response = http.request(request)
puts JSON.parse(response.body)
GO
package main

import (
	"bytes"
	"fmt"
	"io"
	"net/http"
)

func main() {
	req, _ := http.NewRequest("POST", "/api/v3/contacts/{group_id}/store", bytes.NewBuffer([]byte(`{
    "PHONE": "255700333444"
}`)))
	req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
	req.Header.Set("Accept", "application/json")
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{}
	resp, _ := client.Do(req)
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(string(body))
}

Responses

201 Contact created. Phone numbers are normalised to E.164 (international) format and de-duplicated within the group — re-posting the same PHONE returns the existing row.
{
    "status": "success",
    "data": {
        "id": 66,
        "uid": "ctc_01JXYZ001",
        "group_id": 8,
        "group_uid": "grp_01JXYZABC",
        "name": "John Doe",
        "country_code": "255",
        "phone_number": "700333444",
        "full_phone_number": "255700333444",
        "is_subscribed": true,
        "custom_field_values": {
            "CITY": "Dar es Salaam"
        }
    }
}
404 Group not found.
{
    "status": "error",
    "message": "Contact group not found."
}
422 Validation error.
{
    "status": "error",
    "message": "Validation failed.",
    "errors": {
        "PHONE": [
            "The PHONE field is required."
        ]
    }
}

Contacts

Find a contact

POST/api/v3/contacts/{group_id}/search/{uid}

Finds a single contact in a group by public uid.

Parameters

NameInTypeRequiredDescription
group_idpathstringYes
uidpathstringYes
CURL
curl -X POST '/api/v3/contacts/{group_id}/search/{uid}' \
  -H 'Authorization: Bearer YOUR_API_TOKEN' \
  -H 'Accept: application/json'
NODE
const axios = require('axios')

const response = await axios({
    method: 'post',
    url: '/api/v3/contacts/{group_id}/search/{uid}',
    headers: {
      Authorization: 'Bearer YOUR_API_TOKEN',
      Accept: 'application/json'
    }
})

console.log(response.data)
PYTHON
import requests

response = requests.post(
    "/api/v3/contacts/{group_id}/search/{uid}",
    headers={
        "Authorization": "Bearer YOUR_API_TOKEN",
        "Accept": "application/json"
    }
)

print(response.json())
PHP
$client = new \GuzzleHttp\Client();

$response = $client->request('POST', '/api/v3/contacts/{group_id}/search/{uid}', [
    'headers' => [
        'Authorization' => 'Bearer YOUR_API_TOKEN',
        'Accept'        => 'application/json',
    ],
]);

$data = json_decode($response->getBody(), true);
RUBY
require 'net/http'
require 'json'
require 'uri'

uri = URI('/api/v3/contacts/{group_id}/search/{uid}')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'

request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer YOUR_API_TOKEN'
request['Accept'] = 'application/json'

response = http.request(request)
puts JSON.parse(response.body)
GO
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	req, _ := http.NewRequest("POST", "/api/v3/contacts/{group_id}/search/{uid}", nil)
	req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
	req.Header.Set("Accept", "application/json")

	client := &http.Client{}
	resp, _ := client.Do(req)
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(string(body))
}

Responses

200 Single contact.
{
    "status": "success",
    "data": {
        "id": 66,
        "uid": "ctc_01JXYZ001",
        "group_id": 8,
        "group_uid": "grp_01JXYZABC",
        "name": "John Doe",
        "country_code": "255",
        "phone_number": "700333444",
        "full_phone_number": "255700333444",
        "is_subscribed": true,
        "custom_field_values": {
            "CITY": "Dar es Salaam"
        }
    }
}
404 Contact not found.
{
    "status": "error",
    "message": "Contact not found."
}

Contacts

Update a contact

PATCH/api/v3/contacts/{group_id}/update/{uid}

Updates a contact record in a group with the same payload conventions as create.

Parameters

NameInTypeRequiredDescription
group_idpathstringYes
uidpathstringYes

Request body

FieldTypeRequiredDescription
PHONEstringYes
country_codestringNo
namestringNo
is_subscribedbooleanNo
CURL
curl -X PATCH '/api/v3/contacts/{group_id}/update/{uid}' \
  -H 'Authorization: Bearer YOUR_API_TOKEN' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
    "PHONE": "255700333444",
    "name": "John Updated"
}'
NODE
const axios = require('axios')

const response = await axios({
    method: 'patch',
    url: '/api/v3/contacts/{group_id}/update/{uid}',
    headers: {
      Authorization: 'Bearer YOUR_API_TOKEN',
      Accept: 'application/json',
      'Content-Type': 'application/json'
    },
    data: {
    "PHONE": "255700333444",
    "name": "John Updated"
}
})

console.log(response.data)
PYTHON
import requests

response = requests.patch(
    "/api/v3/contacts/{group_id}/update/{uid}",
    headers={
        "Authorization": "Bearer YOUR_API_TOKEN",
        "Accept": "application/json",
        "Content-Type": "application/json"
    },
    json={
    "PHONE": "255700333444",
    "name": "John Updated"
}
)

print(response.json())
PHP
$client = new \GuzzleHttp\Client();

$response = $client->request('PATCH', '/api/v3/contacts/{group_id}/update/{uid}', [
    'headers' => [
        'Authorization' => 'Bearer YOUR_API_TOKEN',
        'Accept'        => 'application/json',
        'Content-Type'  => 'application/json',
    ],
    'json' => {
    "PHONE": "255700333444",
    "name": "John Updated"
},
]);

$data = json_decode($response->getBody(), true);
RUBY
require 'net/http'
require 'json'
require 'uri'

uri = URI('/api/v3/contacts/{group_id}/update/{uid}')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'

request = Net::HTTP::Patch.new(uri)
request['Authorization'] = 'Bearer YOUR_API_TOKEN'
request['Accept'] = 'application/json'
request['Content-Type'] = 'application/json'
request.body = '{
    "PHONE": "255700333444",
    "name": "John Updated"
}'

response = http.request(request)
puts JSON.parse(response.body)
GO
package main

import (
	"bytes"
	"fmt"
	"io"
	"net/http"
)

func main() {
	req, _ := http.NewRequest("PATCH", "/api/v3/contacts/{group_id}/update/{uid}", bytes.NewBuffer([]byte(`{
    "PHONE": "255700333444",
    "name": "John Updated"
}`)))
	req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
	req.Header.Set("Accept", "application/json")
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{}
	resp, _ := client.Do(req)
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(string(body))
}

Responses

200 Contact updated.
{
    "status": "success",
    "data": {
        "id": 66,
        "uid": "ctc_01JXYZ001",
        "group_id": 8,
        "group_uid": "grp_01JXYZABC",
        "name": "John Updated",
        "country_code": "255",
        "phone_number": "700333444",
        "full_phone_number": "255700333444",
        "is_subscribed": true,
        "custom_field_values": []
    }
}
422 Validation error.
{
    "status": "error",
    "message": "Validation failed.",
    "errors": {
        "PHONE": [
            "The PHONE field is required."
        ]
    }
}
404 Contact or group not found.
{
    "status": "error",
    "message": "Contact not found."
}

Contacts

Delete a contact

DELETE/api/v3/contacts/{group_id}/delete/{uid}

Deletes one contact by uid within a contact group.

Parameters

NameInTypeRequiredDescription
group_idpathstringYes
uidpathstringYes
CURL
curl -X DELETE '/api/v3/contacts/{group_id}/delete/{uid}' \
  -H 'Authorization: Bearer YOUR_API_TOKEN' \
  -H 'Accept: application/json'
NODE
const axios = require('axios')

const response = await axios({
    method: 'delete',
    url: '/api/v3/contacts/{group_id}/delete/{uid}',
    headers: {
      Authorization: 'Bearer YOUR_API_TOKEN',
      Accept: 'application/json'
    }
})

console.log(response.data)
PYTHON
import requests

response = requests.delete(
    "/api/v3/contacts/{group_id}/delete/{uid}",
    headers={
        "Authorization": "Bearer YOUR_API_TOKEN",
        "Accept": "application/json"
    }
)

print(response.json())
PHP
$client = new \GuzzleHttp\Client();

$response = $client->request('DELETE', '/api/v3/contacts/{group_id}/delete/{uid}', [
    'headers' => [
        'Authorization' => 'Bearer YOUR_API_TOKEN',
        'Accept'        => 'application/json',
    ],
]);

$data = json_decode($response->getBody(), true);
RUBY
require 'net/http'
require 'json'
require 'uri'

uri = URI('/api/v3/contacts/{group_id}/delete/{uid}')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'

request = Net::HTTP::Delete.new(uri)
request['Authorization'] = 'Bearer YOUR_API_TOKEN'
request['Accept'] = 'application/json'

response = http.request(request)
puts JSON.parse(response.body)
GO
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	req, _ := http.NewRequest("DELETE", "/api/v3/contacts/{group_id}/delete/{uid}", nil)
	req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
	req.Header.Set("Accept", "application/json")

	client := &http.Client{}
	resp, _ := client.Do(req)
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(string(body))
}

Responses

200 Contact deleted.
{
    "status": "success",
    "data": {
        "deleted": true,
        "uid": "ctc_01JXYZ001"
    }
}
404 Contact or group not found.
{
    "status": "error",
    "message": "Contact not found."
}

Contacts

List contacts in a group

POST/api/v3/contacts/{group_id}/all

Lists contacts by group with optional search and pagination controls.

Parameters

NameInTypeRequiredDescription
group_idpathstringYes

Request body

FieldTypeRequiredDescription
searchstringNo
limitintegerNo
per_pageintegerNo
CURL
curl -X POST '/api/v3/contacts/{group_id}/all' \
  -H 'Authorization: Bearer YOUR_API_TOKEN' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
    "limit": 25
}'
NODE
const axios = require('axios')

const response = await axios({
    method: 'post',
    url: '/api/v3/contacts/{group_id}/all',
    headers: {
      Authorization: 'Bearer YOUR_API_TOKEN',
      Accept: 'application/json',
      'Content-Type': 'application/json'
    },
    data: {
    "limit": 25
}
})

console.log(response.data)
PYTHON
import requests

response = requests.post(
    "/api/v3/contacts/{group_id}/all",
    headers={
        "Authorization": "Bearer YOUR_API_TOKEN",
        "Accept": "application/json",
        "Content-Type": "application/json"
    },
    json={
    "limit": 25
}
)

print(response.json())
PHP
$client = new \GuzzleHttp\Client();

$response = $client->request('POST', '/api/v3/contacts/{group_id}/all', [
    'headers' => [
        'Authorization' => 'Bearer YOUR_API_TOKEN',
        'Accept'        => 'application/json',
        'Content-Type'  => 'application/json',
    ],
    'json' => {
    "limit": 25
},
]);

$data = json_decode($response->getBody(), true);
RUBY
require 'net/http'
require 'json'
require 'uri'

uri = URI('/api/v3/contacts/{group_id}/all')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'

request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer YOUR_API_TOKEN'
request['Accept'] = 'application/json'
request['Content-Type'] = 'application/json'
request.body = '{
    "limit": 25
}'

response = http.request(request)
puts JSON.parse(response.body)
GO
package main

import (
	"bytes"
	"fmt"
	"io"
	"net/http"
)

func main() {
	req, _ := http.NewRequest("POST", "/api/v3/contacts/{group_id}/all", bytes.NewBuffer([]byte(`{
    "limit": 25
}`)))
	req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
	req.Header.Set("Accept", "application/json")
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{}
	resp, _ := client.Do(req)
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(string(body))
}

Responses

200 Contact collection. Phone numbers are returned in two parts: `country_code` + `phone_number` (local), and a pre-joined `full_phone_number`.
{
    "status": "success",
    "data": {
        "items": [
            {
                "id": 66,
                "uid": "ctc_01JXYZ001",
                "group_id": 8,
                "group_uid": "grp_01JXYZABC",
                "name": "John Doe",
                "country_code": "255",
                "phone_number": "700333444",
                "full_phone_number": "255700333444",
                "is_subscribed": true,
                "custom_field_values": {
                    "CITY": "Dar es Salaam"
                }
            }
        ],
        "pagination": {
            "current_page": 1,
            "per_page": 25,
            "last_page": 1,
            "total": 1,
            "has_more_pages": false
        }
    }
}
404 Group not found.
{
    "status": "error",
    "message": "Contact group not found."
}

Profile & Balance

Get current account

GET/api/v3/me

Returns the tenant profile represented by the bearer token.

CURL
curl -X GET '/api/v3/me' \
  -H 'Authorization: Bearer YOUR_API_TOKEN' \
  -H 'Accept: application/json'
NODE
const axios = require('axios')

const response = await axios({
    method: 'get',
    url: '/api/v3/me',
    headers: {
      Authorization: 'Bearer YOUR_API_TOKEN',
      Accept: 'application/json'
    }
})

console.log(response.data)
PYTHON
import requests

response = requests.get(
    "/api/v3/me",
    headers={
        "Authorization": "Bearer YOUR_API_TOKEN",
        "Accept": "application/json"
    }
)

print(response.json())
PHP
$client = new \GuzzleHttp\Client();

$response = $client->request('GET', '/api/v3/me', [
    'headers' => [
        'Authorization' => 'Bearer YOUR_API_TOKEN',
        'Accept'        => 'application/json',
    ],
]);

$data = json_decode($response->getBody(), true);
RUBY
require 'net/http'
require 'json'
require 'uri'

uri = URI('/api/v3/me')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'

request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer YOUR_API_TOKEN'
request['Accept'] = 'application/json'

response = http.request(request)
puts JSON.parse(response.body)
GO
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	req, _ := http.NewRequest("GET", "/api/v3/me", nil)
	req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
	req.Header.Set("Accept", "application/json")

	client := &http.Client{}
	resp, _ := client.Do(req)
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(string(body))
}

Responses

200 Tenant profile.
{
    "status": "success",
    "data": {
        "id": 12,
        "name": "Workspace Alpha",
        "slug": "workspace-alpha",
        "external_client_id": null
    }
}
401 Unauthorized.
{
    "status": "error",
    "message": "Invalid API token."
}

Webhooks

How webhooks work — payload reference (not an endpoint)

POST/api/v3/webhooks

**This is a documentation entry, not a callable endpoint.** It describes the payload your server will receive when subscribed events fire. When a subscribed event occurs, we send a `POST` to your configured webhook URL with a JSON body and these headers: | Header | Purpose | |---|---| | `Content-Type: application/json` | Always JSON. | | `User-Agent: MomoBusiness-Webhook/1.0` | Identifies our delivery agent. | | `X-Webhook-Signature: sha256=<hex>` | HMAC-SHA256 of the raw request body using your webhook secret — verify before trusting the payload. | | `X-Event: <event_name>` | Repeats the `event` field from the body for fast routing. | ## Verifying the signature ```php $signature = $request->header('X-Webhook-Signature'); $expected = 'sha256='.hash_hmac('sha256', $request->getContent(), $yourWebhookSecret); if (! hash_equals($expected, $signature)) abort(401); ``` ```node const crypto = require('crypto'); const expected = crypto.createHmac('sha256', YOUR_WEBHOOK_SECRET).update(rawBody).digest('hex'); if ('sha256=' + expected !== req.headers['x-webhook-signature']) return res.status(401).end(); ``` ## Subscribed event names | Event | When it fires | |---|---| | `message.received` | Customer sent you a message (any channel). | | `message.sent` | Your outbound message was accepted by the gateway. | | `message.delivered` | Gateway confirmed delivery to the recipient device. | | `message.read` | Recipient opened the message (WhatsApp only, requires read receipts). | | `message.failed` | Send failed; `metadata.error` carries the gateway message. | | `message.echoed` | An agent replied via the WhatsApp Business app on their phone (out-of-band reply mirrored back to you). | | `order.received` | Customer submitted a cart through your WhatsApp catalogue. | ## Retry policy If your endpoint returns a non-2xx status or doesn't respond within 10s, we retry with exponential backoff (15s, 60s, 300s, 1800s, 3600s) for up to 24h. Idempotency key: `message_id` + `event`.

Request body

FieldTypeRequiredDescription
eventstringYesWhich event triggered this delivery.
message_idintegerYesOur internal message identifier.
directionstringYes
senderstringNoE.164 phone (or sender ID for SMS) of the message originator.
recipientstringNoE.164 phone of the message recipient.
statusstringYes
bodystringNoMessage text or caption (null for media-only / interactive replies).
media_urlstringNoDirect URL to the media file when the message contains media.
channel_typestringYes
timestampstringYesISO 8601 timestamp of when the event was emitted.
CURL
curl -X POST '/api/v3/webhooks' \
  -H 'Authorization: Bearer YOUR_API_TOKEN' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
    "event": "message.received",
    "message_id": 8421,
    "direction": "inbound",
    "sender": "255700111222",
    "recipient": "255700000111",
    "status": "received",
    "body": "Hi, is the laptop still in stock?",
    "media_url": null,
    "channel_type": "whatsapp",
    "timestamp": "2026-04-18T10:21:33Z"
}'
NODE
const axios = require('axios')

const response = await axios({
    method: 'post',
    url: '/api/v3/webhooks',
    headers: {
      Authorization: 'Bearer YOUR_API_TOKEN',
      Accept: 'application/json',
      'Content-Type': 'application/json'
    },
    data: {
    "event": "message.received",
    "message_id": 8421,
    "direction": "inbound",
    "sender": "255700111222",
    "recipient": "255700000111",
    "status": "received",
    "body": "Hi, is the laptop still in stock?",
    "media_url": null,
    "channel_type": "whatsapp",
    "timestamp": "2026-04-18T10:21:33Z"
}
})

console.log(response.data)
PYTHON
import requests

response = requests.post(
    "/api/v3/webhooks",
    headers={
        "Authorization": "Bearer YOUR_API_TOKEN",
        "Accept": "application/json",
        "Content-Type": "application/json"
    },
    json={
    "event": "message.received",
    "message_id": 8421,
    "direction": "inbound",
    "sender": "255700111222",
    "recipient": "255700000111",
    "status": "received",
    "body": "Hi, is the laptop still in stock?",
    "media_url": null,
    "channel_type": "whatsapp",
    "timestamp": "2026-04-18T10:21:33Z"
}
)

print(response.json())
PHP
$client = new \GuzzleHttp\Client();

$response = $client->request('POST', '/api/v3/webhooks', [
    'headers' => [
        'Authorization' => 'Bearer YOUR_API_TOKEN',
        'Accept'        => 'application/json',
        'Content-Type'  => 'application/json',
    ],
    'json' => {
    "event": "message.received",
    "message_id": 8421,
    "direction": "inbound",
    "sender": "255700111222",
    "recipient": "255700000111",
    "status": "received",
    "body": "Hi, is the laptop still in stock?",
    "media_url": null,
    "channel_type": "whatsapp",
    "timestamp": "2026-04-18T10:21:33Z"
},
]);

$data = json_decode($response->getBody(), true);
RUBY
require 'net/http'
require 'json'
require 'uri'

uri = URI('/api/v3/webhooks')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'

request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer YOUR_API_TOKEN'
request['Accept'] = 'application/json'
request['Content-Type'] = 'application/json'
request.body = '{
    "event": "message.received",
    "message_id": 8421,
    "direction": "inbound",
    "sender": "255700111222",
    "recipient": "255700000111",
    "status": "received",
    "body": "Hi, is the laptop still in stock?",
    "media_url": null,
    "channel_type": "whatsapp",
    "timestamp": "2026-04-18T10:21:33Z"
}'

response = http.request(request)
puts JSON.parse(response.body)
GO
package main

import (
	"bytes"
	"fmt"
	"io"
	"net/http"
)

func main() {
	req, _ := http.NewRequest("POST", "/api/v3/webhooks", bytes.NewBuffer([]byte(`{
    "event": "message.received",
    "message_id": 8421,
    "direction": "inbound",
    "sender": "255700111222",
    "recipient": "255700000111",
    "status": "received",
    "body": "Hi, is the laptop still in stock?",
    "media_url": null,
    "channel_type": "whatsapp",
    "timestamp": "2026-04-18T10:21:33Z"
}`)))
	req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
	req.Header.Set("Accept", "application/json")
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{}
	resp, _ := client.Do(req)
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(string(body))
}

Responses

200 Your endpoint should respond with a 2xx within 10 seconds. Body is ignored.
{
    "ok": true
}
401 Return 401 when the signature does not match — we will retry, but persistent 401s eventually disable the webhook.
{
    "error": "Invalid signature"
}

Profile & Balance

Get balance

GET/api/v3/balance

Returns wallet balance, currency, billing mode, and spend metadata.

CURL
curl -X GET '/api/v3/balance' \
  -H 'Authorization: Bearer YOUR_API_TOKEN' \
  -H 'Accept: application/json'
NODE
const axios = require('axios')

const response = await axios({
    method: 'get',
    url: '/api/v3/balance',
    headers: {
      Authorization: 'Bearer YOUR_API_TOKEN',
      Accept: 'application/json'
    }
})

console.log(response.data)
PYTHON
import requests

response = requests.get(
    "/api/v3/balance",
    headers={
        "Authorization": "Bearer YOUR_API_TOKEN",
        "Accept": "application/json"
    }
)

print(response.json())
PHP
$client = new \GuzzleHttp\Client();

$response = $client->request('GET', '/api/v3/balance', [
    'headers' => [
        'Authorization' => 'Bearer YOUR_API_TOKEN',
        'Accept'        => 'application/json',
    ],
]);

$data = json_decode($response->getBody(), true);
RUBY
require 'net/http'
require 'json'
require 'uri'

uri = URI('/api/v3/balance')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'

request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer YOUR_API_TOKEN'
request['Accept'] = 'application/json'

response = http.request(request)
puts JSON.parse(response.body)
GO
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	req, _ := http.NewRequest("GET", "/api/v3/balance", nil)
	req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
	req.Header.Set("Accept", "application/json")

	client := &http.Client{}
	resp, _ := client.Do(req)
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(string(body))
}

Responses

200 Tenant wallet balance.
{
    "status": "success",
    "data": {
        "wallet_balance": 12000.5,
        "wallet_currency": "TZS",
        "billing_mode": "prepaid",
        "cumulative_spend_cents": 0,
        "tier_override": false
    }
}
401 Unauthorized.
{
    "status": "error",
    "message": "Missing bearer token."
}

Catalogue

List shops

GET/api/v3/catalogues

List the shops (product catalogues) belonging to your tenant, with their name, currency and the channels each is published to.

CURL
curl -X GET '/api/v3/catalogues' \
  -H 'Authorization: Bearer YOUR_API_TOKEN' \
  -H 'Accept: application/json'
NODE
const axios = require('axios')

const response = await axios({
    method: 'get',
    url: '/api/v3/catalogues',
    headers: {
      Authorization: 'Bearer YOUR_API_TOKEN',
      Accept: 'application/json'
    }
})

console.log(response.data)
PYTHON
import requests

response = requests.get(
    "/api/v3/catalogues",
    headers={
        "Authorization": "Bearer YOUR_API_TOKEN",
        "Accept": "application/json"
    }
)

print(response.json())
PHP
$client = new \GuzzleHttp\Client();

$response = $client->request('GET', '/api/v3/catalogues', [
    'headers' => [
        'Authorization' => 'Bearer YOUR_API_TOKEN',
        'Accept'        => 'application/json',
    ],
]);

$data = json_decode($response->getBody(), true);
RUBY
require 'net/http'
require 'json'
require 'uri'

uri = URI('/api/v3/catalogues')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'

request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer YOUR_API_TOKEN'
request['Accept'] = 'application/json'

response = http.request(request)
puts JSON.parse(response.body)
GO
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	req, _ := http.NewRequest("GET", "/api/v3/catalogues", nil)
	req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
	req.Header.Set("Accept", "application/json")

	client := &http.Client{}
	resp, _ := client.Do(req)
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(string(body))
}

Responses

200 Successful response.
{
    "status": "success",
    "data": []
}
422 Validation error.
{
    "status": "error",
    "message": "Validation failed.",
    "errors": {
        "field": [
            "The field is required."
        ]
    }
}

Catalogue

Get one shop with its products

GET/api/v3/catalogues/{catalogue}

Return a single shop and the products it holds, including SKU, price, availability, brand and category.

Parameters

NameInTypeRequiredDescription
cataloguepathintegerYesThe shop id, as returned by the shop list.
CURL
curl -X GET '/api/v3/catalogues/{catalogue}' \
  -H 'Authorization: Bearer YOUR_API_TOKEN' \
  -H 'Accept: application/json'
NODE
const axios = require('axios')

const response = await axios({
    method: 'get',
    url: '/api/v3/catalogues/{catalogue}',
    headers: {
      Authorization: 'Bearer YOUR_API_TOKEN',
      Accept: 'application/json'
    }
})

console.log(response.data)
PYTHON
import requests

response = requests.get(
    "/api/v3/catalogues/{catalogue}",
    headers={
        "Authorization": "Bearer YOUR_API_TOKEN",
        "Accept": "application/json"
    }
)

print(response.json())
PHP
$client = new \GuzzleHttp\Client();

$response = $client->request('GET', '/api/v3/catalogues/{catalogue}', [
    'headers' => [
        'Authorization' => 'Bearer YOUR_API_TOKEN',
        'Accept'        => 'application/json',
    ],
]);

$data = json_decode($response->getBody(), true);
RUBY
require 'net/http'
require 'json'
require 'uri'

uri = URI('/api/v3/catalogues/{catalogue}')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'

request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer YOUR_API_TOKEN'
request['Accept'] = 'application/json'

response = http.request(request)
puts JSON.parse(response.body)
GO
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	req, _ := http.NewRequest("GET", "/api/v3/catalogues/{catalogue}", nil)
	req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
	req.Header.Set("Accept", "application/json")

	client := &http.Client{}
	resp, _ := client.Do(req)
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(string(body))
}

Responses

200 Successful response.
{
    "status": "success",
    "data": []
}
422 Validation error.
{
    "status": "error",
    "message": "Validation failed.",
    "errors": {
        "field": [
            "The field is required."
        ]
    }
}

Catalogue

Add a product to a shop

POST/api/v3/catalogues/{catalogue}/products

Create one product in a shop. The SKU is optional — leave it out and the shop issues its own code, which is also what gets sent to any connected platform as its required retailer id.

Parameters

NameInTypeRequiredDescription
cataloguepathintegerYesThe shop the product belongs to.
CURL
curl -X POST '/api/v3/catalogues/{catalogue}/products' \
  -H 'Authorization: Bearer YOUR_API_TOKEN' \
  -H 'Accept: application/json'
NODE
const axios = require('axios')

const response = await axios({
    method: 'post',
    url: '/api/v3/catalogues/{catalogue}/products',
    headers: {
      Authorization: 'Bearer YOUR_API_TOKEN',
      Accept: 'application/json'
    }
})

console.log(response.data)
PYTHON
import requests

response = requests.post(
    "/api/v3/catalogues/{catalogue}/products",
    headers={
        "Authorization": "Bearer YOUR_API_TOKEN",
        "Accept": "application/json"
    }
)

print(response.json())
PHP
$client = new \GuzzleHttp\Client();

$response = $client->request('POST', '/api/v3/catalogues/{catalogue}/products', [
    'headers' => [
        'Authorization' => 'Bearer YOUR_API_TOKEN',
        'Accept'        => 'application/json',
    ],
]);

$data = json_decode($response->getBody(), true);
RUBY
require 'net/http'
require 'json'
require 'uri'

uri = URI('/api/v3/catalogues/{catalogue}/products')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'

request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer YOUR_API_TOKEN'
request['Accept'] = 'application/json'

response = http.request(request)
puts JSON.parse(response.body)
GO
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	req, _ := http.NewRequest("POST", "/api/v3/catalogues/{catalogue}/products", nil)
	req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
	req.Header.Set("Accept", "application/json")

	client := &http.Client{}
	resp, _ := client.Do(req)
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(string(body))
}

Responses

200 Successful response.
{
    "status": "success",
    "data": []
}
422 Validation error.
{
    "status": "error",
    "message": "Validation failed.",
    "errors": {
        "field": [
            "The field is required."
        ]
    }
}

Catalogue

List a shop's products

GET/api/v3/catalogues/{catalogue}/products

List the products in one shop, with SKU, price, availability, brand and category.

Parameters

NameInTypeRequiredDescription
cataloguepathintegerYesThe shop whose products to list.
CURL
curl -X GET '/api/v3/catalogues/{catalogue}/products' \
  -H 'Authorization: Bearer YOUR_API_TOKEN' \
  -H 'Accept: application/json'
NODE
const axios = require('axios')

const response = await axios({
    method: 'get',
    url: '/api/v3/catalogues/{catalogue}/products',
    headers: {
      Authorization: 'Bearer YOUR_API_TOKEN',
      Accept: 'application/json'
    }
})

console.log(response.data)
PYTHON
import requests

response = requests.get(
    "/api/v3/catalogues/{catalogue}/products",
    headers={
        "Authorization": "Bearer YOUR_API_TOKEN",
        "Accept": "application/json"
    }
)

print(response.json())
PHP
$client = new \GuzzleHttp\Client();

$response = $client->request('GET', '/api/v3/catalogues/{catalogue}/products', [
    'headers' => [
        'Authorization' => 'Bearer YOUR_API_TOKEN',
        'Accept'        => 'application/json',
    ],
]);

$data = json_decode($response->getBody(), true);
RUBY
require 'net/http'
require 'json'
require 'uri'

uri = URI('/api/v3/catalogues/{catalogue}/products')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'

request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer YOUR_API_TOKEN'
request['Accept'] = 'application/json'

response = http.request(request)
puts JSON.parse(response.body)
GO
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	req, _ := http.NewRequest("GET", "/api/v3/catalogues/{catalogue}/products", nil)
	req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
	req.Header.Set("Accept", "application/json")

	client := &http.Client{}
	resp, _ := client.Do(req)
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(string(body))
}

Responses

200 Successful response.
{
    "status": "success",
    "data": []
}
422 Validation error.
{
    "status": "error",
    "message": "Validation failed.",
    "errors": {
        "field": [
            "The field is required."
        ]
    }
}

Catalogue

Add or update many products at once

POST/api/v3/catalogues/{catalogue}/products/batch

Create or update products in bulk. Rows are matched on SKU where you supply one, so re-sending the same batch updates rather than duplicating.

Parameters

NameInTypeRequiredDescription
cataloguepathintegerYesThe shop the products belong to.
CURL
curl -X POST '/api/v3/catalogues/{catalogue}/products/batch' \
  -H 'Authorization: Bearer YOUR_API_TOKEN' \
  -H 'Accept: application/json'
NODE
const axios = require('axios')

const response = await axios({
    method: 'post',
    url: '/api/v3/catalogues/{catalogue}/products/batch',
    headers: {
      Authorization: 'Bearer YOUR_API_TOKEN',
      Accept: 'application/json'
    }
})

console.log(response.data)
PYTHON
import requests

response = requests.post(
    "/api/v3/catalogues/{catalogue}/products/batch",
    headers={
        "Authorization": "Bearer YOUR_API_TOKEN",
        "Accept": "application/json"
    }
)

print(response.json())
PHP
$client = new \GuzzleHttp\Client();

$response = $client->request('POST', '/api/v3/catalogues/{catalogue}/products/batch', [
    'headers' => [
        'Authorization' => 'Bearer YOUR_API_TOKEN',
        'Accept'        => 'application/json',
    ],
]);

$data = json_decode($response->getBody(), true);
RUBY
require 'net/http'
require 'json'
require 'uri'

uri = URI('/api/v3/catalogues/{catalogue}/products/batch')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'

request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer YOUR_API_TOKEN'
request['Accept'] = 'application/json'

response = http.request(request)
puts JSON.parse(response.body)
GO
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	req, _ := http.NewRequest("POST", "/api/v3/catalogues/{catalogue}/products/batch", nil)
	req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
	req.Header.Set("Accept", "application/json")

	client := &http.Client{}
	resp, _ := client.Do(req)
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(string(body))
}

Responses

200 Successful response.
{
    "status": "success",
    "data": []
}
422 Validation error.
{
    "status": "error",
    "message": "Validation failed.",
    "errors": {
        "field": [
            "The field is required."
        ]
    }
}

Catalogue

Remove a product from a shop

DELETE/api/v3/catalogues/{catalogue}/products/{product}

Delete one product. It is also retired from any platform it had been published to.

Parameters

NameInTypeRequiredDescription
cataloguepathintegerYesThe shop the product belongs to.
productpathintegerYesThe product to remove.
CURL
curl -X DELETE '/api/v3/catalogues/{catalogue}/products/{product}' \
  -H 'Authorization: Bearer YOUR_API_TOKEN' \
  -H 'Accept: application/json'
NODE
const axios = require('axios')

const response = await axios({
    method: 'delete',
    url: '/api/v3/catalogues/{catalogue}/products/{product}',
    headers: {
      Authorization: 'Bearer YOUR_API_TOKEN',
      Accept: 'application/json'
    }
})

console.log(response.data)
PYTHON
import requests

response = requests.delete(
    "/api/v3/catalogues/{catalogue}/products/{product}",
    headers={
        "Authorization": "Bearer YOUR_API_TOKEN",
        "Accept": "application/json"
    }
)

print(response.json())
PHP
$client = new \GuzzleHttp\Client();

$response = $client->request('DELETE', '/api/v3/catalogues/{catalogue}/products/{product}', [
    'headers' => [
        'Authorization' => 'Bearer YOUR_API_TOKEN',
        'Accept'        => 'application/json',
    ],
]);

$data = json_decode($response->getBody(), true);
RUBY
require 'net/http'
require 'json'
require 'uri'

uri = URI('/api/v3/catalogues/{catalogue}/products/{product}')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'

request = Net::HTTP::Delete.new(uri)
request['Authorization'] = 'Bearer YOUR_API_TOKEN'
request['Accept'] = 'application/json'

response = http.request(request)
puts JSON.parse(response.body)
GO
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	req, _ := http.NewRequest("DELETE", "/api/v3/catalogues/{catalogue}/products/{product}", nil)
	req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
	req.Header.Set("Accept", "application/json")

	client := &http.Client{}
	resp, _ := client.Do(req)
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(string(body))
}

Responses

200 Successful response.
{
    "status": "success",
    "data": []
}
422 Validation error.
{
    "status": "error",
    "message": "Validation failed.",
    "errors": {
        "field": [
            "The field is required."
        ]
    }
}

Catalogue

Get one product

GET/api/v3/catalogues/{catalogue}/products/{product}

Return a single product in full, including its description, images and the platforms it is currently published to.

Parameters

NameInTypeRequiredDescription
cataloguepathintegerYesThe shop the product belongs to.
productpathintegerYesThe product to return.
CURL
curl -X GET '/api/v3/catalogues/{catalogue}/products/{product}' \
  -H 'Authorization: Bearer YOUR_API_TOKEN' \
  -H 'Accept: application/json'
NODE
const axios = require('axios')

const response = await axios({
    method: 'get',
    url: '/api/v3/catalogues/{catalogue}/products/{product}',
    headers: {
      Authorization: 'Bearer YOUR_API_TOKEN',
      Accept: 'application/json'
    }
})

console.log(response.data)
PYTHON
import requests

response = requests.get(
    "/api/v3/catalogues/{catalogue}/products/{product}",
    headers={
        "Authorization": "Bearer YOUR_API_TOKEN",
        "Accept": "application/json"
    }
)

print(response.json())
PHP
$client = new \GuzzleHttp\Client();

$response = $client->request('GET', '/api/v3/catalogues/{catalogue}/products/{product}', [
    'headers' => [
        'Authorization' => 'Bearer YOUR_API_TOKEN',
        'Accept'        => 'application/json',
    ],
]);

$data = json_decode($response->getBody(), true);
RUBY
require 'net/http'
require 'json'
require 'uri'

uri = URI('/api/v3/catalogues/{catalogue}/products/{product}')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'

request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer YOUR_API_TOKEN'
request['Accept'] = 'application/json'

response = http.request(request)
puts JSON.parse(response.body)
GO
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	req, _ := http.NewRequest("GET", "/api/v3/catalogues/{catalogue}/products/{product}", nil)
	req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
	req.Header.Set("Accept", "application/json")

	client := &http.Client{}
	resp, _ := client.Do(req)
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(string(body))
}

Responses

200 Successful response.
{
    "status": "success",
    "data": []
}
422 Validation error.
{
    "status": "error",
    "message": "Validation failed.",
    "errors": {
        "field": [
            "The field is required."
        ]
    }
}

Catalogue

Update a product

PUT/api/v3/catalogues/{catalogue}/products/{product}

Update a product's details. The SKU is fixed once the product has been published to a platform, because that platform treats it as the item's identity.

Parameters

NameInTypeRequiredDescription
cataloguepathintegerYesThe shop the product belongs to.
productpathintegerYesThe product to update.
CURL
curl -X PUT '/api/v3/catalogues/{catalogue}/products/{product}' \
  -H 'Authorization: Bearer YOUR_API_TOKEN' \
  -H 'Accept: application/json'
NODE
const axios = require('axios')

const response = await axios({
    method: 'put',
    url: '/api/v3/catalogues/{catalogue}/products/{product}',
    headers: {
      Authorization: 'Bearer YOUR_API_TOKEN',
      Accept: 'application/json'
    }
})

console.log(response.data)
PYTHON
import requests

response = requests.put(
    "/api/v3/catalogues/{catalogue}/products/{product}",
    headers={
        "Authorization": "Bearer YOUR_API_TOKEN",
        "Accept": "application/json"
    }
)

print(response.json())
PHP
$client = new \GuzzleHttp\Client();

$response = $client->request('PUT', '/api/v3/catalogues/{catalogue}/products/{product}', [
    'headers' => [
        'Authorization' => 'Bearer YOUR_API_TOKEN',
        'Accept'        => 'application/json',
    ],
]);

$data = json_decode($response->getBody(), true);
RUBY
require 'net/http'
require 'json'
require 'uri'

uri = URI('/api/v3/catalogues/{catalogue}/products/{product}')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'

request = Net::HTTP::Put.new(uri)
request['Authorization'] = 'Bearer YOUR_API_TOKEN'
request['Accept'] = 'application/json'

response = http.request(request)
puts JSON.parse(response.body)
GO
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	req, _ := http.NewRequest("PUT", "/api/v3/catalogues/{catalogue}/products/{product}", nil)
	req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
	req.Header.Set("Accept", "application/json")

	client := &http.Client{}
	resp, _ := client.Do(req)
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(string(body))
}

Responses

200 Successful response.
{
    "status": "success",
    "data": []
}
422 Validation error.
{
    "status": "error",
    "message": "Validation failed.",
    "errors": {
        "field": [
            "The field is required."
        ]
    }
}

Catalogue

List shop orders

GET/api/v3/catalogues/orders

List orders placed against your shops, newest first, with their status, total and whether payment has settled.

CURL
curl -X GET '/api/v3/catalogues/orders' \
  -H 'Authorization: Bearer YOUR_API_TOKEN' \
  -H 'Accept: application/json'
NODE
const axios = require('axios')

const response = await axios({
    method: 'get',
    url: '/api/v3/catalogues/orders',
    headers: {
      Authorization: 'Bearer YOUR_API_TOKEN',
      Accept: 'application/json'
    }
})

console.log(response.data)
PYTHON
import requests

response = requests.get(
    "/api/v3/catalogues/orders",
    headers={
        "Authorization": "Bearer YOUR_API_TOKEN",
        "Accept": "application/json"
    }
)

print(response.json())
PHP
$client = new \GuzzleHttp\Client();

$response = $client->request('GET', '/api/v3/catalogues/orders', [
    'headers' => [
        'Authorization' => 'Bearer YOUR_API_TOKEN',
        'Accept'        => 'application/json',
    ],
]);

$data = json_decode($response->getBody(), true);
RUBY
require 'net/http'
require 'json'
require 'uri'

uri = URI('/api/v3/catalogues/orders')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'

request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer YOUR_API_TOKEN'
request['Accept'] = 'application/json'

response = http.request(request)
puts JSON.parse(response.body)
GO
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	req, _ := http.NewRequest("GET", "/api/v3/catalogues/orders", nil)
	req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
	req.Header.Set("Accept", "application/json")

	client := &http.Client{}
	resp, _ := client.Do(req)
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(string(body))
}

Responses

200 Successful response.
{
    "status": "success",
    "data": []
}
422 Validation error.
{
    "status": "error",
    "message": "Validation failed.",
    "errors": {
        "field": [
            "The field is required."
        ]
    }
}

Catalogue

Get one order

GET/api/v3/catalogues/orders/{order}

Return a single order in full: the customer, the items ordered, the total, the status history and every payment attempt against it.

Parameters

NameInTypeRequiredDescription
orderpathintegerYesThe order id, as returned by the order list.
CURL
curl -X GET '/api/v3/catalogues/orders/{order}' \
  -H 'Authorization: Bearer YOUR_API_TOKEN' \
  -H 'Accept: application/json'
NODE
const axios = require('axios')

const response = await axios({
    method: 'get',
    url: '/api/v3/catalogues/orders/{order}',
    headers: {
      Authorization: 'Bearer YOUR_API_TOKEN',
      Accept: 'application/json'
    }
})

console.log(response.data)
PYTHON
import requests

response = requests.get(
    "/api/v3/catalogues/orders/{order}",
    headers={
        "Authorization": "Bearer YOUR_API_TOKEN",
        "Accept": "application/json"
    }
)

print(response.json())
PHP
$client = new \GuzzleHttp\Client();

$response = $client->request('GET', '/api/v3/catalogues/orders/{order}', [
    'headers' => [
        'Authorization' => 'Bearer YOUR_API_TOKEN',
        'Accept'        => 'application/json',
    ],
]);

$data = json_decode($response->getBody(), true);
RUBY
require 'net/http'
require 'json'
require 'uri'

uri = URI('/api/v3/catalogues/orders/{order}')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'

request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer YOUR_API_TOKEN'
request['Accept'] = 'application/json'

response = http.request(request)
puts JSON.parse(response.body)
GO
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	req, _ := http.NewRequest("GET", "/api/v3/catalogues/orders/{order}", nil)
	req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
	req.Header.Set("Accept", "application/json")

	client := &http.Client{}
	resp, _ := client.Do(req)
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(string(body))
}

Responses

200 Successful response.
{
    "status": "success",
    "data": []
}
422 Validation error.
{
    "status": "error",
    "message": "Validation failed.",
    "errors": {
        "field": [
            "The field is required."
        ]
    }
}

Catalogue

Move an order to a new status

PUT/api/v3/catalogues/orders/{order}/status

Advance an order to confirmed, processing, shipped, delivered, cancelled or refunded, and optionally notify the customer on the channel they ordered from.

Parameters

NameInTypeRequiredDescription
orderpathintegerYesThe order to move.
CURL
curl -X PUT '/api/v3/catalogues/orders/{order}/status' \
  -H 'Authorization: Bearer YOUR_API_TOKEN' \
  -H 'Accept: application/json'
NODE
const axios = require('axios')

const response = await axios({
    method: 'put',
    url: '/api/v3/catalogues/orders/{order}/status',
    headers: {
      Authorization: 'Bearer YOUR_API_TOKEN',
      Accept: 'application/json'
    }
})

console.log(response.data)
PYTHON
import requests

response = requests.put(
    "/api/v3/catalogues/orders/{order}/status",
    headers={
        "Authorization": "Bearer YOUR_API_TOKEN",
        "Accept": "application/json"
    }
)

print(response.json())
PHP
$client = new \GuzzleHttp\Client();

$response = $client->request('PUT', '/api/v3/catalogues/orders/{order}/status', [
    'headers' => [
        'Authorization' => 'Bearer YOUR_API_TOKEN',
        'Accept'        => 'application/json',
    ],
]);

$data = json_decode($response->getBody(), true);
RUBY
require 'net/http'
require 'json'
require 'uri'

uri = URI('/api/v3/catalogues/orders/{order}/status')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'

request = Net::HTTP::Put.new(uri)
request['Authorization'] = 'Bearer YOUR_API_TOKEN'
request['Accept'] = 'application/json'

response = http.request(request)
puts JSON.parse(response.body)
GO
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	req, _ := http.NewRequest("PUT", "/api/v3/catalogues/orders/{order}/status", nil)
	req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
	req.Header.Set("Accept", "application/json")

	client := &http.Client{}
	resp, _ := client.Do(req)
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(string(body))
}

Responses

200 Successful response.
{
    "status": "success",
    "data": []
}
422 Validation error.
{
    "status": "error",
    "message": "Validation failed.",
    "errors": {
        "field": [
            "The field is required."
        ]
    }
}

Catalogue

Send one product to a customer

POST/api/v3/catalogues/send-product

Send a single product card to a customer on WhatsApp, so they can view it and add it to a cart without leaving the chat.

CURL
curl -X POST '/api/v3/catalogues/send-product' \
  -H 'Authorization: Bearer YOUR_API_TOKEN' \
  -H 'Accept: application/json'
NODE
const axios = require('axios')

const response = await axios({
    method: 'post',
    url: '/api/v3/catalogues/send-product',
    headers: {
      Authorization: 'Bearer YOUR_API_TOKEN',
      Accept: 'application/json'
    }
})

console.log(response.data)
PYTHON
import requests

response = requests.post(
    "/api/v3/catalogues/send-product",
    headers={
        "Authorization": "Bearer YOUR_API_TOKEN",
        "Accept": "application/json"
    }
)

print(response.json())
PHP
$client = new \GuzzleHttp\Client();

$response = $client->request('POST', '/api/v3/catalogues/send-product', [
    'headers' => [
        'Authorization' => 'Bearer YOUR_API_TOKEN',
        'Accept'        => 'application/json',
    ],
]);

$data = json_decode($response->getBody(), true);
RUBY
require 'net/http'
require 'json'
require 'uri'

uri = URI('/api/v3/catalogues/send-product')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'

request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer YOUR_API_TOKEN'
request['Accept'] = 'application/json'

response = http.request(request)
puts JSON.parse(response.body)
GO
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	req, _ := http.NewRequest("POST", "/api/v3/catalogues/send-product", nil)
	req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
	req.Header.Set("Accept", "application/json")

	client := &http.Client{}
	resp, _ := client.Do(req)
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(string(body))
}

Responses

200 Successful response.
{
    "status": "success",
    "data": []
}
422 Validation error.
{
    "status": "error",
    "message": "Validation failed.",
    "errors": {
        "field": [
            "The field is required."
        ]
    }
}

Catalogue

Send a list of products to a customer

POST/api/v3/catalogues/send-product-list

Send several products grouped into sections as one interactive message — the usual way to answer "what do you have?" in chat.

CURL
curl -X POST '/api/v3/catalogues/send-product-list' \
  -H 'Authorization: Bearer YOUR_API_TOKEN' \
  -H 'Accept: application/json'
NODE
const axios = require('axios')

const response = await axios({
    method: 'post',
    url: '/api/v3/catalogues/send-product-list',
    headers: {
      Authorization: 'Bearer YOUR_API_TOKEN',
      Accept: 'application/json'
    }
})

console.log(response.data)
PYTHON
import requests

response = requests.post(
    "/api/v3/catalogues/send-product-list",
    headers={
        "Authorization": "Bearer YOUR_API_TOKEN",
        "Accept": "application/json"
    }
)

print(response.json())
PHP
$client = new \GuzzleHttp\Client();

$response = $client->request('POST', '/api/v3/catalogues/send-product-list', [
    'headers' => [
        'Authorization' => 'Bearer YOUR_API_TOKEN',
        'Accept'        => 'application/json',
    ],
]);

$data = json_decode($response->getBody(), true);
RUBY
require 'net/http'
require 'json'
require 'uri'

uri = URI('/api/v3/catalogues/send-product-list')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'

request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer YOUR_API_TOKEN'
request['Accept'] = 'application/json'

response = http.request(request)
puts JSON.parse(response.body)
GO
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	req, _ := http.NewRequest("POST", "/api/v3/catalogues/send-product-list", nil)
	req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
	req.Header.Set("Accept", "application/json")

	client := &http.Client{}
	resp, _ := client.Do(req)
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(string(body))
}

Responses

200 Successful response.
{
    "status": "success",
    "data": []
}
422 Validation error.
{
    "status": "error",
    "message": "Validation failed.",
    "errors": {
        "field": [
            "The field is required."
        ]
    }
}

Catalogue

Send the whole shop to a customer

POST/api/v3/catalogues/send-catalogue

Send the full catalogue as one message, letting the customer browse everything the shop has published to that channel.

CURL
curl -X POST '/api/v3/catalogues/send-catalogue' \
  -H 'Authorization: Bearer YOUR_API_TOKEN' \
  -H 'Accept: application/json'
NODE
const axios = require('axios')

const response = await axios({
    method: 'post',
    url: '/api/v3/catalogues/send-catalogue',
    headers: {
      Authorization: 'Bearer YOUR_API_TOKEN',
      Accept: 'application/json'
    }
})

console.log(response.data)
PYTHON
import requests

response = requests.post(
    "/api/v3/catalogues/send-catalogue",
    headers={
        "Authorization": "Bearer YOUR_API_TOKEN",
        "Accept": "application/json"
    }
)

print(response.json())
PHP
$client = new \GuzzleHttp\Client();

$response = $client->request('POST', '/api/v3/catalogues/send-catalogue', [
    'headers' => [
        'Authorization' => 'Bearer YOUR_API_TOKEN',
        'Accept'        => 'application/json',
    ],
]);

$data = json_decode($response->getBody(), true);
RUBY
require 'net/http'
require 'json'
require 'uri'

uri = URI('/api/v3/catalogues/send-catalogue')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'

request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer YOUR_API_TOKEN'
request['Accept'] = 'application/json'

response = http.request(request)
puts JSON.parse(response.body)
GO
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	req, _ := http.NewRequest("POST", "/api/v3/catalogues/send-catalogue", nil)
	req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
	req.Header.Set("Accept", "application/json")

	client := &http.Client{}
	resp, _ := client.Do(req)
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(string(body))
}

Responses

200 Successful response.
{
    "status": "success",
    "data": []
}
422 Validation error.
{
    "status": "error",
    "message": "Validation failed.",
    "errors": {
        "field": [
            "The field is required."
        ]
    }
}

Developer partnership

From first request to production operations.

OpenAPIA machine-readable contract.

ExamplesCopy-ready requests.

SupportRaise a request · support@business.momo.tz

Make the next move

Connect your communication workflow.

Create an account and start testing the API in your workspace.