Skip to content

Conversations & Messages

Conversations are the per-contact message threads in your SMBcrm account. Every SMS, email, or other channel message to or from a contact belongs to one. Use these endpoints to search and read conversations, send or record the messages inside them, export message history, upload attachments, and fetch call recordings and transcripts.

Base URL: https://services.smbcrm.com · Version header: v3 · Scopes: conversations.readonly, conversations.write, conversations/message.readonly, conversations/message.write. See Scopes.

GET/conversations/search

Search conversations in your account by contact, keyword, assignee, or status.

scope conversations.readonlyauth Location token or PIT

locationId is required. Every other parameter is optional.

Query param Type Required Description
locationId string Yes Your SMBcrm account/location ID.
contactId string No Only conversations with this contact.
query string No Free-text search string.
assignedTo string No Comma-separated user IDs the conversations are assigned to. Use unassigned for conversations with no assignee.
followers string No Comma-separated IDs of users who follow the conversation.
mentions string No Comma-separated IDs of mentioned users.
status string No One of all, read, unread, starred, or recents.
lastMessageType string No Only conversations whose last message has this type, for example TYPE_SMS or TYPE_EMAIL.
lastMessageDirection string No inbound or outbound. Filters on the direction of the last message.
lastMessageAction string No automated or manual. Filters on the last outbound message.
sort string No asc or desc.
sortBy string No One of last_message_date, last_manual_message_date, or score_profile.
sortScoreProfile string No ID of the score profile to sort on when sortBy is score_profile.
scoreProfile string No ID of a score profile to filter by. Use with scoreProfileMin and scoreProfileMax.
scoreProfileMin, scoreProfileMax number No Minimum and maximum score for the scoreProfile filter.
limit number No Number of conversations to return. Default 20.
startAfterDate number or array No Start the results after this sort value. Pass the sort value of the last conversation on the previous page.
id string No ID of a conversation.

The response includes total, the number of conversations that match the query.

Terminal window
curl "https://services.smbcrm.com/conversations/search?locationId=<location_id>&contactId=<contact_id>&status=unread&sort=desc&sortBy=last_message_date" \
-H "Authorization: Bearer <token>" \
-H "Version: v3"
200 OK
{
"conversations": [
{
"id": "<conversation_id>",
"contactId": "<contact_id>",
"locationId": "<location_id>",
"lastMessageBody": "See you at 3pm!",
"lastMessageType": "TYPE_SMS",
"type": "TYPE_PHONE",
"unreadCount": 0,
"fullName": "Jordan Lee",
"contactName": "Jordan Lee",
"email": "[email protected]",
"phone": "+15551234567"
}
],
"total": 1
}
GET/conversations/{conversationId}

Fetch a single conversation by ID.

scope conversations.readonlyauth Location token or PIT

type is a numeric channel code here, not the string form that search returns.

Code String form in search results Channel
1 TYPE_PHONE Phone
2 TYPE_EMAIL Email
3 TYPE_FB_MESSENGER Facebook Messenger
4 TYPE_REVIEW Review
5 TYPE_GROUP_SMS Group SMS

assignedTo is the ID of the team member handling the conversation, when the conversation has an assignee.

Terminal window
curl https://services.smbcrm.com/conversations/<conversation_id> \
-H "Authorization: Bearer <token>" \
-H "Version: v3"
200 OK
{
"id": "<conversation_id>",
"locationId": "<location_id>",
"contactId": "<contact_id>",
"assignedTo": "<user_id>",
"type": 1,
"unreadCount": 0,
"inbox": true,
"deleted": false,
"starred": false
}
POST/conversations/

Create a conversation for a contact.

scope conversations.writeauth Location token or PIT

locationId and contactId are both required. Use this to start an empty conversation thread with a contact before you have a message to send.

