openapi: 3.0.3 info: title: 'MasokoDigital API Reference' description: 'Send SMS, track delivery and pull reporting straight from your own systems.' version: 1.0.0 servers: - url: 'https://masokodigital.co.tz' tags: - name: SMS description: "\nSend messages, check your balance, and pull delivery reporting." - name: WhatsApp description: "\nSend WhatsApp messages, check your balance, and see which of your numbers\nand templates are cleared for use.\n\nWhatsApp is stricter than SMS in two ways worth knowing before you\nintegrate. You can only send from a number Meta has approved, and you can\nonly *open* a conversation with a template Meta has approved - free-form\ntext is accepted solely inside the 24-hour window that a customer's own\nmessage opens." components: securitySchemes: default: type: http scheme: bearer description: 'You can retrieve your token by visiting your profile and clicking Request on API access section.' security: - default: [] paths: /api/sms/send/single: post: summary: 'Send a single SMS' operationId: sendASingleSMS description: "Sends one message to one recipient. The sender ID must already be\napproved - call `GET /api/sms/senders` to see which of yours are." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: message: '2026-08-12 09:00:00 API campaign SMS Campaign Processing...' status: Processing campaign_id: 9b1f...c3 properties: message: type: string example: '2026-08-12 09:00:00 API campaign SMS Campaign Processing...' status: type: string example: Processing campaign_id: type: string example: 9b1f...c3 400: description: '' content: application/json: schema: type: object example: error: recipient: - 'The recipient field is required.' properties: error: type: object properties: recipient: type: array example: - 'The recipient field is required.' items: type: string 404: description: '' content: application/json: schema: type: object example: error: 'Sender ID Not Found' properties: error: type: string example: 'Sender ID Not Found' tags: - SMS requestBody: required: true content: application/json: schema: type: object properties: sender_id: type: string description: 'An approved sender ID.' example: MASOKO message: type: string description: 'The message body.' example: 'Hello from MasokoDigital' recipient: type: string description: 'Recipient MSISDN.' example: '255700000000' required: - sender_id - message - recipient /api/sms/send/bulk: post: summary: 'Send bulk SMS' operationId: sendBulkSMS description: "Sends the same message to many recipients in one call. Recipients are\nprocessed in chunks; the response returns as soon as the campaign is\naccepted, so poll the campaign statistics endpoint for delivery." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: message: '2026-08-12 09:00:00 API campaign SMS Campaign Processing...' status: Processing campaign_id: 9b1f...c3 properties: message: type: string example: '2026-08-12 09:00:00 API campaign SMS Campaign Processing...' status: type: string example: Processing campaign_id: type: string example: 9b1f...c3 400: description: '' content: application/json: schema: type: object example: error: 'Campaign has no contacts' properties: error: type: string example: 'Campaign has no contacts' 404: description: '' content: application/json: schema: type: object example: error: 'Sender ID Not Found' properties: error: type: string example: 'Sender ID Not Found' tags: - SMS requestBody: required: true content: application/json: schema: type: object properties: sender_id: type: string description: 'An approved sender ID.' example: MASOKO message: type: string description: 'The message body.' example: 'Hello from MasokoDigital' recipients: type: array description: 'Recipient MSISDNs.' example: - '255700000000' - '255711111111' items: type: string required: - sender_id - message - recipients /api/sms/balance: get: summary: 'Get SMS balance' operationId: getSMSBalance description: 'Remaining SMS credits on your account.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: balance: 4612 properties: balance: type: integer example: 4612 tags: - SMS /api/campaign/sms/report: get: summary: 'Message counts by status' operationId: messageCountsByStatus description: "Totals per delivery status over a date range.\n\nThe `message` field is a legacy string kept for existing integrations;\nprefer the `data` object, which carries the same counts keyed by status." parameters: - in: query name: start description: 'date Start of the range.' example: '2026-01-01' required: true schema: type: string description: 'date Start of the range.' example: '2026-01-01' - in: query name: end description: 'date End of the range.' example: '2026-12-31' required: false schema: type: string description: 'date End of the range.' example: '2026-12-31' responses: 200: description: '' content: application/json: schema: type: object example: message: '["DELIVRD","Success"] -> [155,614]' data: DELIVRD: 155 Success: 614 total: 769 start: '2026-01-01T00:00:00.000000Z' end: null properties: message: type: string example: '["DELIVRD","Success"] -> [155,614]' data: type: object properties: DELIVRD: type: integer example: 155 Success: type: integer example: 614 total: type: integer example: 769 start: type: string example: '2026-01-01T00:00:00.000000Z' end: type: string example: null nullable: true tags: - SMS requestBody: required: true content: application/json: schema: type: object properties: start: type: string description: 'Must be a valid date.' example: '2026-09-06T16:14:20' end: type: string description: 'Must be a valid date.' example: '2026-09-06T16:14:20' nullable: true required: - start /api/sms/campaigns: get: summary: 'List campaigns' operationId: listCampaigns description: "Your campaigns, newest first. Useful for recovering a `campaign_id` you\ndid not keep from the send response." parameters: - in: query name: start description: 'date Only campaigns created on or after this date.' example: '2026-01-01' required: false schema: type: string description: 'date Only campaigns created on or after this date.' example: '2026-01-01' - in: query name: end description: 'date Only campaigns created on or before this date.' example: '2026-12-31' required: false schema: type: string description: 'date Only campaigns created on or before this date.' example: '2026-12-31' - in: query name: per_page description: 'Results per page, 1-200. Defaults to 25.' example: 50 required: false schema: type: integer description: 'Results per page, 1-200. Defaults to 25.' example: 50 responses: 200: description: '' content: application/json: schema: type: object example: data: - campaign_id: 9b1f...c3 name: '2026-08-12 API campaign' status: Completed messages: 120 created_at: '2026-08-12T09:00:00.000000Z' meta: current_page: 1 last_page: 4 per_page: 25 total: 98 properties: data: type: array example: - campaign_id: 9b1f...c3 name: '2026-08-12 API campaign' status: Completed messages: 120 created_at: '2026-08-12T09:00:00.000000Z' items: type: object properties: campaign_id: type: string example: 9b1f...c3 name: type: string example: '2026-08-12 API campaign' status: type: string example: Completed messages: type: integer example: 120 created_at: type: string example: '2026-08-12T09:00:00.000000Z' meta: type: object properties: current_page: type: integer example: 1 last_page: type: integer example: 4 per_page: type: integer example: 25 total: type: integer example: 98 tags: - SMS requestBody: required: false content: application/json: schema: type: object properties: start: type: string description: 'Must be a valid date.' example: '2026-09-06T16:14:20' nullable: true end: type: string description: 'Must be a valid date.' example: '2026-09-06T16:14:20' nullable: true per_page: type: integer description: 'Must be at least 1. Must not be greater than 200.' example: 21 nullable: true /api/sms/senders: get: summary: 'List sender IDs' operationId: listSenderIDs description: "Your approved sender IDs. Only these can be used to send - anything else\nis rejected with `404 Sender ID Not Found`." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: - sender_id: MASOKO is_default: true properties: data: type: array example: - sender_id: MASOKO is_default: true items: type: object properties: sender_id: type: string example: MASOKO is_default: type: boolean example: true tags: - SMS '/api/campaign/sms/statistics/{campaign_scuuid}': get: summary: 'Get campaign statistics' operationId: getCampaignStatistics description: 'Delivery totals for one campaign.' parameters: [] responses: {} tags: - SMS parameters: - in: path name: campaign_scuuid description: '' example: 86939523-bab7-40cf-8a9a-f57919c02980 required: true schema: type: string - in: path name: campaign description: 'The campaign_id returned when sending.' example: 9b1f...c3 required: true schema: type: string '/api/sms/report/{sms_smsuuid}': get: summary: 'Get a message delivery report' operationId: getAMessageDeliveryReport description: 'Delivery detail for a single message.' parameters: [] responses: {} tags: - SMS parameters: - in: path name: sms_smsuuid description: '' example: bcf8534f-aa13-4a66-948e-d1e45c9b35bf required: true schema: type: string - in: path name: sms description: 'The message identifier.' example: 9c2a...f1 required: true schema: type: string '/api/sms/report/{campaign_scuuid}/{phone}': get: summary: 'Get delivery status for one recipient' operationId: getDeliveryStatusForOneRecipient description: 'Delivery status of a single recipient within a campaign.' parameters: [] responses: {} tags: - SMS parameters: - in: path name: campaign_scuuid description: '' example: 86939523-bab7-40cf-8a9a-f57919c02980 required: true schema: type: string - in: path name: phone description: 'Recipient MSISDN.' example: '255700000000' required: true schema: type: string - in: path name: campaign description: 'The campaign_id returned when sending.' example: 9b1f...c3 required: true schema: type: string /api/sms/campaign/report/dates: get: summary: 'Full delivery breakdown' operationId: fullDeliveryBreakdown description: "Message counts broken down by delivery status, gender, age band and\nrecipient location over a date range." parameters: - in: query name: start description: 'date Start of the range.' example: '2026-01-01' required: true schema: type: string description: 'date Start of the range.' example: '2026-01-01' - in: query name: end description: 'date End of the range.' example: '2026-12-31' required: false schema: type: string description: 'date End of the range.' example: '2026-12-31' responses: {} tags: - SMS requestBody: required: true content: application/json: schema: type: object properties: start: type: string description: 'Must be a valid date.' example: '2026-09-06T16:14:20' end: type: string description: 'Must be a valid date.' example: '2026-09-06T16:14:20' nullable: true required: - start /api/whatsapp/send: post: summary: 'Send a WhatsApp message' operationId: sendAWhatsAppMessage description: "Sends one message to one recipient and bills one credit.\n\nPass `template` to open a conversation; without it the message is\nfree-form and will only be delivered if the recipient has messaged you\nwithin the last 24 hours. Omit `sender` to use your default number." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: message_id: 9b1f8c22-0b5e-4b1a-9a3a-1d2e3f4a5b6c status: queued to: '255700000000' from: '255700000002' balance: 411 properties: message_id: type: string example: 9b1f8c22-0b5e-4b1a-9a3a-1d2e3f4a5b6c status: type: string example: queued to: type: string example: '255700000000' from: type: string example: '255700000002' balance: type: integer example: 411 400: description: '' content: application/json: schema: type: object example: error: recipient: - 'The recipient field is required.' properties: error: type: object properties: recipient: type: array example: - 'The recipient field is required.' items: type: string 402: description: '' content: application/json: schema: type: object example: error: 'You do not have enough WhatsApp credits. Please buy more.' properties: error: type: string example: 'You do not have enough WhatsApp credits. Please buy more.' 404: description: '' content: application/json: schema: type: object example: error: 'Template Not Found' properties: error: type: string example: 'Template Not Found' tags: - WhatsApp requestBody: required: true content: application/json: schema: type: object properties: recipient: type: string description: 'Recipient MSISDN in E.164, with or without the plus.' example: '255700000000' message: type: string description: 'The message body. Where a template is used, this is the text as the recipient should see it.' example: 'Hello Amina, your order 4471 has shipped.' template: type: string description: 'An approved template id, from `GET /api/whatsapp/templates`. Required to start a new conversation.' example: 9c2f4e10-77a1-4a0e-9a1d-2f3f9d0f1b22 nullable: true sender: type: string description: 'One of your approved numbers. Defaults to your default number.' example: "255700000002\n\nThe `status` returned is WhatsApp's acknowledgement, not proof of\nreceipt. Poll `GET /api/whatsapp/message/{message_id}` to find out\nwhether it was delivered and read." nullable: true required: - recipient - message /api/whatsapp/balance: get: summary: 'Get WhatsApp balance' operationId: getWhatsAppBalance description: 'Remaining WhatsApp credits on your account. One credit is one message.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: balance: 411 properties: balance: type: integer example: 411 tags: - WhatsApp /api/whatsapp/senders: get: summary: 'List WhatsApp numbers' operationId: listWhatsAppNumbers description: "The numbers registered to your account and where each one stands.\nOnly a number with status `approved` can send." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: - number: '255700000002' display_name: 'Masoko Digital' status: approved is_default: true properties: data: type: array example: - number: '255700000002' display_name: 'Masoko Digital' status: approved is_default: true items: type: object properties: number: type: string example: '255700000002' display_name: type: string example: 'Masoko Digital' status: type: string example: approved is_default: type: boolean example: true tags: - WhatsApp /api/whatsapp/templates: get: summary: 'List WhatsApp templates' operationId: listWhatsAppTemplates description: "Your templates and where each one stands. Only a template with status\n`approved` can be used to open a conversation; the others are listed so\nyou can see what is still in review and why anything was turned down.\n\n`variables` lists the placeholders in the body, in the order Meta\nexpects their values." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: - template_id: 9c2f4e10-77a1-4a0e-9a1d-2f3f9d0f1b22 name: 'Order Update' type: utility language: en body: 'Hello {{1}}, your order {{2}} has shipped.' variables: - '1' - '2' status: approved rejection_reason: null properties: data: type: array example: - template_id: 9c2f4e10-77a1-4a0e-9a1d-2f3f9d0f1b22 name: 'Order Update' type: utility language: en body: 'Hello {{1}}, your order {{2}} has shipped.' variables: - '1' - '2' status: approved rejection_reason: null items: type: object properties: template_id: type: string example: 9c2f4e10-77a1-4a0e-9a1d-2f3f9d0f1b22 name: type: string example: 'Order Update' type: type: string example: utility language: type: string example: en body: type: string example: 'Hello {{1}}, your order {{2}} has shipped.' variables: type: array example: - '1' - '2' items: type: string status: type: string example: approved rejection_reason: type: string example: null nullable: true tags: - WhatsApp '/api/whatsapp/message/{message}': get: summary: "Get a message's delivery status" operationId: getAMessagesDeliveryStatus description: "Where one message got to, and when.\n\n`status` moves forward through `queued`, `sent`, `delivered` and `read`,\nand never backwards - so a message that reads `delivered` has been\ndelivered even if a later callback says otherwise. `failed` and\n`undelivered` are terminal, and carry the reason in `error`.\n\n`read` depends on the recipient having read receipts switched on. When\nthey are off, a message that was read stops at `delivered`, so treat a\nmissing `read_at` as \"not known\" rather than \"not read\"." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: message_id: 9b1f8c22-0b5e-4b1a-9a3a-1d2e3f4a5b6c status: read to: '255700000000' from: '255700000002' direction: outbound sent_at: '2026-09-06T09:00:00.000000Z' delivered_at: '2026-09-06T09:00:04.000000Z' read_at: '2026-09-06T09:12:31.000000Z' error: null properties: message_id: type: string example: 9b1f8c22-0b5e-4b1a-9a3a-1d2e3f4a5b6c status: type: string example: read to: type: string example: '255700000000' from: type: string example: '255700000002' direction: type: string example: outbound sent_at: type: string example: '2026-09-06T09:00:00.000000Z' delivered_at: type: string example: '2026-09-06T09:00:04.000000Z' read_at: type: string example: '2026-09-06T09:12:31.000000Z' error: type: string example: null nullable: true 404: description: '' content: application/json: schema: type: object example: error: 'Message Not Found' properties: error: type: string example: 'Message Not Found' tags: - WhatsApp parameters: - in: path name: message description: 'The message_id returned when sending.' example: 9b1f8c22-0b5e-4b1a-9a3a-1d2e3f4a5b6c required: true schema: type: string