Inbox reference
Base URL: https://api.inbox.destesi.io
Authentication
Section titled “Authentication”Browser session. Signing in through SSO sets a
host-only __Host-inbox_session cookie on the API host. This is what the web
app uses; send it with credentials included.
Personal Access Token. For the CLI, scripts and MCP, send an identity PAT plus the workspace slug:
Authorization: Bearer idn_pat_…X-Destesi-Workspace: acme-coA PAT acts as its user, with that user’s role and permissions in the workspace. See CLI & API auth for minting one.
Permissions
Section titled “Permissions”Each endpoint below names what it needs:
- view, reply, start, note, assign, quick — the
conversations.view,conversations.reply,conversations.start,conversations.note,conversations.assignandquick_replies.managepermissions. A refusal is403 permission_required. - admin — a workspace owner or admin. A refusal is
403 admin_required. - member — any member of the workspace.
contacts.details never refuses a request. Without it, the parts it covers are
left out of the answer. See Inbox concepts.
Beyond permissions, a non-admin sees only the conversations inside their queue
and connection fences. Anything outside them answers 404.
Vocabularies
Section titled “Vocabularies”| Field | Values |
|---|---|
provider |
whatsapp, messenger |
status |
open, closed |
mode |
automated, human |
audience |
customer, operator |
Message direction |
inbound, outbound |
Message author |
customer, agent, human, merchant_device, system |
Message kind |
text, template, interactive, note, image, video, audio, document, sticker |
Message delivery_state |
received, pending, sent, delivered, read, failed, unknown, internal |
| Channel key | <provider>:<channel_address>, e.g. whatsapp:1234567890 |
operator conversations archive messages a connected app exchanged with your
own staff. They are left out of every list unless you pass audience=operator,
and replying to one is 409 operator_thread.
The conversation object
Section titled “The conversation object”| Field | Meaning |
|---|---|
id |
Conversation id |
provider, channel_address, business_number |
The connection it arrived on |
peer_address, peer_name |
The customer’s address and channel profile name |
peer_phone |
The phone an operator filed for this person, "" if none |
status, mode, audience |
See the vocabularies |
automation_epoch |
Moves on every takeover and hand-back |
answerer |
The app answering automated conversations; "" means people answer |
assignee_user_id, queue_id |
"" when unassigned or in no queue |
unread |
Unread messages for the caller |
last_sequence, last_message_preview, last_message_at |
The latest message |
last_message_state |
Delivery state of the latest message when you sent it; "" when the customer did |
last_inbound_at |
When the customer last wrote |
window_open |
Whether free text can be sent now |
templates, media |
Whether this channel carries templates and attachments |
tags |
The conversation’s tags |
Messages carry id, sequence, direction, author, author_user_id,
kind, body, delivery_state, failure_reason, occurred_at, and, when
present, has_media with media_mime, media_filename and media_size,
reply_to (the quoted message) and reaction (the customer’s emoji).
Endpoints
Section titled “Endpoints”Lists answer {"items": [...], "next_cursor": "..."}; pass cursor to get the
next page. Pages hold 50 by default; limit goes up to 200, and anything
above falls back to 50.
Session
Section titled “Session”| Method | Path | Needs | Purpose |
|---|---|---|---|
GET |
/healthz |
Public | Liveness |
GET |
/v1/sso/callback |
Public | SSO landing; sets the session cookie |
GET |
/v1/auth/me |
Session | The signed-in user, role and permissions |
POST |
/v1/auth/logout |
— | Clears the session. Idempotent |
GET |
/v1/me/products |
Session | Launcher entitlements, from identity |
GET |
/v1/me |
member | Your user id, role, Inbox permissions, and queue and connection fences |
Conversations
Section titled “Conversations”| Method | Path | Needs | Purpose |
|---|---|---|---|
GET |
/v1/conversations |
view | List, most recent first |
POST |
/v1/conversations |
reply + start | Write first: open a conversation with a template |
GET |
/v1/conversations/start-channels |
view | Connections you may write first from |
GET |
/v1/conversations/unread |
view | Unread counts: unread and per view views.{all,mine,queue,closed} |
GET |
/v1/conversations/{id} |
view | One conversation |
GET |
/v1/conversations/{id}/messages |
view | Messages; before_seq, limit |
POST |
/v1/conversations/{id}/read |
view | Mark read, optionally up to sequence |
POST |
/v1/conversations/{id}/messages |
reply | Reply {text} |
POST |
/v1/conversations/{id}/attachments |
reply | Send a file (see below) |
POST |
/v1/conversations/{id}/template-messages |
reply + start | Send a template {name, language, params} |
POST |
/v1/conversations/{id}/notes |
note | Internal note {text} |
POST |
/v1/conversations/{id}/take-over |
reply | Switch to human; self-assign if unassigned |
POST |
/v1/conversations/{id}/hand-back |
reply | Switch to automated |
POST |
/v1/conversations/{id}/close |
reply | Close |
POST |
/v1/conversations/{id}/reopen |
reply | Reopen |
POST |
/v1/conversations/{id}/assign |
assign, or reply for yourself | {user_id?, queue_id?} |
PUT |
/v1/conversations/{id}/tags |
reply | Replace the tags: {tag_ids}, at most 10 |
GET |
/v1/conversations/{id}/messages/{mid}/media |
view | {url, mime, filename, size} for an attachment |
GET /v1/conversations filters, combined with AND:
| Param | Meaning |
|---|---|
view |
mine, queue, closed; anything else lists every open conversation |
queue_id |
With view=queue, only this queue |
q |
Search over name, number, filed phone and last message |
peer |
Exact customer address: every conversation with that person, open or closed, and view is ignored |
tag |
Tag id |
channel |
Channel key; repeat for several |
waiting |
1: open, the customer spoke last, and a person owes the answer |
audience |
operator to list operator conversations |
Use peer, not q, to find the conversation for a phone number. q is a
fuzzy search and never picks “the” conversation.
On assign, an absent field is left unchanged and "" clears it. Assigning
only yourself needs conversations.reply; anything else needs
conversations.assign.
Write first takes
{peer_address, template, language, params, name, provider, channel_address}.
peer_address and template are required;
provider defaults to whatsapp; channel_address is needed only when you
work more than one connection. The number may be typed with spaces, dashes or a
leading +.
Attachments take either a multipart form {file, caption} (the web) or
JSON {drive_file_id, caption, filename} naming a file already in the
workspace’s Drive. Up to 16 MB, caption up to 1024 characters.
Contacts
Section titled “Contacts”| Method | Path | Needs | Purpose |
|---|---|---|---|
GET |
/v1/contacts |
view | One row per person; q, channel, no_phone=1, cursor, limit |
PUT |
/v1/contacts/{peer} |
reply | File a name and/or phone: {name?, phone?} |
GET |
/v1/conversations/{id}/contact |
view | What connected apps know about the person, as {contact} |
GET |
/v1/conversations/{id}/customer |
view | A connected app’s customer sheet for the person |
GET |
/v1/conversations/contact-names?ids= |
view | Display names for up to 100 conversations |
A contact row carries peer_address, name, renamed, phone, providers,
threads, open, first_seen and last_message_at, plus contact (email,
document, address…) when you have contacts.details and an app knows them.
On PUT /v1/contacts/{peer}, an absent field is unchanged and "" clears it.
A phone must have 7 to 15 digits and is stored as digits. The person must have
a conversation you can see.
The contact and customer routes answer 404 contact_not_found /
404 customer_not_found when no app knows the person, when the conversation
is on Messenger, and when you lack contacts.details.
Connected apps
Section titled “Connected apps”| Method | Path | Needs | Purpose |
|---|---|---|---|
GET |
/v1/conversations/order-context?ids= |
view | Order context for up to 100 conversations |
GET |
/v1/conversations/suggestions?ids= |
view | Suggestions from every app set up in the workspace |
POST |
/v1/conversations/{id}/suggestions/{app}/{sid}/open |
view | The app’s page for a suggestion, as a relative path |
POST |
/v1/conversations/{id}/suggestions/{app}/{sid}/dismiss |
reply | Dismiss a suggestion |
No app, or an app that does not answer, gives an empty list rather than an error.
Templates
Section titled “Templates”| Method | Path | Needs | Purpose |
|---|---|---|---|
GET |
/v1/templates |
view | Templates with Meta’s status, required per app, accounts, connected |
POST |
/v1/templates |
admin | Submit a template for Meta’s review |
POST |
/v1/templates/ensure |
admin | Retry creating an app’s required templates: {app} |
POST /v1/templates takes
{name, language, category, body, header, footer, examples, buttons, account_id}:
| Field | Rule |
|---|---|
name |
Lowercase letters, digits and underscores |
language |
Required, e.g. es |
category |
UTILITY or MARKETING |
body |
1–1024 characters, variables written {{1}}, {{2}}… |
examples |
One per variable, in order |
header, footer |
Up to 60 characters each |
buttons |
Up to 3 quick-reply labels, 25 characters each |
account_id |
The WhatsApp Business account; empty uses the first |
It answers 201 with {name, status, id}; the status is Meta’s, usually
PENDING. A required template with no match on the account is MISSING.
Queues, tags and quick replies
Section titled “Queues, tags and quick replies”| Method | Path | Needs | Purpose |
|---|---|---|---|
GET |
/v1/queues |
member | Queues with member_user_ids |
POST |
/v1/queues |
admin | Create {name, color} |
PATCH |
/v1/queues/{id} |
admin | Rename or recolour |
DELETE |
/v1/queues/{id} |
admin | Delete; its conversations stay, in no queue |
PUT |
/v1/queues/{id}/members |
admin | Replace members: {user_ids} |
GET |
/v1/tags |
member | Tags |
POST |
/v1/tags |
admin | Create {name, color} |
PATCH |
/v1/tags/{id} |
admin | Rename or recolour |
DELETE |
/v1/tags/{id} |
admin | Delete; removed from every conversation. 204 |
GET |
/v1/quick-replies |
view | Quick replies |
POST |
/v1/quick-replies |
quick | Create {shortcut, body, image_drive_file_id?}; body may be empty when an image is set |
PATCH |
/v1/quick-replies/{id} |
quick | Change shortcut, body and/or image_drive_file_id ("" removes the image) |
POST |
/v1/quick-replies/image |
quick | Multipart {file}, a JPG or PNG up to 5 MB → {drive_file_id} |
GET |
/v1/quick-replies/{id}/image |
view | The reply’s image bytes |
DELETE |
/v1/quick-replies/{id} |
quick | Delete |
Tag names are 1–40 characters and unique per workspace ignoring case; a colour
is #rgb, #rrggbb or empty. Quick-reply shortcuts are unique per workspace
ignoring case, and a leading / is dropped.
Settings, channels and connection access
Section titled “Settings, channels and connection access”| Method | Path | Needs | Purpose |
|---|---|---|---|
GET |
/v1/settings |
member | The workspace’s Inbox settings |
PUT |
/v1/settings |
admin | Partial update; absent fields stay |
GET |
/v1/channels |
view | Connected accounts, and which ones Inbox receives |
POST |
/v1/channels |
admin | Receive an account: {channel_account_id} |
DELETE |
/v1/channels/{id} |
admin | Stop receiving an account. It stays connected in Connect |
GET |
/v1/channel-access |
admin | The connections Inbox has received on, and who works each |
PUT |
/v1/channel-access/{user_id} |
admin | Replace one member’s connections: {channel_keys} |
Settings fields:
| Field | Meaning |
|---|---|
bot_app |
The answering app; "" means people answer, null means never chosen |
bot_apps |
The apps set up in this workspace (read only) |
greeting_message |
Sent to a newly created conversation |
away_message |
Sent outside business hours, at most every 12 hours per conversation |
business_hours |
{"timezone": "America/Bogota", "days": {"mon": [{"from": "09:00", "to": "18:00"}]}} |
default_queue_id |
The queue new conversations land in |
Weekday keys are sun to sat. Send only the fields you change: echoing the
whole document back would turn a null answerer into an explicit "".
On PUT /v1/channel-access/{user_id}, an empty list lifts the restriction; it
never means “no connections”. Each key must be one GET /v1/channel-access
lists. The answer includes released, the number of that member’s
conversations handed back because they lost the connection.
Assistant
Section titled “Assistant”| Method | Path | Needs | Purpose |
|---|---|---|---|
POST |
/v1/chat |
Session | Streams NDJSON events, ending with done or error |
The request body is {"messages": [{"role": ..., "content": ...}]}, the whole
conversation so far. Its tools are the MCP tools below without the inbox_
prefix, each calling the same route with your permissions. Each turn draws on
the workspace’s credits.
Error codes
Section titled “Error codes”Errors are {"error": "code"}, sometimes with a detail. A credit refusal
uses the suite’s quota envelope, {"error": {"code": "quota_exceeded", ...}}.
| Code | Status | Meaning |
|---|---|---|
missing_session |
401 | No cookie and no bearer |
session_invalid |
401 | Cookie rejected by identity; it is cleared |
unknown_bearer_scheme |
401 | Bearer is not an idn_pat_ PAT |
missing_workspace_header |
401 | PAT sent without X-Destesi-Workspace |
invalid_token |
401 | PAT unknown, or not a member of that workspace |
csrf_rejected |
403 | Cookie mutation from an untrusted origin |
product_forbidden |
403 | The workspace does not let you into Inbox |
permission_required |
403 | Your role lacks the named permission |
admin_required |
403 | Owner or admin only |
not_found |
404 | Unknown id, another workspace’s, or outside your fences |
contact_not_found / customer_not_found |
404 | No app knows this person, or they are hidden from you |
invalid_json |
400 | Body is not JSON |
too_many_ids |
400 | More than 100 conversation ids |
window_closed |
409 | More than 24 hours since the customer last wrote |
no_answering_bot |
409 | Hand back with people answering |
operator_thread |
409 | Replying to an operator conversation |
tag_exists |
409 | A tag with that name already exists |
conflict |
409 | Duplicate, e.g. a quick-reply shortcut |
whatsapp_not_connected |
409 | No WhatsApp account connected for templates |
text_required |
422 | Empty reply or note |
name_required |
422 | Missing template, queue or tag name |
peer_and_template_required |
422 | Write first without a number or template |
channel_cannot_start |
422 | Write first on a channel with no templates (Messenger) |
templates_unsupported |
422 | Template on a channel with no templates |
media_unsupported |
422 | Attachment on a channel with no attachments |
channel_required |
422 | Several connections; name one |
unknown_channel |
422 | That connection is not one you may write from |
no_channel |
422 | No connection to write from |
assignee_out_of_scope |
422 | The assignee does not work this conversation’s connection |
queue_out_of_scope |
422 | Every member of that queue works other connections |
unknown_channel_key |
422 | A channel key Inbox has never received on |
unknown_tag / too_many_tags / tag_ids_required |
422 | Tag set problems |
invalid_name / invalid_color |
422 | Tag name or colour out of range |
invalid_phone |
422 | Not 7–15 digits |
name_or_phone_required |
422 | Contact update with neither field |
shortcut_and_body_required |
422 | Quick reply missing a field |
unknown_drive_file |
422 | Not a file in this workspace’s Drive |
caption_too_long |
422 | Caption over 1024 characters |
invalid_business_hours |
422 | business_hours is not the expected shape |
unknown_bot_app / unknown_app |
422 | Not an app this deployment or workspace has |
account_not_selected |
422 | That WhatsApp Business account is not behind Inbox’s numbers |
invalid_name, invalid_language, invalid_category, invalid_body_length, invalid_header_length, invalid_footer_length, invalid_button_length |
422 | Template outside Meta’s limits; detail explains |
attachment_too_large |
413 | Over 16 MB |
media_expired |
410 | The provider no longer holds a received file that was never copied |
no_media |
404 | The message has no attachment |
quota_exceeded |
429 | The workspace is out of credits for the assistant |
template_rejected |
502 | Meta refused the template; detail says why |
connect_unavailable / provider_error / media_unavailable / upload_failed |
502 | Connect, Meta or Drive failed |
identity_unavailable |
502 | Identity could not be reached |
connect_unconfigured |
503 | This deployment has no Connect wiring |
attachments_disabled |
503 | This deployment has no Drive wiring |
chat_disabled |
503 | No AI model selected for Inbox |
store_unavailable |
503 | No database configured |
A send refused by Connect or the provider passes Connect’s status and body through.
MCP tools
Section titled “MCP tools”Available to an AI agent on Destesi’s hosted MCP server at
https://mcp.destesi.io/sse, and on any mcp binary you run yourself with
INBOX_API_URL set. See MCP. Every tool takes a workspace slug and
runs with your PAT’s permissions.
| Tool | Purpose |
|---|---|
inbox_list_conversations |
List with view, queue_id, q, peer, tag, channel, waiting |
inbox_start_conversation |
Write first with a template |
inbox_get_conversation |
One conversation |
inbox_list_messages |
Messages, paged by before_seq |
inbox_mark_read |
Mark read |
inbox_reply |
Send text; takes the conversation over |
inbox_send_attachment |
Send a Drive file |
inbox_send_template |
Send an approved template |
inbox_add_note |
Internal note |
inbox_take_over |
Pause the bot on a conversation |
inbox_hand_back |
Return a conversation to the bot |
inbox_close_conversation |
Close |
inbox_reopen_conversation |
Reopen |
inbox_assign_conversation |
Assign to a person and/or queue |
inbox_list_contacts |
The contact list |
inbox_rename_contact |
File a contact’s name and/or phone |
inbox_list_templates |
Templates and required templates |
inbox_create_template |
Submit a template for review |
inbox_ensure_templates |
Retry an app’s required templates |
inbox_list_queues |
Queues and members |
inbox_create_queue |
Create a queue |
inbox_update_queue |
Rename or recolour a queue |
inbox_delete_queue |
Delete a queue |
inbox_set_queue_members |
Replace a queue’s members |
inbox_list_tags |
Tags |
inbox_create_tag |
Create a tag |
inbox_update_tag |
Rename or recolour a tag |
inbox_delete_tag |
Delete a tag |
inbox_set_conversation_tags |
Replace a conversation’s tags |
inbox_list_channel_access |
Connections and who works each |
inbox_set_member_channels |
Replace one member’s connections |
inbox_list_quick_replies |
Quick replies |
inbox_create_quick_reply |
Create a quick reply |
inbox_update_quick_reply |
Change a quick reply |
inbox_delete_quick_reply |
Delete a quick reply |
inbox_get_settings |
Settings |
inbox_set_settings |
Change settings |
inbox_list_channels |
Connected accounts and which Inbox receives |
inbox_select_channel |
Make Inbox receive an account |
inbox_deselect_channel |
Stop Inbox receiving an account |
dst inbox needs an identity PAT and a workspace. See CLI for install
and login.
dst inbox conversations --view minedst inbox conversations --waiting --channel whatsapp:1234567890dst inbox conversations --peer "+57 300 111 2233"
dst inbox conversation get <id>dst inbox conversation messages <id> --limit 20dst inbox conversation reply <id> "Your order ships today."dst inbox conversation template <id> --name order_update --language es --param 1234dst inbox conversation attach <id> <drive_file_id> --caption "Invoice"dst inbox conversation note <id> "Customer prefers a call."dst inbox conversation take-over <id> # also: hand-back, close, reopen, readdst inbox conversation assign <id> --user <user_id> --queue ""
dst inbox conversations start "+57 300 111 2233" --template welcome --language es --name "Ana"
dst inbox contacts list --no-phonedst inbox contacts rename <peer> "Ana Gómez"dst inbox contacts phone <peer> "+57 300 111 2233"
dst inbox templates listdst inbox templates create --name order_update --language es --category UTILITY \ --body "Your order {{1}} is on its way" --example 1234dst inbox templates ensure commerce
dst inbox queues list # also: create, update, delete, membersdst inbox tags list # also: create, update, delete, setdst inbox quick-replies list # also: create, update, deletedst inbox settings getdst inbox settings set --greeting "Hi! We'll be right with you."dst inbox channels list # also: select, deselectdst inbox channel-access list # also: set <user_id> [key...], --clearEvery subcommand takes -o json and -w <workspace>. On assign,
settings set, and the update verbs, only the flags you pass are sent, and
"" clears a value. The base URL is derived from your configured API URL and
can be overridden with --inbox-api-url or DESTESI_INBOX_API_URL.