Send Message
api.ziett.co/c/v1 /messages
Submit a request to send a single transactional message to a specific recipient. Designed for high-reliability, low-latency programmatic notifications — OTPs, delivery alerts, account events, and other time-sensitive communications that must reach one user immediately.
One endpoint, multiple channels. You choose the channel with channel_type; the rest of the request shape stays the same.
For sending messages to large audiences, see Send Batch Campaign.
Choose your channel
| SMS | ||
|---|---|---|
channel_type | SMS | WHATSAPP |
| Message source | content (free text) or template_id | template_id (a template approved by Meta) |
| Variables | Optional, when using a template | Required whenever the template declares variables |
| Delivery | Carrier network | Meta Cloud API |
How It Works
- Validation — The API authenticates your key, verifies the
messages:sendscope, and validates all fields. - Template resolution — If a
template_idis provided, the template is loaded and its variables are resolved (see Template Variables). - Routing — The recipient's number is analysed to identify the target carrier network and country.
- Pricing — The cost for the specific route and channel is calculated against your plan.
- Funds Check — The system verifies your balance is sufficient. If not, the request is rejected with
402. - Queuing — The message is placed in a high-priority delivery queue and dispatched to the carrier or Meta's Cloud API.
- Response —
202 Acceptedis returned immediately with amessage_idto track the lifecycle.
202 Accepted— not200 OK— is intentional. It means your message has been accepted and queued, not yet delivered. Track final status via webhook or Retrieve Message.
Request
Required scope: apikey:messages:send
Body
Content-Type: application/json
| Field | Type | Required | Description |
|---|---|---|---|
remitter_id | uuid | ✅ | UUID of the Sender ID configured in your organization. Determines the sender name or number displayed to the recipient. |
channel_type | enum | ✅ | Delivery channel. Accepted values: SMS, WHATSAPP. |
target_e164 | string | ✅ | Recipient phone number in E.164 format (e.g., +244990090990). Must include the country code. |
content | string | ⚠️ | Message body for free-text SMS. Max 1600 characters — multi-part messages are concatenated automatically. Not used for WhatsApp. |
template_id | uuid | ⚠️ | UUID of a template to send. Required for WhatsApp. For SMS, use it instead of content. |
variables | object | ❌ | Values for the template's variables. See Template Variables. |
save_contact | object | ❌ | If provided, upserts the recipient into your contact list. See Save Contact Object. |
⚠️ Provide either
contentortemplate_id, depending on the channel — see the channel sections below.
Save Contact Object
When included, the recipient is created or updated in your contacts list at send time — useful for building your audience organically from transactional traffic. Works the same way on every channel.
| Field | Type | Required | Description |
|---|---|---|---|
name | string | ✅ | Full name of the contact. |
email | string | ❌ | Email address. Must be a valid format. |
tags | array[string] | ❌ | Up to 10 tags to assign (e.g., ["Premium", "Angola"]). Created automatically if they don't exist. |
Template Variables
When you send from a template, the variables object fills in its placeholders. Values can be sent literally in values, read from the recipient's contact with mapping, or matched automatically by name. If a value can't be resolved and strict is true (the default), the request fails with a 422.
The full rules (resolution order, contact lookup, key format per channel) are in Templates & Variables.
Sending SMS
Set channel_type to SMS and provide the text with either:
content— free text, sent as-is; ortemplate_id— a saved template, optionally withvariables.
Examples
import httpx
response = httpx.post(
"https://api.ziett.co/c/v1/messages",
headers={
"X-API-KEY": "zk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"Content-Type": "application/json",
},
json={
"remitter_id": "019b4b56-e704-7c30-83a0-527df63c3e00",
"channel_type": "SMS",
"target_e164": "+244990090990",
"content": "Your verification code is 482910. It expires in 5 minutes.",
},
)
if response.status_code == 202:
message_id = response.json()["message_id"]
print(f"Message accepted. Tracking ID: {message_id}")
else:
print(f"Error: {response.json()}")
curl -X POST https://api.ziett.co/c/v1/messages \
-H "Content-Type: application/json" \
-H "X-API-KEY: zk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-d '{
"remitter_id": "019b4b56-e704-7c30-83a0-527df63c3e00",
"channel_type": "SMS",
"target_e164": "+244990090990",
"content": "Your verification code is 482910. It expires in 5 minutes."
}'
const response = await fetch('https://api.ziett.co/c/v1/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-KEY': 'zk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxx',
},
body: JSON.stringify({
remitter_id: '019b4b56-e704-7c30-83a0-527df63c3e00',
channel_type: 'SMS',
target_e164: '+244990090990',
content: 'Your verification code is 482910. It expires in 5 minutes.',
}),
})
const data = await response.json()
if (response.status === 202) {
console.log('Message accepted. Tracking ID:', data.message_id)
} else {
console.error('Error:', data)
}
curl -X POST https://api.ziett.co/c/v1/messages \
-H "Content-Type: application/json" \
-H "X-API-KEY: zk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-d '{
"remitter_id": "019b4b56-e704-7c30-83a0-527df63c3e00",
"template_id": "019c1a2e-5b10-7a44-9d3e-6f0b2c7a91d4",
"channel_type": "SMS",
"target_e164": "+244990090990",
"variables": {
"values": { "code": "482910" }
}
}'
curl -X POST https://api.ziett.co/c/v1/messages \
-H "Content-Type: application/json" \
-H "X-API-KEY: zk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-d '{
"remitter_id": "019b4b56-e704-7c30-83a0-527df63c3e00",
"channel_type": "SMS",
"target_e164": "+244990090990",
"content": "Welcome to Ziett. Your account is now active.",
"save_contact": {
"name": "Carlos David",
"email": "carlos.david@example.com",
"tags": ["New Customer", "Angola"]
}
}'
Sending WhatsApp
Set channel_type to WHATSAPP and provide a template_id. WhatsApp messages are sent through Meta's Cloud API, so the content comes from a template approved by Meta; your request only supplies the values for its variables.
remitter_idfor WhatsApp. Use the Ziett ID of the WhatsApp number linked to your organization — the internal Ziett identifier, not the phone number ID from Meta.
Variable keys
Keys in values and mapping are written as <scope>:<name>, one scope per part of the template:
header:1orheader:<name>— header placeholders.body:1,body:2, … orbody:<name>— body placeholders.button:0,button:1, … — button parameters.
Buttons start at 0. Header and body placeholders are numbered from 1, but the first button is
button:0.
Use numbers for positional templates ({{1}}) and names for named templates ({{customer_name}}). See Templates & Variables for the full table.
Examples
Named template. Body declares {{customer_name}} and {{order_id}}; the customer's name is read from the contact, the order ID is sent literally.
curl -X POST https://api.ziett.co/c/v1/messages \
-H "Content-Type: application/json" \
-H "X-API-KEY: zk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-d '{
"remitter_id": "019b4b56-e704-7c30-83a0-527df63c3e00",
"template_id": "019c2f7d-8a31-7b02-b6c5-1e94d0a37f58",
"channel_type": "WHATSAPP",
"target_e164": "+244990090990",
"variables": {
"values": { "body:order_id": "A-10293" },
"mapping": {
"body:customer_name": { "attribute": "first_name", "default": "customer" }
}
}
}'
import httpx
response = httpx.post(
"https://api.ziett.co/c/v1/messages",
headers={"X-API-KEY": "zk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxx"},
json={
"remitter_id": "019b4b56-e704-7c30-83a0-527df63c3e00",
"template_id": "019c2f7d-8a31-7b02-b6c5-1e94d0a37f58",
"channel_type": "WHATSAPP",
"target_e164": "+244990090990",
"variables": {
"values": {"body:order_id": "A-10293"},
"mapping": {
"body:customer_name": {"attribute": "first_name", "default": "customer"}
},
},
},
)
print(response.status_code, response.json())
const response = await fetch('https://api.ziett.co/c/v1/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-KEY': 'zk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxx',
},
body: JSON.stringify({
remitter_id: '019b4b56-e704-7c30-83a0-527df63c3e00',
template_id: '019c2f7d-8a31-7b02-b6c5-1e94d0a37f58',
channel_type: 'WHATSAPP',
target_e164: '+244990090990',
variables: {
values: { 'body:order_id': 'A-10293' },
mapping: {
'body:customer_name': { attribute: 'first_name', default: 'customer' },
},
},
}),
})
console.log(response.status, await response.json())
Positional template with header and button. Header has {{1}}, body has {{1}} and {{2}}, and the first button is a dynamic URL.
curl -X POST https://api.ziett.co/c/v1/messages \
-H "Content-Type: application/json" \
-H "X-API-KEY: zk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-d '{
"remitter_id": "019b4b56-e704-7c30-83a0-527df63c3e00",
"template_id": "019c3a90-2d44-7e19-a8f3-5b7c60d1e2a6",
"channel_type": "WHATSAPP",
"target_e164": "+244990090990",
"variables": {
"values": {
"header:1": "Order A-10293",
"body:1": "Carlos",
"body:2": "Friday, 18 July",
"button:0": "track/A-10293"
}
}
}'
Lenient mode. With strict set to false, variables that can't be resolved are sent as empty strings instead of rejecting the request.
{
"variables": {
"values": { "body:1": "Carlos" },
"strict": false
}
}
Prefer the default
strict: truefor transactional traffic such as OTPs and payment alerts, where a message with a missing value is worse than a rejected request.
Responses
202 Accepted — Message Queued
The message has been validated, your account debited, and the message placed in the delivery queue. The response is the same for every channel.
{
"message_id": "019b4b56-e704-7c30-83a0-527df63c3e00"
}
| Field | Type | Description |
|---|---|---|
message_id | uuid | Unique identifier for this message. Store it to retrieve delivery status or correlate with webhook events. |
Errors
All errors share the same body shape: code, message, status, trace_id, timestamp and service. Include the trace_id when contacting support.
401 Unauthorized — Invalid API Key
The X-API-KEY header is missing, malformed, or the key has been revoked.
{
"code": "AUTH_INVALID_API_KEY",
"message": "The provided API key is invalid or has been revoked.",
"status": 401,
"trace_id": "a1b2c3d4e5f644358a9e7011c123c831",
"timestamp": "2025-07-14T10:30:00.000000+00:00",
"service": "core"
}
402 Payment Required — Insufficient Funds
Your organization's credit balance is below the cost of this message route.
{
"code": "BILLING_INSUFFICIENT_FUNDS",
"message": "Your account balance is too low to process this request. Please top up your credits.",
"status": 402,
"trace_id": "c109f78ae57744358a9e7011c123c831",
"timestamp": "2025-07-14T10:30:00.000000+00:00",
"service": "billing"
}
422 Unprocessable Entity — Validation Error
One or more fields are missing or contain invalid values. The fields object maps each failing field to a specific message.
{
"code": "VALIDATION_ERROR",
"message": "One or more fields failed validation.",
"status": 422,
"trace_id": "f812a3bc91e044358a9e7011c456d901",
"timestamp": "2025-07-14T10:30:00.000000+00:00",
"service": "core",
"fields": {
"target_e164": "Must be a valid E.164 formatted phone number (e.g., +244990090990).",
"channel_type": "Accepted values are: SMS, WHATSAPP."
}
}
Unresolved template variables. With strict: true (the default), a variable that no strategy could resolve is reported in fields, keyed by the variable key:
{
"code": "VALIDATION_ERROR",
"message": "One or more fields failed validation.",
"status": 422,
"trace_id": "9be41d07a3c244358a9e7011c8f2a105",
"timestamp": "2025-07-14T10:30:00.000000+00:00",
"service": "core",
"fields": {
"variables": "Could not resolve a value for: body:customer_name."
}
}
To fix it, send the value in variables.values, add a mapping with a default, make sure the contact has the attribute, or set strict to false.
Rate Limits
This endpoint is limited to 120 requests per minute per API Key, shared across channels. See Rate Limits & Reliability for backoff strategies and retry guidance.
Related Endpoints
- Retrieve Message — Get the delivery status of a specific message by
message_id. - List Messages — Retrieve paginated history of all messages sent by your organization.
- Send Batch Campaign — Send to a large audience in a single request.
- Templates & Variables — How template variables are resolved.