Developers

Public API

Send SMS, email and WhatsApp, run campaigns, verify OTPs and read your balance over a simple HTTP API. Every error is machine-readable with a stable code. The full reference is below.

Getting started

All endpoints are served under:

https://api-s1.textdigo.com/api/v0

Requests and responses are JSON (except multipart file uploads). Send the header x-locale: fr or en to localize error messages.

Authentication

Authenticate every request with HTTP Basic using your apiLogin as the username and your apiKey as the password (found in Settings → Organization → API).

curl -u "apiLogin:apiKey" \
  -H "Content-Type: application/json" \
  -d '{"phoneNumber":"+237670000000","message":"Hello"}' \
  https://api-s1.textdigo.com/api/v0/sms/message

Responses & errors

Errors share one shape. statusCode is the HTTP status, errorCode is a stable machine-readable string (see the reference), message is human-readable, and details[] lists per-field problems for validation errors.

{
  "statusCode": 422,
  "errorCode": "PUBLIC_API_INVALID_PHONE_NUMBER",
  "message": "The phone number 0670 is not a valid international number",
  "details": [],
  "timestamp": "2026-06-10T10:00:00.000Z",
  "path": "/api/v0/sms/message"
}

Send messages

POST/sms/messagetest send

Send an SMS to one number.

FieldTypeInDescription
phoneNumber*string (E.164)bodyRecipient number in international format, e.g. +237670000000.
message*string (≤2000)bodyMessage body.
senderNamestring (≤50)bodyApproved sender ID.
smsTypeenum TRANSACTIONAL|MARKETINGbodyBilling tier (default TRANSACTIONAL).
externalIdstringbodyYour reference, echoed in webhooks.

Possible errors: PUBLIC_API_INVALID_PHONE_NUMBER (422), PUBLIC_API_UNSUPPORTED_PHONE_NUMBER (422)

POST/email/messagebillable

Send an email.

FieldTypeInDescription
email*string (email)bodyRecipient email address.
subject*string (≤998)bodyEmail subject.
htmlMessagestringbodyHTML body.
textMessagestringbodyPlain-text body.

Possible errors: PUBLIC_API_INVALID_EMAIL (422)

POST/email/template/messagebillable

Send a templated email.

FieldTypeInDescription
email*string (email)bodyRecipient email address.
subject*stringbodyEmail subject.
templateId*stringbodyTemplate identifier.
parametersobject<string,string>bodyTemplate variable values.

Possible errors: PUBLIC_API_INVALID_EMAIL (422), PUBLIC_API_TEMPLATE_NOT_FOUND (404)

POST/whatsapp-business/messagebillable

Send a WhatsApp Business message.

FieldTypeInDescription
whatsappBusinessPhoneNumberId*stringbodyYour WhatsApp sender id.
phoneNumber*string (E.164)bodyRecipient number in international format, e.g. +237670000000.
messagestring (≤4096)bodyText body (within the 24h window).
mediaUrlstring (url)bodyPublic URL of an attachment.

Possible errors: PUBLIC_API_INVALID_PHONE_NUMBER (422), PUBLIC_API_WAB_ACCOUNT_NOT_FOUND (404)

POST/whatsapp-business/otp/messagebillable

Send a WhatsApp OTP message.

FieldTypeInDescription
whatsappBusinessPhoneNumberId*stringbodyYour WhatsApp sender id.
phoneNumber*string (E.164)bodyRecipient number in international format, e.g. +237670000000.
message*string (≤4096)bodyThe rendered OTP text, e.g. "482913 is your verification code.". It is not sent as-is: it identifies which approved AUTHENTICATION template it came from, and the code is sent as that template.

Possible errors: PUBLIC_API_INVALID_PHONE_NUMBER (422), PUBLIC_API_WAB_ACCOUNT_NOT_FOUND (404), PUBLIC_API_OTP_NO_AUTH_TEMPLATE (422), PUBLIC_API_OTP_TEMPLATE_NO_MATCH (422)

POST/whatsapp-business/template/messagebillable

Send a WhatsApp template message.

FieldTypeInDescription
whatsappBusinessPhoneNumberId*stringbodyYour WhatsApp sender id.
phoneNumber*string (E.164)bodyRecipient number in international format, e.g. +237670000000.
templateId*stringbodyApproved template id.
bodyParametersobject<string,string>bodyBody variable values.

Possible errors: PUBLIC_API_INVALID_PHONE_NUMBER (422), PUBLIC_API_TEMPLATE_NOT_FOUND (404), PUBLIC_API_WAB_ACCOUNT_NOT_FOUND (404)

POST/api-campaign/send-smsbillable

Send one SMS to many contacts (bulk).

FieldTypeInDescription
message*stringbodyMessage body.
contacts*string[] (≤1000)bodyRecipient numbers; invalid ones are skipped and reported.
senderNamestringbodyApproved sender ID.

