Skip to content
TablaDevelopers

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

Parameters of token
NameInTypeRequiredDescription
AuthorizationheaderstringNo

Request body

Content type: application/x-www-form-urlencoded

Fields of token
NameTypeRequiredDescription
client_idstringNo
client_secretstringNo
grant_typestringNo
scopestringNo
Example
client_id=tc_your_client_id&client_secret=ts_your_client_secret&grant_type=client_credentials&scope=messages%3Asend%20messages%3Aread

Responses

200 OK · */* · object

Example
{}

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

Parameters of sendMessage
NameInTypeRequiredDescription
Idempotency-KeyheaderstringNo
1 to 100 printable characters; the same key and body answer the same message

Request body

Content type: application/json · SendRequest

Example
{
  "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

Example
{
  "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.

Errors of sendMessage
StatusDescriptionContent 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

Parameters of sendMessages
NameInTypeRequiredDescription
Idempotency-KeyheaderstringNo
For the whole batch; each message takes <key>#<index>

Request body

Content type: application/json · BatchRequest

Example
{
  "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

Example
{
  "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.

Errors of sendMessages
StatusDescriptionContent 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

Parameters of getMessage
NameInTypeRequiredDescription
messageIdpathstring (uuid)Yes
Message id

Responses

200 OK · */* · MessageResponse