Terminal window
curl -X POST https://services.smbcrm.com/conversations/ \
-H "Authorization: Bearer <token>" \
-H "Version: v3" \
-H "Content-Type: application/json" \
-d '{
"locationId": "<location_id>",
"contactId": "<contact_id>"
}'
201 Created
{
"success": true,
"conversation": {
"id": "<conversation_id>",
"locationId": "<location_id>",
"contactId": "<contact_id>",
"dateAdded": "2026-07-08T15:04:00.000Z",
"dateUpdated": "2026-07-08T15:04:00.000Z",
"lastMessageDate": "2026-07-08T15:04:00.000Z",
"deleted": false
}
}
PUT/conversations/{conversationId}

Update a conversation, for example to mark it read/unread or starred.

scope conversations.writeauth Location token or PIT

locationId is required in the body. The path carries only the conversation ID. The optional body fields are unreadCount and starred.

Terminal window
curl -X PUT https://services.smbcrm.com/conversations/<conversation_id> \
-H "Authorization: Bearer <token>" \
-H "Version: v3" \
-H "Content-Type: application/json" \
-d '{
"locationId": "<location_id>",
"starred": true,
"unreadCount": 0
}'
200 OK
{
"success": true,
"conversation": {
"id": "<conversation_id>",
"locationId": "<location_id>",
"contactId": "<contact_id>",
"assignedTo": "<user_id>",
"userId": "<user_id>",
"lastMessageBody": "See you at 3pm!",
"lastMessageDate": "1783523040000",
"lastMessageType": "TYPE_SMS",
"unreadCount": 0,
"inbox": true,
"starred": true,
"deleted": false
}
}
DELETE/conversations/{conversationId}

Delete a conversation by ID.

scope conversations.writeauth Location token or PIT
Terminal window
curl -X DELETE https://services.smbcrm.com/conversations/<conversation_id> \
-H "Authorization: Bearer <token>" \
-H "Version: v3"
200 OK
{ "success": true }

Each message object includes both a numeric type and a readable messageType (for example TYPE_SMS). Use messageType unless you need the raw code. The common codes are:

type messageType
1 TYPE_CALL
2 TYPE_SMS
3 TYPE_EMAIL
10 TYPE_CAMPAIGN_VOICEMAIL
11 TYPE_FACEBOOK
18 TYPE_INSTAGRAM
19 TYPE_WHATSAPP

A message object has these fields. id, type, messageType, locationId, contactId, conversationId, dateAdded, direction, and contentType are always present. The rest appear when they apply.

Field Type Description
id string Message ID.
type number Numeric message type. See the table above.
messageType string Readable message type, for example TYPE_SMS.
direction string inbound or outbound.
status string One of connected, delivered, failed, opened, pending, read, scheduled, sent, undelivered, clicked, or opt_out.
body string Message text.
contentType string Content type of the body, for example text/plain.
attachments array Attachment URLs. Empty for calls and voicemails; fetch their audio from the recording endpoint.
meta object Channel details: callDuration (seconds, as a string) and callStatus for calls, email.messageIds (every email message ID in the thread) for emails, and the page details in fb and ig for Facebook and Instagram messages. callStatus is one of pending, completed, answered, busy, no-answer, failed, canceled, or voicemail.
source string Where the message came from: workflow, bulk_actions, campaign, api, or app.
userId string ID of the team member who sent the message.
from, to string Sender and recipient (a phone number or name). Not returned for email messages.
error string Delivery failure text, when the message failed.
altId string Alternate message ID from an external provider.
conversationProviderId string ID of the conversation provider the message went through.
chatWidgetId string ID of the chat widget the message came from.
GET/conversations/{conversationId}/messages

List the messages in a conversation.

scope conversations/message.readonlyauth Location token or PIT
Query param Type Required Description
limit number No Number of messages to return. Default 20.
lastMessageId string No The messages.lastMessageId from the previous response. Pass it to fetch the next page.
type string No Comma-separated message types to return, for example TYPE_SMS,TYPE_CALL.