Possible errors: PUBLIC_API_INVALID_RECIPIENTS (422)

GET/api-campaign/send-smsbillable

Bulk SMS (query-param variant).

FieldTypeInDescription
message*stringqueryMessage body.
contacts*string (comma-separated)queryComma-separated numbers.

Possible errors: PUBLIC_API_INVALID_RECIPIENTS (422)

Delivery status

GET/message/{channel}/status/{msgId}read-only

Get a message delivery status.

FieldTypeInDescription
channel*enum sms|email|whatsapppathChannel of the message.
msgId*stringpathThe msgId returned at send time.

Possible errors: PUBLIC_API_MESSAGE_NOT_FOUND (404)

GET/message/sms/{msgId}read-only

Get full SMS details.

FieldTypeInDescription
msgId*stringpathThe msgId returned at send time.

Possible errors: PUBLIC_API_MESSAGE_NOT_FOUND (404)

Account & usage

GET/integration/usageread-only

Usage & quota snapshot.

GET/integration/walletread-only

Wallet balance & recent transactions.

GET/integration/canalsread-only

List your channels.

FieldTypeInDescription
typeenum CanalTypequeryFilter by channel type.
limitnumberqueryPage size (default 50).

Resources

POST/integration/campaignsbillable

Create and launch a campaign.

FieldTypeInDescription
campaignSpecId*uuidbodyMessage template id.
profileIds*uuid[]bodyContact list ids.
frequency*enum CampaignFrequencybodySend cadence.
POST/integration/campaign-specsbillable

Create a message template.

FieldTypeInDescription
name*string (≤200)bodyTemplate name.
type*enum CampaignSpecTypebodyChannel type (SMS, EMAIL…).
configobjectbodyChannel-specific config.
POST/integration/campaign-profilesbillable

Create a contact list.

FieldTypeInDescription
name*string (≤200)bodyList name.
type*enum CampaignProfileTypebodyList source type.
campaignSpecType*enum CampaignSpecTypebodyChannel the list targets.
POST/integration/campaign-profiles/{id}/contacts/importbillable

Import contacts into a list.

FieldTypeInDescription
id*uuidpathContact list id.
contacts*object[] (≤10000)bodyArray of { phoneNumber?, email?, parameters? }.
POST/integration/ticketsbillable

Create a ticket.

FieldTypeInDescription
canalType*enum TicketCanalTypebodyChannel of the ticket.
descriptionstring (≤2000)bodyTicket description.
POST/integration/contactsbillable

Create a contact.

FieldTypeInDescription
namestringbodyContact name.
phoneNumbersstring[]bodyPhone numbers.
emailsstring[] (email)bodyEmail addresses.
POST/integration/groupsbillable

Create a contact group.

FieldTypeInDescription
name*stringbodyGroup name.

One-time passwords

POST/otp/smsbillable

Send an SMS OTP.

FieldTypeInDescription
phoneNumber*string (E.164)bodyRecipient number in international format, e.g. +237670000000.
numberOfDigitsint 4–8bodyCode length (default service value).

Possible errors: PUBLIC_API_INVALID_PHONE_NUMBER (422)

POST/otp/emailbillable

Send an email OTP.

FieldTypeInDescription
email*string (email)bodyRecipient email address.

Possible errors: PUBLIC_API_INVALID_EMAIL (422)

Files & tickets

POST/issuebillable

Create a support ticket (multipart).

FieldTypeInDescription
email*string (email)multipartRecipient email address.
issueCanalType*enum SMS|WHATSAPP|EMAILmultipartReply channel.
issueMessagestringmultipartThe issue text.
filebinarymultipartOptional attachment.

Possible errors: PUBLIC_API_INVALID_EMAIL (422), PUBLIC_API_FILE_TOO_LARGE (413)

Webhooks

Configure per-channel callback URLs in Settings → Organization → API. Every call is a POST signed with X-TextDigo-Signature: sha256=HMAC_SHA256(webhookSecret, rawBody). Verify it against your webhookSecret.

Delivery reports

{ "msgId": "...", "from": "...", "status": "DELIVERED",
  "partCount": 1, "amount": 0.02, "operator": "...",
  "externalId": "...", "currency": "XAF", "subject": "dlr", "type": "sms" }

status ∈ DELIVERED | UNDELIVERED | SENT | FAILED

Inbound messages

{ "event": "inbound-message", "type": "sms", "lineId": "...",
  "phoneNumber": "...", "chatId": "...", "message": "...", "medias": [] }

Error codes

Branch on errorCode, not on the human message (which is localized). Codes migrating from the legacy Meteor API show their old numeric code.

Authentication

