Overview
The Messages API queues outbound SMS and returns 202 Accepted. Custom message bodies must come from a template accepted by Bar9. Submit a named template with all required language variants in Dashboard → Templates, then send its ID, language, and placeholder values. Callers cannot provide arbitrary SMS copy.
It requires a production API key from an account whose production verification has been approved. Test keys use the isolated /v1/test/messages API with the same accepted templates. Credits are debited when a live message is queued. If an automatic prohibited-content rule matches the rendered body, the message is created as pending_review without a debit or provider job; an administrator must approve it before Bar9 atomically charges and queues it. A rejection changes the message to failed and includes the moderation reason.
Timestamps are Unix seconds. Backend reporting windows use GMT+1; browser dashboards render timestamps in the user's local timezone.
POST /v1/message-templates
Submit a template with an authenticated dashboard session or an API key carrying templates:write. A name is required and may contain at most 120 characters. Supply 1 to 10 variants; every language must be unique, every body must contain at most 2,000 characters, and all variants must use the same placeholder names.
{
"name": "Shipping update",
"variants": [
{ "language": "fr", "body": "Bonjour {{name}}, commande {{order_id}} expédiée." },
{ "language": "ar", "body": "مرحبًا {{name}}، تم شحن الطلب {{order_id}}." }
]
}
The response is 201 Created. A new submission has status pending until an administrator accepts or rejects the complete set.
{
"ok": true,
"data": {
"id": "tpl_...",
"name": "Shipping update",
"status": "pending",
"rejection_reason": null,
"submitted_at": 1778407200,
"reviewed_at": null,
"variants": [
{
"language": "fr",
"body": "Bonjour {{name}}, commande {{order_id}} expédiée.",
"variables": ["name", "order_id"]
}
]
}
}
PUT /v1/message-templates/:id
Edit the name and complete set of language variants with the same payload as creation. The template keeps its ID, but every edit clears the previous decision and returns it to pending; it cannot be used again until an administrator accepts the updated copy.
GET /v1/message-templates
List the current user's submissions with a dashboard session or templates:read API key:
curl "https://api.bar9.dev/v1/message-templates?page=1&per_page=25" \
-H "Authorization: Bearer $BAR9_API_KEY"
Items include all variants and the current pending, accepted, or rejected status. Rejected submissions include rejection_reason. Template, campaign, and automatically intercepted-message decisions are handled from one moderation desk. Rejections carry a stable category (prohibited_content, misleading_content, missing_consent, policy_violation, or other) plus administrator detail. Bar9 emails the decision to the submitting account and records delivery success or failure without rolling back the moderation decision. The list uses the standard pagination object described below.
POST /v1/sender-ids
Submit a branded SenderID for review using a production API key with sender-ids:write, or a dashboard session. The multipart request requires the sender name, company, use case, and an INAPI certificate (PDF or supported image, at most 10 MB).
curl https://api.bar9.dev/v1/sender-ids \
-H "Authorization: Bearer $BAR9_API_KEY" \
-F "sender_id=BAR9" \
-F "company=Bar9" \
-F "use_case=Transactional notifications" \
-F "[email protected]"
The response is 201 Created with a pending application. An administrator later accepts or rejects it; rejected applications include the review notes.
GET /v1/sender-ids
List the account's SenderID applications with a production API key carrying sender-ids:read:
curl "https://api.bar9.dev/v1/sender-ids?page=1&per_page=25" \
-H "Authorization: Bearer $BAR9_API_KEY"
The certificate itself is never returned by this account endpoint.
POST /v1/messages
curl https://api.bar9.dev/v1/messages \
-H "Authorization: Bearer $BAR9_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "+213555000000",
"template_id": "tpl_welcome",
"language": "fr",
"variables": { "name": "Amine" }
}'
The response is 202 Accepted and contains the queued message shown in the response example below.
POST /v1/messages/bulk
curl https://api.bar9.dev/v1/messages/bulk \
-H "Authorization: Bearer $BAR9_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"messages": [
{ "to": "+213555000000", "template_id": "tpl_campaign", "language": "fr", "variables": { "first_name": "Amine" } },
{ "to": "+213555000001", "template_id": "tpl_campaign", "language": "ar", "variables": { "first_name": "أمين" } }
]
}'
Bulk requests accept from 1 to 100 message objects. The request is rejected if any item is invalid or the account cannot cover the messages queued immediately. Matched items remain uncharged in pending_review; each is charged separately if approved later. A successful 202 Accepted response contains count and messages:
{
"ok": true,
"data": {
"count": 2,
"messages": [{ "id": "msg_...", "status": "queued", "cost_credits": 5 }]
}
}
Request fields
| Field | Required | Notes |
|---|---|---|
to |
Yes | Algerian mobile number. E.164 (+213...) and local forms work. |
template_id |
Yes | An accepted template owned by the API-key account. |
language |
Yes | One of the accepted template's language tags. |
variables |
Yes | Object whose keys exactly match the variant's placeholders. |
sender_id |
No | Approved sender ID to use for this message. |
Message response
{
"ok": true,
"data": {
"id": "msg_...",
"to": "+213555000000",
"sender": "BAR9",
"body": "Bonjour Amine",
"type": "message",
"status": "queued",
"provider_message_id": null,
"segments": 1,
"cost_credits": 5,
"created_at": 1778407200,
"queued_at": 1778407200,
"sent_at": null,
"delivered_at": null,
"failed_at": null,
"failure_reason": null
}
}
List responses omit body to keep history payloads smaller. Fetch an individual message to retrieve the body.
queued_at is null for pending_review messages. The cost_credits value is the amount that will be charged if an administrator approves the message; it is not a reservation, so approval can return an insufficient-credit error if the balance changes first.
GET /v1/messages
List production messages owned by the API-key account:
curl "https://api.bar9.dev/v1/messages?page=1&per_page=25&status=delivered" \
-H "Authorization: Bearer $BAR9_API_KEY"
The endpoint supports these filters:
| Query | Notes |
|---|---|
page |
Page number. |
per_page |
Items per page. |
status |
Filter by message status. |
type |
Filter by message or otp. |
to |
Filter by recipient number. |
List responses use this envelope:
{
"ok": true,
"data": [{ "id": "msg_...", "status": "delivered" }],
"pagination": {
"limit": 25,
"offset": 0,
"total": 1,
"has_more": false
}
}
page defaults to 1, per_page defaults to 25, and per_page is capped at 100.
GET /v1/messages/:message_id
Fetch one production message owned by the API-key account:
curl https://api.bar9.dev/v1/messages/msg_123 \
-H "Authorization: Bearer $BAR9_API_KEY"
The response uses the full message representation, including body. An unknown ID or a message owned by another account returns 404.
GET /v1/messages/events
This dashboard-session endpoint lists lifecycle events across the current user's messages:
curl "https://api.bar9.dev/v1/messages/events?page=1&per_page=25&event_type=delivered" \
--cookie "$BAR9_SESSION_COOKIE"
| Query | Notes |
|---|---|
page |
Page number; defaults to 1. |
per_page |
Items per page; defaults to 25 and is capped at 100. |
search |
Searches message ID, recipient, sender, and message content. |
event_type |
Filters the lifecycle event type. |
status |
Filters the associated message status. |
Each event includes id, message_id, event_type, status, timestamps, recipient, sender, type, segment count, and response time when available. The response uses the standard list envelope.
Message status values
| Status | Meaning |
|---|---|
pending_review |
An automatic rule matched; no credits or provider job exist yet. |
queued |
The message was accepted and is waiting to send. |
sent |
The provider accepted or submitted the SMS. |
delivered |
The provider reported successful delivery. |
failed |
Sending, delivery, or content moderation rejected the message. |
Common errors
| Status | Typical cause |
|---|---|
400 |
Invalid number, template, language, variables, filters, or bulk size. |
401 |
Missing or invalid API key/session. |
403 |
Wrong environment, missing scope, unapproved production access, or key. |
404 |
Message or template does not exist for the authenticated account. |
409 |
Template state conflicts with the requested operation. |
402 |
Insufficient credits for the message or complete bulk request. |