type accepts TYPE_CALL, TYPE_SMS, TYPE_EMAIL, TYPE_FACEBOOK, TYPE_GMB, TYPE_INSTAGRAM, TYPE_WHATSAPP, TYPE_ACTIVITY_APPOINTMENT, TYPE_ACTIVITY_CONTACT, TYPE_ACTIVITY_INVOICE, TYPE_ACTIVITY_PAYMENT, TYPE_ACTIVITY_OPPORTUNITY, TYPE_LIVE_CHAT, TYPE_INTERNAL_COMMENTS, and TYPE_ACTIVITY_EMPLOYEE_ACTION_LOG.

Terminal window
curl "https://services.smbcrm.com/conversations/<conversation_id>/messages?limit=20&type=TYPE_SMS,TYPE_CALL" \
-H "Authorization: Bearer <token>" \
-H "Version: v3"

The response nests the list. The message array is at messages.messages, and the paging fields lastMessageId and nextPage sit beside it inside messages.

200 OK
{
"messages": {
"lastMessageId": "<message_id>",
"nextPage": false,
"messages": [
{
"id": "<message_id>",
"type": 2,
"messageType": "TYPE_SMS",
"locationId": "<location_id>",
"contactId": "<contact_id>",
"conversationId": "<conversation_id>",
"dateAdded": "2026-07-08T15:04:00.000Z",
"body": "See you at 3pm!",
"direction": "outbound",
"status": "delivered",
"contentType": "text/plain"
}
]
}
}

When nextPage is true, more messages remain. Send lastMessageId back as the lastMessageId query parameter to get the next page, and repeat until nextPage is false.

Page through every message
const all = [];
let lastMessageId;
do {
const url = new URL('https://services.smbcrm.com/conversations/<conversation_id>/messages');
url.searchParams.set('limit', '20');
if (lastMessageId) url.searchParams.set('lastMessageId', lastMessageId);
const res = await fetch(url, {
headers: { Authorization: 'Bearer <token>', Version: 'v3' },
});
const { messages } = await res.json();
all.push(...messages.messages);
lastMessageId = messages.nextPage ? messages.lastMessageId : undefined;
} while (lastMessageId);
GET/conversations/messages/{id}

Fetch a single message by ID.

scope conversations/message.readonlyauth Location token or PIT

This response is the message object itself, not wrapped in a messages key.

Terminal window
curl https://services.smbcrm.com/conversations/messages/<message_id> \
-H "Authorization: Bearer <token>" \
-H "Version: v3"
200 OK
{
"id": "<message_id>",
"type": 2,
"messageType": "TYPE_SMS",
"locationId": "<location_id>",
"contactId": "<contact_id>",
"conversationId": "<conversation_id>",
"dateAdded": "2026-07-08T15:04:00.000Z",
"body": "See you at 3pm!",
"direction": "outbound",
"status": "delivered",
"contentType": "text/plain"
}
GET/conversations/messages/email/{id}

Fetch a single email message by its email message ID.

scope conversations/message.readonlyauth Location token or PIT

Use the emailMessageId returned when you sent the email, or an ID from meta.email.messageIds on a message object.

id, threadId, locationId, contactId, conversationId, dateAdded, body, direction, contentType, from, and to are always present. The rest appear when they apply.

Field Type Description
from string Name and email address of the sender.
to array Email addresses of the recipients.
direction string inbound or outbound.
subject string Subject line.
status string One of pending, scheduled, sent, delivered, read, undelivered, connected, failed, or opened.
cc, bcc array Email addresses in the CC and BCC fields.
attachments array Attachment URLs.
replyToMessageId string For a reply, the email message ID of the email being replied to.
source string workflow, bulk_actions, campaign, api, or app.
provider, conversationProviderId, altId string The provider the email went through, its conversation provider ID, and the external ID.
error string Bounce or failure text for emails that failed.
Terminal window
curl https://services.smbcrm.com/conversations/messages/email/<email_message_id> \
-H "Authorization: Bearer <token>" \
-H "Version: v3"
200 OK
{
"id": "<email_message_id>",
"threadId": "<thread_id>",
"locationId": "<location_id>",
"contactId": "<contact_id>",
"conversationId": "<conversation_id>",
"dateAdded": "2026-07-08T15:04:00.000Z",
"subject": "Confirming our call today",
"body": "<p>Hey Jordan, just confirming our call at 3pm today.</p>",
"direction": "outbound",
"status": "delivered",
"contentType": "text/html",
"from": "Sam Rivera <[email protected]>",
}
GET/conversations/messages/export