Example
{
  "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.

Errors of getMessage
StatusDescriptionContent 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

get/v1/usage

Messages providers accepted, per Cairo day and channel (92 days at most)

Scope
messages:read
operationId
getUsage

Parameters

Parameters of getUsage
NameInTypeRequiredDescription
fromquerystring (date)Yes
First day, yyyy-MM-dd
toquerystring (date)Yes
Last day, yyyy-MM-dd

Responses

200 OK · */* · UsageResponse

Example
{
  "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.

Errors of getUsage
StatusDescriptionContent 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

Recipients and devices

get/v1/recipients/{recipientId}

Scope
recipients:read
operationId
getRecipient

Parameters

Parameters of getRecipient
NameInTypeRequiredDescription
recipientIdpathstringYes
The tenant's own user id

Responses

200 OK · */* · RecipientResponse

Example
{
  "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.

Errors of getRecipient
StatusDescriptionContent 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

put/v1/recipients/{recipientId}

Creates or replaces a recipient

Scope
recipients:write
operationId
putRecipient

Parameters

Parameters of putRecipient
NameInTypeRequiredDescription
recipientIdpathstringYes
The tenant's own user id

Request body

Content type: application/json · RecipientRequest

Example
{
  "email": "mona@example.com",
  "locale": "ar",
  "phone": "+201001234567",
  "phoneVerified": true,
  "preferences": {},
  "quietHours": {
    "end": "string",
    "start": "string"
  },
  "timeZone": "string"
}

Responses

200 OK · */* · RecipientResponse

Example
{
  "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.

Errors of putRecipient
StatusDescriptionContent 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}/devices

Registers a push token; a token already known for the app moves to this recipient

Scope
recipients:write
operationId
registerDevice

Parameters

Parameters of registerDevice
NameInTypeRequiredDescription
recipientIdpathstringYes
The tenant's own user id

Request body

Content type: application/json · DeviceRequest

Example
{
  "appId": "app.example.android",
  "platform": "string",
  "token": "fcm-device-token…"
}

Responses

201 Created · */* · DeviceResponse

Example
{
  "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.

Errors of registerDevice
StatusDescriptionContent 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}/devices/unregister

Forgets a push token; 204 whether or not it was known

Scope
recipients:write
operationId
unregisterDevice

Parameters

Parameters of unregisterDevice
NameInTypeRequiredDescription
recipientIdpathstringYes
The tenant's own user id

Request body

Content type: application/json · DeviceRemovalRequest

Example
{
  "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.

Errors of unregisterDevice
StatusDescriptionContent 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}/erasure

Deletes the recipient, devices and inbox now and redacts their messages (ADR 0008)

Scope
erasure:write
operationId
eraseRecipient

Parameters

Parameters of eraseRecipient
NameInTypeRequiredDescription
recipientIdpathstringYes
The tenant's own user id

Responses

202 Accepted · */* · ErasureResponse

Example
{
  "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.

Errors of eraseRecipient
StatusDescriptionContent 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

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

Parameters of listInbox
NameInTypeRequiredDescription
recipientIdpathstringYes
The tenant's own user id
cursorquerystringNo
From the previous page's nextmaxLength 200
beforequerystring (date-time)No
Instead of cursor: the last item's createdAt (ISO 8601, e.g. 2026-10-05T10:00:00.123Z)
beforeIdquerystring (uuid)No
With before: the last item's id
limitqueryinteger (int32)No
Page size, 1 to 100min 1 · max 100 · default 20
unreadOnlyquerybooleanNo
Only unread itemsdefault false

Responses

200 OK · */* · InboxPage

Example
{
  "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.

Errors of listInbox
StatusDescriptionContent 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/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

Parameters of markInboxItemReadByMessage
NameInTypeRequiredDescription
recipientIdpathstringYes
The tenant's own user id
messageIdpathstring (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.

Errors of markInboxItemReadByMessage
StatusDescriptionContent 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

Parameters of markInboxRead
NameInTypeRequiredDescription
recipientIdpathstringYes
The tenant's own user id

Responses

200 OK · */* · MarkedRead

Example
{
  "marked": 1
}

Errors

Problem details (application/problem+json) with a stable code. The common codes are explained under Errors, in Arabic and English.

Errors of markInboxRead
StatusDescriptionContent 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

get/v1/recipients/{recipientId}/inbox/unread-count

Scope
inbox:read
operationId
countUnreadInbox

Parameters

Parameters of countUnreadInbox
NameInTypeRequiredDescription
recipientIdpathstringYes
The tenant's own user id

Responses

200 OK · */* · UnreadCount

Example
{
  "unread": 1
}

Errors

Problem details (application/problem+json) with a stable code. The common codes are explained under Errors, in Arabic and English.

Errors of countUnreadInbox
StatusDescriptionContent 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/{itemId}/read

Scope
inbox:write
operationId
markInboxItemRead

Parameters

Parameters of markInboxItemRead
NameInTypeRequiredDescription
recipientIdpathstringYes
The tenant's own user id
itemIdpathstring (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.

Errors of markInboxItemRead
StatusDescriptionContent 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

Templates

get/v1/templates

Templates by key, 50 a page by default

Scope
templates:read
operationId
listTemplates

Parameters

Parameters of listTemplates
NameInTypeRequiredDescription
afterquerystringNo
The last key of the previous pagemaxLength 100
limitqueryinteger (int32)No
Page size, 1 to 200min 1 · max 200 · default 50

Responses

200 OK · */* · TemplatePage

Example
{
  "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.

Errors of listTemplates
StatusDescriptionContent 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

get/v1/templates/{key}

The current version

Scope
templates:read
operationId
getTemplate

Parameters

Parameters of getTemplate
NameInTypeRequiredDescription
keypathstringYes
Template key

Responses

200 OK · */* · TemplateResponse

Example
{
  "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.

Errors of getTemplate
StatusDescriptionContent 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

put/v1/templates/{key}

Creates the template (201) or, when the content changed, its next version (200)

Scope
templates:write
operationId
putTemplate

Parameters

Parameters of putTemplate
NameInTypeRequiredDescription
keypathstringYes
e.g. booking.confirmed

Request body

Content type: application/json · TemplateRequest

Example
{
  "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

Example
{
  "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.

Errors of putTemplate
StatusDescriptionContent 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

get/v1/templates/{key}/versions/{version}

Scope
templates:read
operationId
getTemplateVersion

Parameters

Parameters of getTemplateVersion
NameInTypeRequiredDescription
keypathstringYes
Template key
versionpathinteger (int32)Yes
Version number

Responses

200 OK · */* · TemplateResponse

Example
{
  "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.

Errors of getTemplateVersion
StatusDescriptionContent 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

Schemas

AcceptedResponse

An accepted message

Fields of AcceptedResponse
NameTypeRequiredDescription
idstring (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

Fields of AttemptResponse
NameTypeRequiredDescription
addressstringNo
Masked address
atstring (date-time)No
Last change
attemptinteger (int32)No
Attempt number on that channel
channelstringNo
Channel
errorstringNo
Why it failed or was skipped
providerstringNo
Provider
statusstringNo
SENDING, SENT, DELIVERED, FAILED, SKIPPED or EXPIRED
willRetrybooleanNo
Whether a retry is scheduled

BatchRequest

Up to 500 messages, accepted all or none

Fields of BatchRequest
NameTypeRequiredDescription
messagesarray of SendRequestYes
The messagesminItems 1 · maxItems 500

BatchResponse

The accepted messages, in request order

Fields of BatchResponse
NameTypeRequiredDescription
messagesarray of AcceptedResponseNo
One per message

ChannelState

One channel of one leg

Fields of ChannelState
NameTypeRequiredDescription
channel"PUSH" | "SMS" | "EMAIL" | "WHATSAPP" | "INBOX"No
Channel
leginteger (int32)No
Leg
statusstringNo
QUEUED, SENT, DELIVERED, FAILED, SKIPPED or EXPIRED

DeviceRemovalRequest

A push token to forget, e.g. on sign-out

Fields of DeviceRemovalRequest
NameTypeRequiredDescription
appIdstringYes
The tenant app's idmaxLength 150
tokenstringYes
The FCM registration tokenmaxLength 512

DeviceRequest

A push token the tenant's app reported for this user

Fields of DeviceRequest
NameTypeRequiredDescription
appIdstringYes
The tenant app's id, as registered with Tabla for pushmaxLength 150
platformstringYes
ANDROID, IOS or WEBminLength 1
tokenstringYes
The FCM registration tokenmaxLength 512

DeviceResponse

The registered device

Fields of DeviceResponse
NameTypeRequiredDescription
idstring (uuid)No
Tabla's device id

EmailBody

An e-mail: a subject, and HTML, plain text or both

Fields of EmailBody
NameTypeRequiredDescription
htmlstringNo
HTML partmaxLength 12000
subjectstringYes
SubjectmaxLength 200
textstringNo
Plain-text partmaxLength 12000

EmailLocales

Arabic and English; at least one

Fields of EmailLocales
NameTypeRequiredDescription
arEmailBodyNo
Egyptian Arabic
enEmailBodyNo
English

ErasureResponse

The recipient is gone; their messages are redacted shortly after

Fields of ErasureResponse
NameTypeRequiredDescription
erasedAtstring (date-time)No
When it was erased
idstringNo
The tenant's id

InboxItemResponse

An inbox item, rendered in the recipient's language when it arrived

Fields of InboxItemResponse
NameTypeRequiredDescription
bodystringNo
Body
category"OTP" | "TRANSACTIONAL" | "MARKETING"No
Category
createdAtstring (date-time)No
When it arrived
idstring (uuid)No
Item id
linkstringNo
What it opens in the app, as the tenant sent it
messageIdstring (uuid)No
The message it came from; its push carries the same messageId
readAtstring (date-time)No
When it was read; null while unread
templatestringNo
Template key
titlestringNo
Title

InboxPage

A page of inbox items, newest first

Fields of InboxPage
NameTypeRequiredDescription
itemsarray of InboxItemResponseNo
Items
nextstringNo
Cursor of the next page; null on the last

LegResponse

A fallback chain and what happened on it

Fields of LegResponse
NameTypeRequiredDescription
attemptsarray of AttemptResponseNo
Every attempt
channelsarray of "PUSH" | "SMS" | "EMAIL" | "WHATSAPP" | "INBOX"No
The chain
current"PUSH" | "SMS" | "EMAIL" | "WHATSAPP" | "INBOX"No
Where the chain stands
leginteger (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

Fields of MarkedRead
NameTypeRequiredDescription
markedinteger (int32)No
Items marked read

MessageResponse

A message's status, per channel and per attempt

Fields of MessageResponse
NameTypeRequiredDescription
category"OTP" | "TRANSACTIONAL" | "MARKETING"No
Category
channelsarray of ChannelStateNo
Each channel's state
createdAtstring (date-time)No
Accepted at
expiresAtstring (date-time)No
Goes out until
finishedAtstring (date-time)No
Every leg final at; null until then
idstring (uuid)No
Message id
lane"OTP" | "TRANSACTIONAL" | "BULK"No
Lane
legsarray of LegResponseNo
Each leg with its attempts
priority"HIGH" | "NORMAL" | "LOW"No
Priority
recipientIdstringNo
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
templatestringNo
Template key

Problem

RFC 9457 problem details with a stable code

Fields of Problem
NameTypeRequiredDescription
codeobjectNo
Stable error code
detailobjectNo
Localized explanation
statusobjectNo
HTTP status
titleobjectNo
Short summary
typeobjectNo
urn:tabla:problem:<code>

QuietHoursBody

A wall-clock window, may cross midnight, e.g. 22:00 to 08:00

Fields of QuietHoursBody
NameTypeRequiredDescription
endstringYes
HH:mmminLength 1
startstringYes
HH:mmminLength 1

RecipientRef

A registered recipient by the tenant's id, or for OTP templates only an inline phone or e-mail

Fields of RecipientRef
NameTypeRequiredDescription
emailstringNo
OTP only: an e-mail addressmaxLength 254
idstringNo
The tenant's own user idmaxLength 128
localestringNo
OTP only: ar or en; the tenant's default when absent
phonestringNo
OTP only: a mobile numbermaxLength 32

RecipientRequest

Everything Tabla keeps about one of the tenant's users; a PUT replaces every field

Fields of RecipientRequest
NameTypeRequiredDescription
emailstringNo
E-mail addressmaxLength 254
localestringYes
ar or en
phonestringNo
Mobile number; read as Egyptian without a country codemaxLength 32
phoneVerifiedbooleanNo
Whether the tenant verified the phone; non-OTP SMS and WhatsApp need it
preferencesobjectNo
Overrides per category (TRANSACTIONAL, MARKETING) and channel
quietHoursQuietHoursBodyNo
Daily quiet window; none when absent
timeZonestringNo
IANA zone of the recipient's clock; Africa/Cairo when absentmaxLength 40

RecipientResponse

A recipient; contact data is masked

Fields of RecipientResponse
NameTypeRequiredDescription
activeDevicesinteger (int32)No
Push devices still active
createdAtstring (date-time)No
First stored
emailstringNo
Masked e-mail
idstringNo
The tenant's own id
localestringNo
ar or en
phonestringNo
Masked phone
phoneVerifiedbooleanNo
Whether the tenant verified the phone
preferencesobjectNo
Effective preferences, defaults included
quietHoursQuietHoursBodyNo
Quiet window
timeZonestringNo
IANA zone
updatedAtstring (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)

Fields of SendOptions
NameTypeRequiredDescription
appIdstringNo
Limit push to one of the tenant's appsmaxLength 150
channelsarray of "PUSH" | "SMS" | "EMAIL" | "WHATSAPP" | "INBOX"No
Channels each delivered on its ownminItems 0 · maxItems 5
fallbackarray of "PUSH" | "SMS" | "EMAIL" | "WHATSAPP" | "INBOX"No
One fallback chain, tried in order until one takes itminItems 0 · maxItems 5
linkstringNo
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
pushDataobjectNo
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
ttlSecondsinteger (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

Fields of SendRequest
NameTypeRequiredDescription
dataobjectNo
Template variables: strings, numbers (Arabic-Indic digits in Arabic) and booleans
optionsSendOptionsNo
Channels, fallback, priority, TTL; all optional
recipientRecipientRefYes
The recipient
templatestringYes
Template key, e.g. booking.confirmedmaxLength 100

SmsBody

An SMS text

Fields of SmsBody
NameTypeRequiredDescription
textstringYes
TextmaxLength 1600

SmsLocales

Arabic and English; at least one

Fields of SmsLocales
NameTypeRequiredDescription
arSmsBodyNo
Egyptian Arabic
enSmsBodyNo
English

TemplatePage

A page of templates by key

Fields of TemplatePage
NameTypeRequiredDescription
itemsarray of TemplateSummaryNo
Templates
nextstringNo
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

Fields of TemplateRequest
NameTypeRequiredDescription
category"OTP" | "TRANSACTIONAL" | "MARKETING"Yes
OTP, TRANSACTIONAL or MARKETING; fixed after the first version
emailEmailLocalesNo
E-mail; html is HTML-escaped per value
inboxTitleBodyLocalesNo
In-app inbox item
pushTitleBodyLocalesNo
Push notification
smsSmsLocalesNo
SMS
whatsappWhatsAppLocalesNo
A WhatsApp template approved by Meta

TemplateResponse

One version of a template

Fields of TemplateResponse
NameTypeRequiredDescription
category"OTP" | "TRANSACTIONAL" | "MARKETING"No
Category
contentobjectNo
Content per channel and locale
createdAtstring (date-time)No
When this version was written
currentVersioninteger (int32)No
The template's current version
keystringNo
Key
variablesarray of stringNo
Variables a message must supply
versioninteger (int32)No
This version's number

TemplateSummary

A template in a list

Fields of TemplateSummary
NameTypeRequiredDescription
category"OTP" | "TRANSACTIONAL" | "MARKETING"No
Category
currentVersioninteger (int32)No
Current version
keystringNo
Key
updatedAtstring (date-time)No
When the current version was written

TitleBody

A title and a body

Fields of TitleBody
NameTypeRequiredDescription
bodystringYes
BodymaxLength 2000
titlestringYes
TitlemaxLength 200

TitleBodyLocales

Arabic and English; at least one

Fields of TitleBodyLocales
NameTypeRequiredDescription
arTitleBodyNo
Egyptian Arabic
enTitleBodyNo
English

UnreadCount

Unread items

Fields of UnreadCount
NameTypeRequiredDescription
unreadinteger (int64)No
How many items are unread

UsageDay

One day and channel

Fields of UsageDay
NameTypeRequiredDescription
channelstringNo
Channel
daystringNo
Cairo day
sentinteger (int64)No
Accepted by the provider

UsageResponse

Messages providers accepted, per Cairo day and channel

Fields of UsageResponse
NameTypeRequiredDescription
daysarray of UsageDayNo
Rows
fromstringNo
First day
tostringNo
Last day

WhatsAppBody

A Meta-approved template: name, language, and the variables for its body parameters in order

Fields of WhatsAppBody
NameTypeRequiredDescription
languagestringYes
Language code at Meta, e.g. ar or en_USmaxLength 10
namestringYes
Template name at MetamaxLength 512
paramsarray of stringNo
Variable names, in parameter orderminItems 0 · maxItems 20

WhatsAppLocales

Arabic and English; at least one

Fields of WhatsAppLocales
NameTypeRequiredDescription
arWhatsAppBodyNo
Arabic template
enWhatsAppBodyNo
English template