API reference
Every operation of the Tabla API 1.2.0, generated at build time from the contract the API serves.
Operation and field descriptions come from the contract, in English.
Tabla, the multi-tenant notification engine: send messages by template over push, SMS, e-mail, WhatsApp and an in-app inbox; manage recipients, devices and templates; read statuses and usage. Server to server only, with OAuth 2.0 client credentials. Status webhooks (message.sent, message.delivered, message.failed, message.expired) carry Tabla-Signature: t=<unix>,v1=<hex HMAC-SHA256 of "t.body" with the webhook secret>.
Authentication
post/oauth/token
Client credentials: a 10-minute access token for the tenant API
- Scope
- None: this is how you get a token
- operationId
token
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
Authorization | header | string | No |
Request body
Content type: application/x-www-form-urlencoded
| Name | Type | Required | Description |
|---|---|---|---|
client_id | string | No | |
client_secret | string | No | |
grant_type | string | No | |
scope | string | No |
client_id=tc_your_client_id&client_secret=ts_your_client_secret&grant_type=client_credentials&scope=messages%3Asend%20messages%3AreadResponses
200 OK · */* · object
{}Messages and usage
Status webhooks (message.sent, message.delivered, message.failed, message.expired) are calls Tabla makes to the URL you set in the console. Getting started shows the payload and how to check its signature.
post/v1/messages
Accepts a message for delivery (202); idempotent per key
- Scope
messages:send- operationId
sendMessage
Needs an Idempotency-Key header
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
Idempotency-Key | header | string | No | 1 to 100 printable characters; the same key and body answer the same message |
Request body
Content type: application/json · SendRequest
{
"data": {},
"options": {
"appId": "app.example.android",
"channels": [
"PUSH"
],
"fallback": [
"PUSH"
],
"link": "app://bookings/42",
"priority": "HIGH",
"pushData": {},
"ttlSeconds": 30
},
"recipient": {
"email": "mona@example.com",
"id": "user-42",
"locale": "ar",
"phone": "+201001234567"
},
"template": "booking.confirmed"
}Responses
200 OK · */* · AcceptedResponse
{
"id": "3f6c1a52-8d4e-4b7a-9c2f-5e1d0a9b7c31",
"status": "ACCEPTED"
}Errors
Problem details (application/problem+json) with a stable code. The common codes are explained under Errors, in Arabic and English.
| Status | Description | Content type |
|---|---|---|
401 | Missing, expired or revoked access token | application/problem+jsonProblem |
402 | Billing refused it: BILLING_CAP_REACHED (the month's spending cap; OTP and transactional get a grace of 10% of it), CREDIT_EXHAUSTED (prepaid credit or the credit limit), or PLAN_VOLUME_EXHAUSTED (a plan without overage) | application/problem+jsonProblem |
403 | The token lacks the operation's scope | application/problem+jsonProblem |
429 | The tenant's request budget or quota is spent; see Retry-After | application/problem+jsonProblem |
503 | BILLING_UNAVAILABLE: bulk can't be checked right now; OTP and transactional still go. Retry shortly | application/problem+jsonProblem |
post/v1/messages/batch
Accepts up to 500 messages, all or none (202)
- Scope
messages:send- operationId
sendMessages
Needs an Idempotency-Key header
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
Idempotency-Key | header | string | No | For the whole batch; each message takes <key>#<index> |
Request body
Content type: application/json · BatchRequest
{
"messages": [
{
"data": {},
"options": {
"appId": "app.example.android",
"channels": [
"PUSH"
],
"fallback": [
"PUSH"
],
"link": "app://bookings/42",
"priority": "HIGH",
"pushData": {},
"ttlSeconds": 30
},
"recipient": {
"email": "mona@example.com",
"id": "user-42",
"locale": "ar",
"phone": "+201001234567"
},
"template": "booking.confirmed"
}
]
}Responses
200 OK · */* · BatchResponse
{
"messages": [
{
"id": "3f6c1a52-8d4e-4b7a-9c2f-5e1d0a9b7c31",
"status": "ACCEPTED"
}
]
}Errors
Problem details (application/problem+json) with a stable code. The common codes are explained under Errors, in Arabic and English.
| Status | Description | Content type |
|---|---|---|
401 | Missing, expired or revoked access token | application/problem+jsonProblem |
402 | Billing refused it: BILLING_CAP_REACHED (the month's spending cap; OTP and transactional get a grace of 10% of it), CREDIT_EXHAUSTED (prepaid credit or the credit limit), or PLAN_VOLUME_EXHAUSTED (a plan without overage) | application/problem+jsonProblem |
403 | The token lacks the operation's scope | application/problem+jsonProblem |
429 | The tenant's request budget or quota is spent; see Retry-After | application/problem+jsonProblem |
503 | BILLING_UNAVAILABLE: bulk can't be checked right now; OTP and transactional still go. Retry shortly | application/problem+jsonProblem |
get/v1/messages/{messageId}
A message's status, per channel and attempt
- Scope
messages:read- operationId
getMessage
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
messageId | path | string (uuid) | Yes | Message id |
Responses
200 OK · */* · MessageResponse
{
"category": "OTP",
"channels": [
{
"channel": "PUSH",
"leg": 1,
"status": "string"
}
],
"createdAt": "2026-10-10T17:00:00Z",
"expiresAt": "2026-10-10T17:00:00Z",
"finishedAt": "2026-10-10T17:00:00Z",
"id": "3f6c1a52-8d4e-4b7a-9c2f-5e1d0a9b7c31",
"lane": "OTP",
"legs": [
{
"attempts": [
{
"address": "string",
"at": "2026-10-10T17:00:00Z",
"attempt": 1,
"channel": "string",
"error": "string",
"provider": "string",
"status": "string",
"willRetry": true
}
],
"channels": [
"PUSH"
],
"current": "PUSH",
"leg": 1,
"status": "PENDING"
}
],
"priority": "HIGH",
"recipientId": "user-42",
"status": "ACCEPTED",
"template": "booking.confirmed"
}Errors
Problem details (application/problem+json) with a stable code. The common codes are explained under Errors, in Arabic and English.
get/v1/usage
Messages providers accepted, per Cairo day and channel (92 days at most)
- Scope
messages:read- operationId
getUsage
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
from | query | string (date) | Yes | First day, yyyy-MM-dd |
to | query | string (date) | Yes | Last day, yyyy-MM-dd |
Responses
200 OK · */* · UsageResponse
{
"days": [
{
"channel": "string",
"day": "string",
"sent": 1
}
],
"from": "string",
"to": "string"
}Errors
Problem details (application/problem+json) with a stable code. The common codes are explained under Errors, in Arabic and English.
Recipients and devices
get/v1/recipients/{recipientId}
- Scope
recipients:read- operationId
getRecipient
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
recipientId | path | string | Yes | The tenant's own user id |
Responses
200 OK · */* · RecipientResponse
{
"activeDevices": 1,
"createdAt": "2026-10-10T17:00:00Z",
"email": "mona@example.com",
"id": "user-42",
"locale": "ar",
"phone": "+201001234567",
"phoneVerified": true,
"preferences": {},
"quietHours": {
"end": "string",
"start": "string"
},
"timeZone": "string",
"updatedAt": "2026-10-10T17:00:00Z"
}Errors
Problem details (application/problem+json) with a stable code. The common codes are explained under Errors, in Arabic and English.
put/v1/recipients/{recipientId}
Creates or replaces a recipient
- Scope
recipients:write- operationId
putRecipient
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
recipientId | path | string | Yes | The tenant's own user id |
Request body
Content type: application/json · RecipientRequest
{
"email": "mona@example.com",
"locale": "ar",
"phone": "+201001234567",
"phoneVerified": true,
"preferences": {},
"quietHours": {
"end": "string",
"start": "string"
},
"timeZone": "string"
}Responses
200 OK · */* · RecipientResponse
{
"activeDevices": 1,
"createdAt": "2026-10-10T17:00:00Z",
"email": "mona@example.com",
"id": "user-42",
"locale": "ar",
"phone": "+201001234567",
"phoneVerified": true,
"preferences": {},
"quietHours": {
"end": "string",
"start": "string"
},
"timeZone": "string",
"updatedAt": "2026-10-10T17:00:00Z"
}Errors
Problem details (application/problem+json) with a stable code. The common codes are explained under Errors, in Arabic and English.
post/v1/recipients/{recipientId}/devices
Registers a push token; a token already known for the app moves to this recipient
- Scope
recipients:write- operationId
registerDevice
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
recipientId | path | string | Yes | The tenant's own user id |
Request body
Content type: application/json · DeviceRequest
{
"appId": "app.example.android",
"platform": "string",
"token": "fcm-device-token…"
}Responses
201 Created · */* · DeviceResponse
{
"id": "3f6c1a52-8d4e-4b7a-9c2f-5e1d0a9b7c31"
}Errors
Problem details (application/problem+json) with a stable code. The common codes are explained under Errors, in Arabic and English.
post/v1/recipients/{recipientId}/devices/unregister
Forgets a push token; 204 whether or not it was known
- Scope
recipients:write- operationId
unregisterDevice
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
recipientId | path | string | Yes | The tenant's own user id |
Request body
Content type: application/json · DeviceRemovalRequest
{
"appId": "app.example.android",
"token": "fcm-device-token…"
}Responses
204 No Content
Errors
Problem details (application/problem+json) with a stable code. The common codes are explained under Errors, in Arabic and English.
post/v1/recipients/{recipientId}/erasure
Deletes the recipient, devices and inbox now and redacts their messages (ADR 0008)
- Scope
erasure:write- operationId
eraseRecipient
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
recipientId | path | string | Yes | The tenant's own user id |
Responses
202 Accepted · */* · ErasureResponse
{
"erasedAt": "2026-10-10T17:00:00Z",
"id": "user-42"
}Errors
Problem details (application/problem+json) with a stable code. The common codes are explained under Errors, in Arabic and English.
In-app inbox
get/v1/recipients/{recipientId}/inbox
Newest first, keyset-paged
Items are ordered by createdAt, then id, both descending. Page on with the opaque cursor (the previous page's next), or instead with before and beforeId set to the last item's createdAt and id: the next page is the items after it in that order. before alone gives the items strictly older than that instant. A cursor with before, or beforeId without before, is CURSOR_INVALID.
- Scope
inbox:read- operationId
listInbox
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
recipientId | path | string | Yes | The tenant's own user id |
cursor | query | string | No | From the previous page's nextmaxLength 200 |
before | query | string (date-time) | No | Instead of cursor: the last item's createdAt (ISO 8601, e.g. 2026-10-05T10:00:00.123Z) |
beforeId | query | string (uuid) | No | With before: the last item's id |
limit | query | integer (int32) | No | Page size, 1 to 100min 1 · max 100 · default 20 |
unreadOnly | query | boolean | No | Only unread itemsdefault false |
Responses
200 OK · */* · InboxPage
{
"items": [
{
"body": "See you at 6 PM, Mona.",
"category": "OTP",
"createdAt": "2026-10-10T17:00:00Z",
"id": "3f6c1a52-8d4e-4b7a-9c2f-5e1d0a9b7c31",
"link": "app://bookings/42",
"messageId": "3f6c1a52-8d4e-4b7a-9c2f-5e1d0a9b7c31",
"readAt": "2026-10-10T17:00:00Z",
"template": "booking.confirmed",
"title": "Booking confirmed"
}
],
"next": "string"
}Errors
Problem details (application/problem+json) with a stable code. The common codes are explained under Errors, in Arabic and English.
post/v1/recipients/{recipientId}/inbox/by-message/{messageId}/read
Marks read the item a message made
For a tapped push, which carries the messageId but not the item id. Idempotent: an item already read keeps its readAt. 404 INBOX_ITEM_NOT_FOUND when the recipient has no inbox item for that message.
- Scope
inbox:write- operationId
markInboxItemReadByMessage
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
recipientId | path | string | Yes | The tenant's own user id |
messageId | path | string (uuid) | Yes | Message id, as in the push's messageId |
Responses
204 No Content
Errors
Problem details (application/problem+json) with a stable code. The common codes are explained under Errors, in Arabic and English.
| Status | Description | Content type |
|---|---|---|
401 | Missing, expired or revoked access token | application/problem+jsonProblem |
403 | The token lacks the operation's scope | application/problem+jsonProblem |
429 | The tenant's request budget or quota is spent; see Retry-After | application/problem+jsonProblem |
post/v1/recipients/{recipientId}/inbox/read-all
Marks every unread item read
- Scope
inbox:write- operationId
markInboxRead
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
recipientId | path | string | Yes | The tenant's own user id |
Responses
200 OK · */* · MarkedRead
{
"marked": 1
}Errors
Problem details (application/problem+json) with a stable code. The common codes are explained under Errors, in Arabic and English.
get/v1/recipients/{recipientId}/inbox/unread-count
- Scope
inbox:read- operationId
countUnreadInbox
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
recipientId | path | string | Yes | The tenant's own user id |
Responses
200 OK · */* · UnreadCount
{
"unread": 1
}Errors
Problem details (application/problem+json) with a stable code. The common codes are explained under Errors, in Arabic and English.
post/v1/recipients/{recipientId}/inbox/{itemId}/read
- Scope
inbox:write- operationId
markInboxItemRead
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
recipientId | path | string | Yes | The tenant's own user id |
itemId | path | string (uuid) | Yes | Inbox item id |
Responses
204 No Content
Errors
Problem details (application/problem+json) with a stable code. The common codes are explained under Errors, in Arabic and English.
Templates
get/v1/templates
Templates by key, 50 a page by default
- Scope
templates:read- operationId
listTemplates
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
after | query | string | No | The last key of the previous pagemaxLength 100 |
limit | query | integer (int32) | No | Page size, 1 to 200min 1 · max 200 · default 50 |
Responses
200 OK · */* · TemplatePage
{
"items": [
{
"category": "OTP",
"currentVersion": 1,
"key": "booking.confirmed",
"updatedAt": "2026-10-10T17:00:00Z"
}
],
"next": "string"
}Errors
Problem details (application/problem+json) with a stable code. The common codes are explained under Errors, in Arabic and English.
get/v1/templates/{key}
The current version
- Scope
templates:read- operationId
getTemplate
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
key | path | string | Yes | Template key |
Responses
200 OK · */* · TemplateResponse
{
"category": "OTP",
"content": {},
"createdAt": "2026-10-10T17:00:00Z",
"currentVersion": 1,
"key": "booking.confirmed",
"variables": [
"string"
],
"version": 1
}Errors
Problem details (application/problem+json) with a stable code. The common codes are explained under Errors, in Arabic and English.
put/v1/templates/{key}
Creates the template (201) or, when the content changed, its next version (200)
- Scope
templates:write- operationId
putTemplate
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
key | path | string | Yes | e.g. booking.confirmed |
Request body
Content type: application/json · TemplateRequest
{
"category": "OTP",
"email": {
"ar": {
"html": "string",
"subject": "Your booking is confirmed",
"text": "Your code is 123456"
},
"en": {
"html": "string",
"subject": "Your booking is confirmed",
"text": "Your code is 123456"
}
},
"inbox": {
"ar": {
"body": "See you at 6 PM, Mona.",
"title": "Booking confirmed"
},
"en": {
"body": "See you at 6 PM, Mona.",
"title": "Booking confirmed"
}
},
"push": {
"ar": {
"body": "See you at 6 PM, Mona.",
"title": "Booking confirmed"
},
"en": {
"body": "See you at 6 PM, Mona.",
"title": "Booking confirmed"
}
},
"sms": {
"ar": {
"text": "Your code is 123456"
},
"en": {
"text": "Your code is 123456"
}
},
"whatsapp": {
"ar": {
"language": "ar",
"name": "booking_confirmed",
"params": [
"string"
]
},
"en": {
"language": "ar",
"name": "booking_confirmed",
"params": [
"string"
]
}
}
}Responses
200 OK · */* · TemplateResponse
{
"category": "OTP",
"content": {},
"createdAt": "2026-10-10T17:00:00Z",
"currentVersion": 1,
"key": "booking.confirmed",
"variables": [
"string"
],
"version": 1
}Errors
Problem details (application/problem+json) with a stable code. The common codes are explained under Errors, in Arabic and English.
get/v1/templates/{key}/versions/{version}
- Scope
templates:read- operationId
getTemplateVersion
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
key | path | string | Yes | Template key |
version | path | integer (int32) | Yes | Version number |
Responses
200 OK · */* · TemplateResponse
{
"category": "OTP",
"content": {},
"createdAt": "2026-10-10T17:00:00Z",
"currentVersion": 1,
"key": "booking.confirmed",
"variables": [
"string"
],
"version": 1
}Errors
Problem details (application/problem+json) with a stable code. The common codes are explained under Errors, in Arabic and English.
Schemas
AcceptedResponse
An accepted message
| Name | Type | Required | Description |
|---|---|---|---|
id | string (uuid) | No | Message id |
status | "ACCEPTED" | "QUEUED" | "SENT" | "DELIVERED" | "FAILED" | "EXPIRED" | No | ACCEPTED, or its current status when replayed |
AttemptResponse
One attempt on one channel
| Name | Type | Required | Description |
|---|---|---|---|
address | string | No | Masked address |
at | string (date-time) | No | Last change |
attempt | integer (int32) | No | Attempt number on that channel |
channel | string | No | Channel |
error | string | No | Why it failed or was skipped |
provider | string | No | Provider |
status | string | No | SENDING, SENT, DELIVERED, FAILED, SKIPPED or EXPIRED |
willRetry | boolean | No | Whether a retry is scheduled |
BatchRequest
Up to 500 messages, accepted all or none
| Name | Type | Required | Description |
|---|---|---|---|
messages | array of SendRequest | Yes | The messagesminItems 1 · maxItems 500 |
BatchResponse
The accepted messages, in request order
| Name | Type | Required | Description |
|---|---|---|---|
messages | array of AcceptedResponse | No | One per message |
ChannelState
One channel of one leg
| Name | Type | Required | Description |
|---|---|---|---|
channel | "PUSH" | "SMS" | "EMAIL" | "WHATSAPP" | "INBOX" | No | Channel |
leg | integer (int32) | No | Leg |
status | string | No | QUEUED, SENT, DELIVERED, FAILED, SKIPPED or EXPIRED |
DeviceRemovalRequest
A push token to forget, e.g. on sign-out
| Name | Type | Required | Description |
|---|---|---|---|
appId | string | Yes | The tenant app's idmaxLength 150 |
token | string | Yes | The FCM registration tokenmaxLength 512 |
DeviceRequest
A push token the tenant's app reported for this user
| Name | Type | Required | Description |
|---|---|---|---|
appId | string | Yes | The tenant app's id, as registered with Tabla for pushmaxLength 150 |
platform | string | Yes | ANDROID, IOS or WEBminLength 1 |
token | string | Yes | The FCM registration tokenmaxLength 512 |
DeviceResponse
The registered device
| Name | Type | Required | Description |
|---|---|---|---|
id | string (uuid) | No | Tabla's device id |
EmailBody
An e-mail: a subject, and HTML, plain text or both
| Name | Type | Required | Description |
|---|---|---|---|
html | string | No | HTML partmaxLength 12000 |
subject | string | Yes | SubjectmaxLength 200 |
text | string | No | Plain-text partmaxLength 12000 |
EmailLocales
Arabic and English; at least one
ErasureResponse
The recipient is gone; their messages are redacted shortly after
| Name | Type | Required | Description |
|---|---|---|---|
erasedAt | string (date-time) | No | When it was erased |
id | string | No | The tenant's id |
InboxItemResponse
An inbox item, rendered in the recipient's language when it arrived
| Name | Type | Required | Description |
|---|---|---|---|
body | string | No | Body |
category | "OTP" | "TRANSACTIONAL" | "MARKETING" | No | Category |
createdAt | string (date-time) | No | When it arrived |
id | string (uuid) | No | Item id |
link | string | No | What it opens in the app, as the tenant sent it |
messageId | string (uuid) | No | The message it came from; its push carries the same messageId |
readAt | string (date-time) | No | When it was read; null while unread |
template | string | No | Template key |
title | string | No | Title |
InboxPage
A page of inbox items, newest first
| Name | Type | Required | Description |
|---|---|---|---|
items | array of InboxItemResponse | No | Items |
next | string | No | Cursor of the next page; null on the last |
LegResponse
A fallback chain and what happened on it
| Name | Type | Required | Description |
|---|---|---|---|
attempts | array of AttemptResponse | No | Every attempt |
channels | array of "PUSH" | "SMS" | "EMAIL" | "WHATSAPP" | "INBOX" | No | The chain |
current | "PUSH" | "SMS" | "EMAIL" | "WHATSAPP" | "INBOX" | No | Where the chain stands |
leg | integer (int32) | No | Leg |
status | "PENDING" | "SENT" | "DELIVERED" | "FAILED" | "SKIPPED" | "EXPIRED" | No | PENDING, SENT, DELIVERED, FAILED, SKIPPED or EXPIRED |
MarkedRead
How many items were marked read
| Name | Type | Required | Description |
|---|---|---|---|
marked | integer (int32) | No | Items marked read |
MessageResponse
A message's status, per channel and per attempt
| Name | Type | Required | Description |
|---|---|---|---|
category | "OTP" | "TRANSACTIONAL" | "MARKETING" | No | Category |
channels | array of ChannelState | No | Each channel's state |
createdAt | string (date-time) | No | Accepted at |
expiresAt | string (date-time) | No | Goes out until |
finishedAt | string (date-time) | No | Every leg final at; null until then |
id | string (uuid) | No | Message id |
lane | "OTP" | "TRANSACTIONAL" | "BULK" | No | Lane |
legs | array of LegResponse | No | Each leg with its attempts |
priority | "HIGH" | "NORMAL" | "LOW" | No | Priority |
recipientId | string | No | The tenant's recipient id; null for an inline OTP or an erased recipient |
status | "ACCEPTED" | "QUEUED" | "SENT" | "DELIVERED" | "FAILED" | "EXPIRED" | No | ACCEPTED, QUEUED, SENT, DELIVERED, FAILED or EXPIRED |
template | string | No | Template key |
Problem
RFC 9457 problem details with a stable code
| Name | Type | Required | Description |
|---|---|---|---|
code | object | No | Stable error code |
detail | object | No | Localized explanation |
status | object | No | HTTP status |
title | object | No | Short summary |
type | object | No | urn:tabla:problem:<code> |
QuietHoursBody
A wall-clock window, may cross midnight, e.g. 22:00 to 08:00
| Name | Type | Required | Description |
|---|---|---|---|
end | string | Yes | HH:mmminLength 1 |
start | string | Yes | HH:mmminLength 1 |
RecipientRef
A registered recipient by the tenant's id, or for OTP templates only an inline phone or e-mail
| Name | Type | Required | Description |
|---|---|---|---|
email | string | No | OTP only: an e-mail addressmaxLength 254 |
id | string | No | The tenant's own user idmaxLength 128 |
locale | string | No | OTP only: ar or en; the tenant's default when absent |
phone | string | No | OTP only: a mobile numbermaxLength 32 |
RecipientRequest
Everything Tabla keeps about one of the tenant's users; a PUT replaces every field
| Name | Type | Required | Description |
|---|---|---|---|
email | string | No | E-mail addressmaxLength 254 |
locale | string | Yes | ar or en |
phone | string | No | Mobile number; read as Egyptian without a country codemaxLength 32 |
phoneVerified | boolean | No | Whether the tenant verified the phone; non-OTP SMS and WhatsApp need it |
preferences | object | No | Overrides per category (TRANSACTIONAL, MARKETING) and channel |
quietHours | QuietHoursBody | No | Daily quiet window; none when absent |
timeZone | string | No | IANA zone of the recipient's clock; Africa/Cairo when absentmaxLength 40 |
RecipientResponse
A recipient; contact data is masked
| Name | Type | Required | Description |
|---|---|---|---|
activeDevices | integer (int32) | No | Push devices still active |
createdAt | string (date-time) | No | First stored |
email | string | No | Masked e-mail |
id | string | No | The tenant's own id |
locale | string | No | ar or en |
phone | string | No | Masked phone |
phoneVerified | boolean | No | Whether the tenant verified the phone |
preferences | object | No | Effective preferences, defaults included |
quietHours | QuietHoursBody | No | Quiet window |
timeZone | string | No | IANA zone |
updatedAt | string (date-time) | No | Last replaced |
SendOptions
How to deliver. Neither channels nor fallback: auto (inbox plus push, e-mail, WhatsApp, SMS as a fallback chain, over the channels the template has content for)
| Name | Type | Required | Description |
|---|---|---|---|
appId | string | No | Limit push to one of the tenant's appsmaxLength 150 |
channels | array of "PUSH" | "SMS" | "EMAIL" | "WHATSAPP" | "INBOX" | No | Channels each delivered on its ownminItems 0 · maxItems 5 |
fallback | array of "PUSH" | "SMS" | "EMAIL" | "WHATSAPP" | "INBOX" | No | One fallback chain, tried in order until one takes itminItems 0 · maxItems 5 |
link | string | No | What the push and inbox item open in the appmaxLength 500 |
priority | "HIGH" | "NORMAL" | "LOW" | No | HIGH, NORMAL (default) or LOW; LOW transactional goes to the bulk lane |
pushData | object | No | The tenant's own keys for its app, sent in the push's data payload beside Tabla's messageId, template and link. Flat strings: at most 20 keys matching [a-zA-Z][a-zA-Z0-9_]{0,39}, values up to 256 characters, 2048 bytes (UTF-8) of keys and values in all. Reserved, in any case: messageId, template, link, from, collapse_key, message_type, notification, aps, and keys starting tabla, google or gcm (MESSAGE_PUSH_DATA_RESERVED). Kept with the message, so retries carry it, and redacted with the body after 30 days. Push only: ids and routing keys, never personal data, as it passes through Google and Apple |
ttlSeconds | integer (int32) | No | Seconds the message may still go out; OTP 30 to 1800, others 30 to 604800min 30 · max 604800 |
SendRequest
One message: who, which template, which data, and how
| Name | Type | Required | Description |
|---|---|---|---|
data | object | No | Template variables: strings, numbers (Arabic-Indic digits in Arabic) and booleans |
options | SendOptions | No | Channels, fallback, priority, TTL; all optional |
recipient | RecipientRef | Yes | The recipient |
template | string | Yes | Template key, e.g. booking.confirmedmaxLength 100 |
SmsBody
An SMS text
| Name | Type | Required | Description |
|---|---|---|---|
text | string | Yes | TextmaxLength 1600 |
SmsLocales
Arabic and English; at least one
TemplatePage
A page of templates by key
| Name | Type | Required | Description |
|---|---|---|---|
items | array of TemplateSummary | No | Templates |
next | string | No | Pass as `after` for the next page; null on the last |
TemplateRequest
A template's content: per channel, per locale. Text may use {{variable}} placeholders only
| Name | Type | Required | Description |
|---|---|---|---|
category | "OTP" | "TRANSACTIONAL" | "MARKETING" | Yes | OTP, TRANSACTIONAL or MARKETING; fixed after the first version |
email | EmailLocales | No | E-mail; html is HTML-escaped per value |
inbox | TitleBodyLocales | No | In-app inbox item |
push | TitleBodyLocales | No | Push notification |
sms | SmsLocales | No | SMS |
whatsapp | WhatsAppLocales | No | A WhatsApp template approved by Meta |
TemplateResponse
One version of a template
| Name | Type | Required | Description |
|---|---|---|---|
category | "OTP" | "TRANSACTIONAL" | "MARKETING" | No | Category |
content | object | No | Content per channel and locale |
createdAt | string (date-time) | No | When this version was written |
currentVersion | integer (int32) | No | The template's current version |
key | string | No | Key |
variables | array of string | No | Variables a message must supply |
version | integer (int32) | No | This version's number |
TemplateSummary
A template in a list
| Name | Type | Required | Description |
|---|---|---|---|
category | "OTP" | "TRANSACTIONAL" | "MARKETING" | No | Category |
currentVersion | integer (int32) | No | Current version |
key | string | No | Key |
updatedAt | string (date-time) | No | When the current version was written |
TitleBody
A title and a body
| Name | Type | Required | Description |
|---|---|---|---|
body | string | Yes | BodymaxLength 2000 |
title | string | Yes | TitlemaxLength 200 |
TitleBodyLocales
Arabic and English; at least one
UnreadCount
Unread items
| Name | Type | Required | Description |
|---|---|---|---|
unread | integer (int64) | No | How many items are unread |
UsageDay
One day and channel
| Name | Type | Required | Description |
|---|---|---|---|
channel | string | No | Channel |
day | string | No | Cairo day |
sent | integer (int64) | No | Accepted by the provider |
UsageResponse
Messages providers accepted, per Cairo day and channel
| Name | Type | Required | Description |
|---|---|---|---|
days | array of UsageDay | No | Rows |
from | string | No | First day |
to | string | No | Last day |
WhatsAppBody
A Meta-approved template: name, language, and the variables for its body parameters in order
| Name | Type | Required | Description |
|---|---|---|---|
language | string | Yes | Language code at Meta, e.g. ar or en_USmaxLength 10 |
name | string | Yes | Template name at MetamaxLength 512 |
params | array of string | No | Variable names, in parameter orderminItems 0 · maxItems 20 |
WhatsAppLocales
Arabic and English; at least one
| Name | Type | Required | Description |
|---|---|---|---|
ar | WhatsAppBody | No | Arabic template |
en | WhatsAppBody | No | English template |