Export the messages in your account page by page, with a cursor.

scope conversations/message.readonlyauth Location token or PIT

Use this to pull message history across conversations. Each message is the standard message object.

Query param Type Required Description
locationId string Yes Your SMBcrm account/location ID.
channel string No One of Call, SMS, Email, WhatsApp, Instagram, or Facebook.
limit number No Messages per page. Default 100, minimum 10, maximum 1000.
cursor string No The nextCursor from the previous response.
sortBy string No createdAt or updatedAt. Default createdAt.
sortOrder string No asc or desc. Default desc.
conversationId string No Only messages in this conversation.
contactId string No Only messages for this contact.
startDate, endDate string No Start and end of the date range to export.

Without channel, the export returns every non-email message type, including activity messages such as opportunity updates and appointments. Set channel=Email to get emails. Group chat and SMS review request messages are not supported.

A cursor stays valid for 2 minutes after your last request. Pass each response’s nextCursor as the next request’s cursor until nextCursor is null.

Terminal window
curl "https://services.smbcrm.com/conversations/messages/export?locationId=<location_id>&channel=SMS&limit=100" \
-H "Authorization: Bearer <token>" \
-H "Version: v3"
200 OK
{
"messages": [
{
"id": "<message_id>",
"type": 2,
"messageType": "TYPE_SMS",
"locationId": "<location_id>",
"contactId": "<contact_id>",
"conversationId": "<conversation_id>",
"dateAdded": "2026-07-08T15:04:00.000Z",
"body": "See you at 3pm!",
"direction": "outbound",
"status": "delivered",
"contentType": "text/plain"
}
],
"nextCursor": "<cursor>",
"total": 1234
}

total is the number of messages that match the query.

Call and voicemail messages have an empty attachments array. Fetch the audio and the transcript with these endpoints, using the message’s id and your locationId.

GET/conversations/messages/{messageId}/locations/{locationId}/recording

Download the audio recording for a call or voicemail message.

scope conversations/message.readonlyauth Location token or PIT

The response is the audio file, returned as an audio/x-wav attachment named audio.wav. Save it with -o.

Terminal window
curl https://services.smbcrm.com/conversations/messages/<message_id>/locations/<location_id>/recording \
-H "Authorization: Bearer <token>" \
-H "Version: v3" \
-o audio.wav
GET/conversations/locations/{locationId}/messages/{messageId}/transcription

Get the transcript data for a call recording.

scope conversations/message.readonlyauth Location token or PIT

Each transcript sentence has the fields below. startTime and endTime are in milliseconds.

Field Type Description
mediaChannel number The audio channel the sentence was spoken on.
sentenceIndex number Position of the sentence in the transcript.
startTime, endTime number When the sentence starts and ends, in milliseconds.
transcript string The text of the sentence.
confidence number Confidence of the transcription.
Terminal window
curl https://services.smbcrm.com/conversations/locations/<location_id>/messages/<message_id>/transcription \
-H "Authorization: Bearer <token>" \
-H "Version: v3"
200 OK
{
"mediaChannel": 1,
"sentenceIndex": 1,
"startTime": 34,
"endTime": 45,
"transcript": "This call may be recorded for quality assurance purposes.",
"confidence": 0.5
}
GET/conversations/locations/{locationId}/messages/{messageId}/transcription/download

Download the call transcript as a text file.

scope conversations/message.readonlyauth Location token or PIT

The response is a text/plain attachment named transcription.txt.

Terminal window
curl https://services.smbcrm.com/conversations/locations/<location_id>/messages/<message_id>/transcription/download \
-H "Authorization: Bearer <token>" \
-H "Version: v3" \
-o transcription.txt
POST/conversations/messages

