Messages
Este conteúdo não está disponível em sua língua ainda.
Messages in BenchKey are organized into conversations, one thread per customer. Each conversation contains one or more messages sent or received via SMS, email, the customer portal, or voice. You can read conversations and their messages, and send new SMS or email messages on any ticket.
The conversation object
Section titled “The conversation object”{ "object": "conversation", "id": 4201, "customer_id": 8812, "customer_email": "jane.smith@example.com", "customer_name": "Jane Smith", "last_inbound_at": "2025-11-04T09:12:00.000Z", "last_outbound_at": "2025-11-03T16:45:00.000Z", "unread_count": 1, "assigned_user_id": null, "created_at": "2025-10-28T11:00:00.000Z", "updated_at": "2025-11-04T09:12:00.000Z"}| Field | Type | Description |
|---|---|---|
id | integer | Conversation ID |
customer_id | integer|null | Linked customer ID, or null for unlinked threads |
customer_email | string|null | Customer email at time of last message |
customer_name | string|null | Customer display name |
last_inbound_at | string|null | ISO-8601 timestamp of the last inbound message |
last_outbound_at | string|null | ISO-8601 timestamp of the last outbound message |
unread_count | integer | Number of unread inbound messages |
assigned_user_id | integer|null | User ID this conversation is assigned to, or null |
created_at | string | ISO-8601 creation timestamp |
updated_at | string | ISO-8601 last-activity timestamp |
The message object
Section titled “The message object”{ "object": "message", "id": 91042, "conversation_id": 4201, "channel": "sms", "direction": "outbound", "body": "Hi Jane! Your phone is ready for pickup.", "subject": null, "sender": "alex@repairshop.com", "sent_from_ticket_id": "TK-10042", "external_uuid": null, "in_reply_to": null, "media_urls": null, "attachments": null, "created_at": "2025-11-03T16:45:00.000Z"}| Field | Type | Description |
|---|---|---|
id | integer | Message ID |
conversation_id | integer | Parent conversation |
channel | string | sms, email, portal, or voice |
direction | string | inbound (customer → shop) or outbound (shop → customer) |
body | string|null | Message text content |
subject | string|null | Email subject line (email channel only) |
sender | string|null | Sender identifier (staff email or customer contact) |
sent_from_ticket_id | string|null | Ticket ID this message was sent from |
external_uuid | string|null | Provider message ID (Twilio SID, etc.) |
in_reply_to | string|null | Message-ID this email replied to |
media_urls | array|null | MMS media URLs, if any |
attachments | array|null | Email attachment metadata, if any |
created_at | string | ISO-8601 send/receive timestamp |
List conversations
Section titled “List conversations”GET /api/v1/messages/conversationsReturns conversations sorted by most-recently-updated first. GET /api/v1/messages is an alias for this endpoint.
Scope required: messages.read
Query parameters
Section titled “Query parameters”| Parameter | Type | Description |
|---|---|---|
customer_id | integer | Filter to a specific customer’s conversations |
limit | integer | Page size, 1–100 (default: 25) |
cursor | string | Opaque cursor from a previous response’s next_cursor |
curl "https://app.benchkey.com/api/v1/messages/conversations?customer_id=8812&limit=10" \ -H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa"Node.js
Section titled “Node.js”const params = new URLSearchParams({ customer_id: "8812", limit: "10" });const res = await fetch( `https://app.benchkey.com/api/v1/messages/conversations?${params}`, { headers: { Authorization: "Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" } });const { data, has_more, next_cursor } = await res.json();Response
Section titled “Response”{ "object": "list", "data": [ { "object": "conversation", "id": 4201, "customer_id": 8812, "customer_email": "jane.smith@example.com", "customer_name": "Jane Smith", "last_inbound_at": "2025-11-04T09:12:00.000Z", "last_outbound_at": "2025-11-03T16:45:00.000Z", "unread_count": 1, "assigned_user_id": null, "created_at": "2025-10-28T11:00:00.000Z", "updated_at": "2025-11-04T09:12:00.000Z" } ], "has_more": false, "next_cursor": null}See Pagination for how to page through results.
Retrieve a conversation
Section titled “Retrieve a conversation”GET /api/v1/messages/conversations/:idReturns a single conversation by ID.
Scope required: messages.read
curl "https://app.benchkey.com/api/v1/messages/conversations/4201" \ -H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa"Node.js
Section titled “Node.js”const res = await fetch( "https://app.benchkey.com/api/v1/messages/conversations/4201", { headers: { Authorization: "Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" } });const conversation = await res.json();Response
Section titled “Response”Returns the conversation object. Returns 404 if not found.
List messages in a conversation
Section titled “List messages in a conversation”GET /api/v1/messages/conversations/:id/messagesReturns messages in a conversation, newest first.
Scope required: messages.read
Query parameters
Section titled “Query parameters”| Parameter | Type | Description |
|---|---|---|
limit | integer | Page size, 1–100 (default: 25) |
cursor | string | Opaque cursor from a previous response’s next_cursor |
curl "https://app.benchkey.com/api/v1/messages/conversations/4201/messages?limit=25" \ -H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa"Node.js
Section titled “Node.js”const res = await fetch( "https://app.benchkey.com/api/v1/messages/conversations/4201/messages", { headers: { Authorization: "Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" } });const { data, has_more, next_cursor } = await res.json();Response
Section titled “Response”{ "object": "list", "data": [ { "object": "message", "id": 91043, "conversation_id": 4201, "channel": "sms", "direction": "inbound", "body": "Thanks! I'll be there in an hour.", "subject": null, "sender": "+15558675309", "sent_from_ticket_id": null, "external_uuid": "SMxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "in_reply_to": null, "media_urls": null, "attachments": null, "created_at": "2025-11-04T09:12:00.000Z" }, { "object": "message", "id": 91042, "conversation_id": 4201, "channel": "sms", "direction": "outbound", "body": "Hi Jane! Your phone is ready for pickup.", "subject": null, "sender": "alex@repairshop.com", "sent_from_ticket_id": "TK-10042", "external_uuid": null, "in_reply_to": null, "media_urls": null, "attachments": null, "created_at": "2025-11-03T16:45:00.000Z" } ], "has_more": false, "next_cursor": null}Retrieve a message
Section titled “Retrieve a message”GET /api/v1/messages/:idReturns a single message by ID.
Scope required: messages.read
curl "https://app.benchkey.com/api/v1/messages/91042" \ -H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa"Node.js
Section titled “Node.js”const res = await fetch( "https://app.benchkey.com/api/v1/messages/91042", { headers: { Authorization: "Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" } });const message = await res.json();Response
Section titled “Response”Returns the message object. Returns 404 if not found.
Send a message
Section titled “Send a message”POST /api/v1/messages/sendSends an SMS or email on a ticket. Uses the same path the BenchKey app uses, so suppression lists, SMS consent checks, per-tenant rate limits, and the conversation log all apply automatically.
Returns 202 Accepted on success. A 202 means the message was dispatched to the send pipeline, delivery is asynchronous and subject to carrier or email provider acceptance.
Scope required: messages.write
Send an Idempotency-Key header to make retries safe. See Idempotency.
Request body
Section titled “Request body”| Field | Required | Description |
|---|---|---|
ticket_id | yes | The ticket to send the message on (e.g. "TK-10042") |
channel | yes | "sms" or "email" |
content | yes (SMS) | Message text for SMS (alias: body, max 1,600 chars) |
to | yes (SMS) | Recipient phone number (SMS) |
body | yes (email) | Email body text (alias: content, max 100,000 chars) |
subject | no | Email subject line (email only; BenchKey derives one from the ticket if omitted) |
to | no (email) | Recipient email address. If supplied, it must exactly match the ticket’s customer email (case-insensitive); mismatches return 400 invalid_field. Omit to use the ticket’s customer email automatically. |
curl: SMS
Section titled “curl: SMS”curl -X POST https://app.benchkey.com/api/v1/messages/send \ -H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: msg-ready-TK-10042-$(uuidgen)" \ -d '{ "ticket_id": "TK-10042", "channel": "sms", "to": "+15558675309", "content": "Hi Jane! Your phone is ready for pickup." }'curl: email
Section titled “curl: email”curl -X POST https://app.benchkey.com/api/v1/messages/send \ -H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: email-ready-TK-10042-$(uuidgen)" \ -d '{ "ticket_id": "TK-10042", "channel": "email", "to": "jane.smith@example.com", "subject": "Your repair is ready", "body": "Hi Jane,\n\nYour iPhone 15 Pro is repaired and ready for pickup. See you soon!\n\n- Alex" }'Node.js
Section titled “Node.js”const res = await fetch("https://app.benchkey.com/api/v1/messages/send", { method: "POST", headers: { Authorization: "Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa", "Content-Type": "application/json", "Idempotency-Key": crypto.randomUUID(), }, body: JSON.stringify({ ticket_id: "TK-10042", channel: "sms", to: "+15558675309", content: "Hi Jane! Your phone is ready for pickup.", }),});const result = await res.json(); // HTTP 202Response
Section titled “Response”{ "object": "message_send_result", "ok": true, "ticket_id": "TK-10042", "channel": "sms"}Error codes
Section titled “Error codes”| HTTP status | Code | Meaning |
|---|---|---|
400 | missing_field | A required field is absent (ticket_id, channel, content, to, body) |
400 | invalid_field | A field value is invalid or too long; or to (email) does not match the ticket’s customer email |
400 | conflicting_aliases | Both content and body were provided but their trimmed values differ |
400 | invalid_channel | channel is not "sms" or "email" |
400 | invalid_id | :id or customer_id is not a valid positive integer, or exceeds 2,147,483,647 |
400 | invalid_query | A list filter parameter was supplied as an array or object instead of a single scalar value |
400 | invalid_cursor | cursor is malformed, contains an impossible calendar value (e.g. month 99, hour 25), or was issued by a different endpoint |
404 | not_found | Ticket or conversation not found for this tenant |
422 | send_failed | Message was rejected by the send pipeline (suppression, invalid contact, missing consent) |
429 | (rate limit) | Per-tenant send rate limit exceeded |
403 | insufficient_scope | API key lacks messages.read or messages.write |
See Errors for the full error envelope format.