CodeHTTPMeaningHow to fix
API_KEY_MISSING_CREDENTIALS401Missing API credentials.No Authorization header (or token) was supplied.Send Authorization: Basic base64(apiLogin:apiKey).
API_KEY_INVALID_CREDENTIALS401Invalid API credentials.The apiLogin/apiKey pair does not match any account.Copy the keys from Settings → Organization → API.
API_KEY_ORG_NOT_ACTIVE403Organization is not active.The account is suspended or cancelled.Reactivate the account or contact support.

Validation

CodeHTTPMeaningHow to fix
VALIDATION_FAILED422One or more fields are invalid.A field is missing, the wrong type, too long, or unknown.Read the per-field details[] array in the response.
PUBLIC_API_INVALID_PHONE_NUMBERwas #3001422The phone number is not a valid international number.The number is not parseable in E.164.Use the full international form, e.g. +237670000000.
PUBLIC_API_UNSUPPORTED_PHONE_NUMBERwas #3002422No active operator serves this number.The destination country/operator is not enabled on your account.Check your enabled coverage or contact support.
PUBLIC_API_INVALID_EMAILwas #3003422The email address is not valid.The address failed format validation.Provide a syntactically valid address.
PUBLIC_API_INVALID_RECIPIENTS422None of the supplied recipients are valid.Every number in a bulk request failed validation.Send at least one valid E.164 number.
OTP_EXPIRED400The OTP code has expired.The validity window elapsed before verification.Request a new OTP and verify it sooner.
OTP_TOO_MANY_ATTEMPTS400Too many failed OTP attempts.The code was entered incorrectly too many times.Request a fresh OTP to reset the attempt counter.
OTP_INVALID_CODE400Invalid OTP code.The submitted code does not match the pending OTP.Re-enter the exact code that was sent.
PUBLIC_API_OTP_TEMPLATE_NO_MATCH422The message matches none of this sender’s approved AUTHENTICATION templates.The wording you send must match an approved template body so the placeholder values can be recovered from it.Align your wording with the approved template, or add your variant to that template’s accepted wordings.
PUBLIC_API_FILE_REQUIREDwas #101422A file is required.The multipart request carried no file part.Attach the file under the "file" field.
PUBLIC_API_FILE_TOO_LARGE413The file exceeds the maximum size.The upload is over the per-file limit.Compress or split the file under the limit.

Resources

CodeHTTPMeaningHow to fix
PUBLIC_API_MESSAGE_NOT_FOUND404Message not found.The msgId does not belong to your account.Use the msgId returned by the send call.
PUBLIC_API_OTP_CONTACT_NOT_FOUND404No pending OTP for this contact.No OTP was sent, or it expired.Send a new OTP before verifying.
OTP_NOT_FOUND404No OTP record was found.No OTP was generated for this contact, or it was already consumed.Send a new OTP before verifying.
OTP_CONTACT_NOT_FOUND404The contact was not found.The supplied contactId does not belong to your account.Omit contactId to match by phone/email, or use a valid id.
PUBLIC_API_WAB_ACCOUNT_NOT_FOUND404No WhatsApp Business sender matches the id.whatsappBusinessPhoneNumberId is wrong or not connected.Use an id from your connected WhatsApp channels.
PUBLIC_API_TEMPLATE_NOT_FOUND404Template not found.The templateId is unknown in your account.List your templates and use a valid id.
PUBLIC_API_OTP_NO_AUTH_TEMPLATE422This WhatsApp sender has no approved AUTHENTICATION template.WhatsApp only delivers OTP messages as an approved AUTHENTICATION template, and this sender has none.Create an AUTHENTICATION template and get it approved by Meta, then retry.
PUBLIC_API_OTP_TEMPLATE_PARAM_MISMATCH422No AUTHENTICATION template of this sender takes exactly one parameter.We generate the code ourselves, so the template body must carry that one value and nothing else.Approve an AUTHENTICATION template whose body has a single placeholder, or send your own text to /whatsapp-business/otp/message instead.
PUBLIC_API_CANAL_NOT_ACTIVEwas #501422The target channel is not active.The channel exists but is disabled.Activate the channel in the dashboard.

Rate limiting

CodeHTTPMeaningHow to fix
RATE_LIMITED429Too many requests.You exceeded the per-key request budget.Back off and retry after a short delay.

Server

CodeHTTPMeaningHow to fix
INTERNAL_ERRORwas #100500Unexpected server error.Something failed on our side.Retry later; contact support if it persists.

Limits & quotas

  • Rate limiting: requests are throttled per API key. Back off on 429 (RATE_LIMITED).
  • Bulk SMS: up to 1000 contacts per /api-campaign/send-sms call.
  • Contact import: up to 10 000 contacts per import call.
  • File uploads are capped by the per-file size limit (PUBLIC_API_FILE_TOO_LARGE otherwise).