Send an SMS, email, or other channel message to a contact.

scope conversations/message.writeauth Location token or PIT

type, contactId, and status are required. Set type to the channel you are sending on. The rest of the body depends on that channel.

Body field Type Required Description
type string Yes One of SMS, Email, WhatsApp, IG, FB, Custom, Live_Chat, or InternalComment.
contactId string Yes The contact receiving the message.
status string Yes Message status. One of delivered, failed, pending, or read.
message string No Text content. For InternalComment, this is the comment text.
html string No HTML content, for email.
subject string No Subject line, for email.
attachments array No Attachment URLs. Get them from Upload attachments.
fromNumber, toNumber string No Sender and recipient phone numbers for SMS.
emailFrom string No Address to send the email from.
emailTo string No Recipient address, if it differs from the contact’s primary email. It should be an address associated with the contact.
emailCc, emailBcc array No Email addresses to copy or blind copy.
emailReplyMode string No reply or reply_all.
replyMessageId string No ID of the message you are replying to.
threadId string No ID of the message thread. For email, the message ID that groups the emails in the thread.
templateId string No ID of a message template.
appointmentId string No ID of an appointment to associate with the message.
scheduledTimestamp number No UTC timestamp in seconds. Schedules the message to send then. See Schedule and cancel a message.
conversationProviderId string No ID of the conversation provider.
whatsapp object No WhatsApp media payload. Applies only when type is WhatsApp.
mentions array No User IDs mentioned in the comment. Required when type is InternalComment.
userId string No Author of the comment when type is InternalComment.
Terminal window
curl -X POST https://services.smbcrm.com/conversations/messages \
-H "Authorization: Bearer <token>" \
-H "Version: v3" \
-H "Content-Type: application/json" \
-d '{
"type": "SMS",
"contactId": "<contact_id>",
"status": "pending",
"message": "Hey Jordan, just confirming our call at 3pm today."
}'

The response always has conversationId and messageId:

200 OK
{
"conversationId": "<conversation_id>",
"messageId": "<message_id>"
}

For Email sends, the response also has emailMessageId. Use it to fetch the email or to thread inbound replies.

The response doesn’t include a delivery status. To check on it, fetch the message by its ID and read status.

Set scheduledTimestamp on Send a message to a UTC timestamp in seconds, and the message sends at that time. For example, 1792072800 is October 15, 2026 at 14:00 UTC.

Terminal window
curl -X POST https://services.smbcrm.com/conversations/messages \
-H "Authorization: Bearer <token>" \
-H "Version: v3" \
-H "Content-Type: application/json" \
-d '{
"type": "SMS",
"contactId": "<contact_id>",
"status": "pending",
"message": "Reminder: your appointment is tomorrow at 3pm.",
"scheduledTimestamp": 1792072800
}'

Cancel a scheduled message before it sends. Messages and emails have separate cancel endpoints. Both return status (the HTTP status code of the request) and message (a result message).

DELETE/conversations/messages/{messageId}/schedule

Cancel a scheduled message.

scope conversations/message.writeauth Location token or PIT

Use the messageId that the send response returned when you scheduled the message.

Terminal window
curl -X DELETE https://services.smbcrm.com/conversations/messages/<message_id>/schedule \
-H "Authorization: Bearer <token>" \
-H "Version: v3"
DELETE/conversations/messages/email/{emailMessageId}/schedule

Cancel a scheduled email.

scope conversations/message.writeauth Location token or PIT

Use the emailMessageId that the send response returned when you scheduled the email.

Terminal window
curl -X DELETE https://services.smbcrm.com/conversations/messages/email/<email_message_id>/schedule \
-H "Authorization: Bearer <token>" \
-H "Version: v3"
POST/conversations/messages/upload

Upload files to attach to a message.

scope conversations/message.writeauth Location token or PIT

