# Messages Messages are individual entries in a conversation. Each carries a `channel` (sms, email, whatsapp, etc.), `direction` (inbound or outbound), and lifecycle timestamps (`sent_at`, `delivered_at`, `read_at`). Outbound messages are routed by `ChannelRouter` to the appropriate provider integration. ## List messages in a conversation `GET /api/conversations/{conversation_id}/messages` Lists the messages of one conversation, oldest first, cursor-paginated by `id`. For infinite scroll, page backwards using `prev_cursor`. ### Path parameters - `conversation_id` (integer, required) — Conversation ID. ### Query parameters - `per_page` (integer) — Rows per page. Default 50, maximum 200. See [Lists, paging and filters](#lists-paging-and-filters). - `cursor` (string) — Opaque cursor from a previous response. ### Example request ```bash curl --request GET \ --get "https://klozzo.com/api/conversations/17/messages?per_page=50&cursor=architecto" \ --header "Authorization: Bearer {YOUR_API_TOKEN}" \ --header "Content-Type: application/json" \ --header "Accept: application/json" ``` ### Response `200` ```json { "data": [ { "id": 9001, "conversation_id": 17, "channel": "whatsapp", "direction": "outbound", "body": "Hi Ada, your appointment is confirmed.", "attachments": null, "status": "delivered", "provider_message_id": "wamid.HBgN...", "sent_at": "2026-06-05T13:42:00+00:00", "delivered_at": "2026-06-05T13:42:02+00:00", "read_at": null, "error": null, "user_id": 7, "created_at": "2026-06-05T13:42:00+00:00" } ], "next_cursor": "eyJpZCI6OTAwMSwiX3BvaW50c1RvTmV4dEl0ZW1zIjp0cnVlfQ", "prev_cursor": null } ``` ### Response `404` — Does not exist, or belongs to another location ```json { "message": "No query results for model." } ``` ## Send a message `POST /api/conversations/{conversation_id}/messages` Records the outbound message immediately (returning 201) and dispatches delivery via the appropriate provider asynchronously. Poll the message resource for `delivered_at` / `read_at` updates, or subscribe to outbound webhooks (`message.delivered`, `message.read`). If `recipient` is omitted, it is inferred from the contact: - `email` → `contact.email` - everything else → `contact.phone_e164` or `contact.phone` ### Path parameters - `conversation_id` (integer, required) — Conversation ID. ### Body parameters - `channel` (string, required) — Channel to send on. One of `sms`, `email`, `whatsapp`, `messenger`, `instagram`, `gmb`. - `body` (string, required) — Message body (plain text). Max 8000 chars. - `attachments` (string[]) — Optional list of attachment URLs. Provider-specific support. - `recipient` (string) — Override destination (e.g. specific email or phone). Defaults to the contact's primary handle for the channel. ### Example request ```bash curl --request POST \ "https://klozzo.com/api/conversations/17/messages" \ --header "Authorization: Bearer {YOUR_API_TOKEN}" \ --header "Content-Type: application/json" \ --header "Accept: application/json" \ --data "{ \"channel\": \"whatsapp\", \"body\": \"Hi Ada, your appointment is confirmed.\", \"attachments\": [ \"architecto\" ], \"recipient\": \"architecto\" }" ``` ### Response `201` ```json { "data": { "id": 9001, "conversation_id": 17, "channel": "whatsapp", "direction": "outbound", "body": "Hi Ada, your appointment is confirmed.", "attachments": null, "status": "queued", "provider_message_id": null, "sent_at": null, "delivered_at": null, "read_at": null, "error": null, "user_id": 7, "created_at": "2026-06-05T13:42:00+00:00" } } ``` ### Response `422` — A channel that does not exist ```json { "message": "Channel no es una opción válida.", "errors": { "channel": [ "Channel no es una opción válida." ] } } ``` ### Response `422` — Nothing to send ```json { "message": "Falta body.", "errors": { "body": [ "Falta body." ] } } ``` ### Response `422` — The person asked not to be reached on this channel ```json { "message": "El contacto tiene activado No molestar para este canal.", "errors": { "channel": [ "El contacto tiene activado No molestar para este canal." ] } } ```