Send the request as multipart/form-data, with each file under the key fileAttachment. The response has an uploadedFiles object with the URLs of the stored files. Pass those URLs in attachments when you send a message, or in whatsapp.media.url for WhatsApp media.

Form field Required Description
locationId Yes Your SMBcrm account/location ID.
conversationId, contactId, workflowId, campaignId One of them The conversation, contact, workflow, or campaign the files belong to.
fileAttachment Yes The file to upload.
isSecureAttachment No true or false. Default false. See below.

Each file can be up to 5 MB, and one upload can hold up to 5 files. These file types are allowed:

Category Types
Images JPG, JPEG, PNG, GIF, SVG, HEIC, AI
Videos MP4, MPEG, 3GP
Audio MP3, WAV, WAVE, AIFF, AIF, AIFC, GSM, ULAW, OGG, AAC, M4A, AMR
Documents PDF, DOC, DOCX, TXT, CSV, XLS, XLSX, PPT, PPTX, ODT
Archives ZIP, RAR
Other VCF, VCARD (contact files), ICS (calendar files)
Terminal window
curl -X POST https://services.smbcrm.com/conversations/messages/upload \
-H "Authorization: Bearer <token>" \
-H "Version: v3" \
-F "locationId=<location_id>" \
-F "contactId=<contact_id>" \

Errors: 400 for a bad request, 401 for an invalid token, 404 when the conversation, contact, workflow, or campaign ID isn’t found, 413 when the upload is too large, and 415 for an unsupported file type.

If a contact messages you on a channel your account doesn’t handle natively, such as a custom chat widget or a third-party number, use this endpoint to log what they sent so it appears in their conversation history like any other message.

POST/conversations/messages/inbound

Record an inbound message from an external channel.

scope conversations/message.writeauth Location token or PIT

type and conversationProviderId are required, plus either conversationId or contactId. If you send contactId without conversationId, the message is added to that contact’s conversation. conversationProviderId is the ID of the custom conversation provider the message belongs to. Set type to the channel and supply the matching fields.

direction defaults to outbound, so set it to inbound to record a message the contact sent.

Body field Type Required Description
type string Yes One of SMS, Email, WhatsApp, GMB, IG, FB, Custom, WebChat, Live_Chat, or Call.
conversationProviderId string Yes ID of the conversation provider.
conversationId string One of the two The conversation to add the message to.
contactId string One of the two The contact the message is from.
direction string No inbound or outbound. Default outbound.
message string No Message text.
date string No Date and time of the message, as an ISO 8601 date-time.
attachments array No Attachment URLs.
altId string No The external provider’s ID for the message.
html string No HTML body, for email.
subject string No Subject line, for email.
emailFrom, emailTo string No Sender and recipient addresses. They come from the contact record and can’t be changed here.
emailCc, emailBcc array No Email addresses to copy or blind copy.
emailMessageId string No The email message ID to thread this email under, for a reply to a specific email.
call object No For Call messages: to and from phone numbers, and status (pending, completed, answered, busy, no-answer, failed, canceled, or voicemail).
Terminal window
curl -X POST https://services.smbcrm.com/conversations/messages/inbound \
-H "Authorization: Bearer <token>" \
-H "Version: v3" \
-H "Content-Type: application/json" \
-d '{
"type": "SMS",
"contactId": "<contact_id>",
"conversationId": "<conversation_id>",
"conversationProviderId": "<conversation_provider_id>",
"direction": "inbound",
"message": "Sounds good, see you then!"
}'

The response has success, conversationId, messageId, and message. It also returns contactId, dateAdded, and, for emails, emailMessageId.

200 OK
{
"success": true,
"conversationId": "<conversation_id>",
"messageId": "<message_id>",
"message": "success",
"contactId": "<contact_id>",
"dateAdded": "2026-07-08T15:04:00.000Z"
}
  • Contacts: the contactId every conversation and message belongs to.
  • Users: the team member IDs used in assignedTo, userId, and mentions.
  • Webhooks: receive new messages in real time instead of polling for them.
  • Scopes: the exact permissions each endpoint above requires.