DevelopersAPI referenceEngagement

Messages API

REST endpoints for messages in SKUFlow: 8 operations with parameters, request and response fields, permissions and curl examples.

Base URL: https://app.skuflow.ai/api. Every call needs Authorization: Bearer skf_… (Authentication); {organizationId} ($ORGANIZATION_ID in the examples) is your business's id.

OperationEndpointRisk
List messagesGET /organizations/{organizationId}/messagesread
Get a messageGET /organizations/{organizationId}/messages/{messageId}read
Preview a manual sendPOST /organizations/{organizationId}/messages/send/previewwrite
Send a message to a customerPOST /organizations/{organizationId}/messages/sendsensitive
List text conversationsGET /organizations/{organizationId}/conversationsread
Get a text conversationGET /organizations/{organizationId}/conversations/threadread
Mark a text conversation readPOST /organizations/{organizationId}/conversations/readwrite
Text a customer (reply)POST /organizations/{organizationId}/conversations/replysensitive

List messages

GET /organizations/{organizationId}/messages

The message log: every email and text sent (or logged in test mode as SENT_TEST), deferred by quiet hours, skipped with its reason, failed or bounced, and every inbound reply (direction INBOUND). Filter by status, channel, direction, customer, automation, dates or text. Newest first. Read-only.

Permissionengagement.read (Owner, Admin, Staff)
Riskread, OAuth scope skuflow.read. Read: reads only.
MCP toolmessages_list (toolset marketing)

Parameters

NameInTypeRequiredDescription
statusqueryenumNoOne of QUEUED, DEFERRED, SENDING, SENT, SENT_TEST, DELIVERED, FAILED, BOUNCED, SKIPPED, CANCELLED, RECEIVED.
channelqueryenumNoOne of EMAIL, SMS.
directionqueryenumNoINBOUND = the replies inbox. One of OUTBOUND, INBOUND.
customerIdquerystringNo
ruleIdquerystringNo
campaignIdquerystringNoA marketing campaign's messages.
queryquerystringNoText in the address, subject, body or customer name. Up to 200 characters.
fromquerystring (date-time)No
toquerystring (date-time)No
limitqueryintegerNoPage size, 1–200. Default 50.
offsetqueryintegerNoRows to skip; the answer's total counts every match. Default 0.

Example

curl "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/messages" \
  -H "Authorization: Bearer $SKUFLOW_API_KEY"

Response

200: the answer's fields
FieldTypeDescription
itemsarray of object
items[].idstring
items[].directionenumOne of OUTBOUND, INBOUND.
items[].sourceenumSystem: a message the customer asked for themselves (a portal sign-in code; its code is scrubbed once sent). One of AUTOMATION, MANUAL, TEST, INBOUND, CAMPAIGN, SYSTEM.
items[].channelenumOne of EMAIL, SMS.
items[].purposeenumOne of TRANSACTIONAL, MARKETING.
items[].statusenumOne of QUEUED, DEFERRED, SENDING, SENT, SENT_TEST, DELIVERED, FAILED, BOUNCED, SKIPPED, CANCELLED, RECEIVED.
items[].statusReasonstring or null
items[].providerstring or null
items[].toAddressstring or null
items[].fromAddressstring or null
items[].subjectstring or null
items[].previewstringThe first 160 characters of the body.
items[].customerIdstring or null
items[].customerNamestring or null
items[].ruleIdstring or null
items[].ruleNamestring or null
items[].campaignIdstring or nullThe marketing campaign it belongs to (source CAMPAIGN).
items[].openedAtstring or nullFirst open the email provider reported.
items[].clickedAtstring or nullFirst click the email provider reported.
items[].subjectTypestring or null
items[].subjectIdstring or null
items[].scheduledForstringISO 8601 time (UTC).
items[].sentAtstring or nullISO 8601 time (UTC).
items[].createdAtstringISO 8601 time (UTC).
totalinteger

Get a message

GET /organizations/{organizationId}/messages/{messageId}

One message with the exact rendered body (and the email's HTML), its status timeline (queued, deferred, sent, delivered, failed …), what produced it and the replies to it. Read-only.

Permissionengagement.read (Owner, Admin, Staff)
Riskread, OAuth scope skuflow.read. Read: reads only.
MCP toolmessages_get (toolset marketing)

Parameters

NameInTypeRequiredDescription
messageIdpathstringYesFrom GET /messages.

Example

curl "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/messages/{messageId}" \
  -H "Authorization: Bearer $SKUFLOW_API_KEY"

Response

200: the answer's fields
FieldTypeDescription
idstring
directionenumOne of OUTBOUND, INBOUND.
sourceenumSystem: a message the customer asked for themselves (a portal sign-in code; its code is scrubbed once sent). One of AUTOMATION, MANUAL, TEST, INBOUND, CAMPAIGN, SYSTEM.
channelenumOne of EMAIL, SMS.
purposeenumOne of TRANSACTIONAL, MARKETING.
statusenumOne of QUEUED, DEFERRED, SENDING, SENT, SENT_TEST, DELIVERED, FAILED, BOUNCED, SKIPPED, CANCELLED, RECEIVED.
statusReasonstring or null
providerstring or null
toAddressstring or null
fromAddressstring or null
subjectstring or null
customerIdstring or null
customerNamestring or null
ruleIdstring or null
ruleNamestring or null
campaignIdstring or nullThe marketing campaign it belongs to (source CAMPAIGN).
openedAtstring or nullFirst open the email provider reported.
clickedAtstring or nullFirst click the email provider reported.
subjectTypestring or null
subjectIdstring or null
scheduledForstringISO 8601 time (UTC).
sentAtstring or nullISO 8601 time (UTC).
createdAtstringISO 8601 time (UTC).
bodystring
htmlstring or null
providerMessageIdstring or null
contactMethodIdstring or null
templateIdstring or null
variantIdstring or null
stepIndexinteger or null
sentByUserIdstring or null
deliveredAtstring or nullISO 8601 time (UTC).
failedAtstring or nullISO 8601 time (UTC).
attemptCountinteger
timelinearray of object
timeline[].atstring
timeline[].statusstring
timeline[].notestring
contextobject
inReplyToobject or null
inReplyTo.idstring
inReplyTo.directionenumOne of OUTBOUND, INBOUND.
inReplyTo.sourceenumSystem: a message the customer asked for themselves (a portal sign-in code; its code is scrubbed once sent). One of AUTOMATION, MANUAL, TEST, INBOUND, CAMPAIGN, SYSTEM.
inReplyTo.channelenumOne of EMAIL, SMS.
inReplyTo.purposeenumOne of TRANSACTIONAL, MARKETING.
inReplyTo.statusenumOne of QUEUED, DEFERRED, SENDING, SENT, SENT_TEST, DELIVERED, FAILED, BOUNCED, SKIPPED, CANCELLED, RECEIVED.
inReplyTo.statusReasonstring or null
inReplyTo.providerstring or null
inReplyTo.toAddressstring or null
inReplyTo.fromAddressstring or null
inReplyTo.subjectstring or null
inReplyTo.previewstringThe first 160 characters of the body.
inReplyTo.customerIdstring or null
inReplyTo.customerNamestring or null
inReplyTo.ruleIdstring or null
inReplyTo.ruleNamestring or null
inReplyTo.campaignIdstring or nullThe marketing campaign it belongs to (source CAMPAIGN).
inReplyTo.openedAtstring or nullFirst open the email provider reported.
inReplyTo.clickedAtstring or nullFirst click the email provider reported.
inReplyTo.subjectTypestring or null
inReplyTo.subjectIdstring or null
inReplyTo.scheduledForstringISO 8601 time (UTC).
inReplyTo.sentAtstring or nullISO 8601 time (UTC).
inReplyTo.createdAtstringISO 8601 time (UTC).
repliesarray of object
replies[].idstring
replies[].directionenumOne of OUTBOUND, INBOUND.
replies[].sourceenumSystem: a message the customer asked for themselves (a portal sign-in code; its code is scrubbed once sent). One of AUTOMATION, MANUAL, TEST, INBOUND, CAMPAIGN, SYSTEM.
replies[].channelenumOne of EMAIL, SMS.
replies[].purposeenumOne of TRANSACTIONAL, MARKETING.
replies[].statusenumOne of QUEUED, DEFERRED, SENDING, SENT, SENT_TEST, DELIVERED, FAILED, BOUNCED, SKIPPED, CANCELLED, RECEIVED.
replies[].statusReasonstring or null
replies[].providerstring or null
replies[].toAddressstring or null
replies[].fromAddressstring or null
replies[].subjectstring or null
replies[].previewstringThe first 160 characters of the body.
replies[].customerIdstring or null
replies[].customerNamestring or null
replies[].ruleIdstring or null
replies[].ruleNamestring or null
replies[].campaignIdstring or nullThe marketing campaign it belongs to (source CAMPAIGN).
replies[].openedAtstring or nullFirst open the email provider reported.
replies[].clickedAtstring or nullFirst click the email provider reported.
replies[].subjectTypestring or null
replies[].subjectIdstring or null
replies[].scheduledForstringISO 8601 time (UTC).
replies[].sentAtstring or nullISO 8601 time (UTC).
replies[].createdAtstringISO 8601 time (UTC).

Preview a manual send

POST /organizations/{organizationId}/messages/send/preview

The Send dialog for a quote, an invoice (or a reminder about it), or a customer's portal link: who it goes to and whether consent allows it, the other addresses to choose from, and the rendered subject and body to edit. Nothing is sent, but the record's customer link (invoice or portal) is made if it does not exist yet. Then POST /messages/send.

Permissionmessages.send (Owner, Admin, Staff)
Riskwrite, OAuth scope skuflow.write. Write: creates or changes records inside the business; nothing leaves it.
MCP toolmessages_previewSend (toolset marketing, in the default set)

Request body

FieldTypeRequiredDescription
kindenumYesquote | invoice | invoiceReminder | portal One of quote, invoice, invoiceReminder, portal, updateCard.
recordIdstringYesThe quote, invoice or customer (portal) id.
channelenumYesOne of EMAIL, SMS.
contactMethodIdstringNoSend to this contact method instead of the default.

Example

curl -X POST "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/messages/send/preview" \
  -H "Authorization: Bearer $SKUFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"kind":"quote","recordId":"record_id","channel":"EMAIL"}'

Response

200: the answer's fields
FieldTypeDescription
customerIdstring
channelenumOne of EMAIL, SMS.
recipientobject
recipient.contactMethodIdstring or null
recipient.namestring or null
recipient.addressstring or null
recipient.allowedbooleanWhether consent allows this send.
recipient.reasonstring or null
optionsarray of object
options[].contactMethodIdstring
options[].labelstring
options[].smsConsentstring or null
subjectstring or null
bodystringThe rendered message to edit before sending.
unresolvedarray of string
deferredUntilstring or nullSet when quiet hours hold it: when it will go.
marksAsstring or nullWhat sending changes on the record.
smsobject or null
sms.charactersinteger
sms.segmentsinteger
sms.encodingstring

Send a message to a customer

POST /organizations/{organizationId}/messages/send

Customer-facing: sends the (possibly edited) message from POST /messages/send/preview by email or text, now, or at the end of quiet hours. Sending a draft quote marks it sent; sending a draft invoice issues it (SENT). Consent applies: no text without OK-to-text or after STOP (the answer is SKIPPED with the reason). In test mode the message is logged as SENT_TEST.

Permissionmessages.send (Owner, Admin, Staff)
Risksensitive, OAuth scope skuflow.sensitive. Sensitive: moves money, deletes, sends or shows something to a customer, or grants or revokes access.
MCP toolmessages_send (toolset marketing, in the default set)
ApprovalOver MCP, this tool asks for your OK first (in the AI app when it supports it, otherwise in SKUFlow). See Approvals.

Request body

FieldTypeRequiredDescription
kindenumYesOne of quote, invoice, invoiceReminder, portal, updateCard.
recordIdstringYes
channelenumYesOne of EMAIL, SMS.
subjectstring or nullNoEmail only. Up to 200 characters.
bodystringYesThe message as it should be sent (merge fields still allowed).
contactMethodIdstringNo

Example

curl -X POST "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/messages/send" \
  -H "Authorization: Bearer $SKUFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"kind":"quote","recordId":"record_id","channel":"EMAIL","body":"string"}'

Response

200: the answer's fields
FieldTypeDescription
messageobject
message.idstring
message.directionenumOne of OUTBOUND, INBOUND.
message.sourceenumSystem: a message the customer asked for themselves (a portal sign-in code; its code is scrubbed once sent). One of AUTOMATION, MANUAL, TEST, INBOUND, CAMPAIGN, SYSTEM.
message.channelenumOne of EMAIL, SMS.
message.purposeenumOne of TRANSACTIONAL, MARKETING.
message.statusenumOne of QUEUED, DEFERRED, SENDING, SENT, SENT_TEST, DELIVERED, FAILED, BOUNCED, SKIPPED, CANCELLED, RECEIVED.
message.statusReasonstring or null
message.providerstring or null
message.toAddressstring or null
message.fromAddressstring or null
message.subjectstring or null
message.previewstringThe first 160 characters of the body.
message.customerIdstring or null
message.customerNamestring or null
message.ruleIdstring or null
message.ruleNamestring or null
message.campaignIdstring or nullThe marketing campaign it belongs to (source CAMPAIGN).
message.openedAtstring or nullFirst open the email provider reported.
message.clickedAtstring or nullFirst click the email provider reported.
message.subjectTypestring or null
message.subjectIdstring or null
message.scheduledForstringISO 8601 time (UTC).
message.sentAtstring or nullISO 8601 time (UTC).
message.createdAtstringISO 8601 time (UTC).
markedbooleanThe quote / invoice was marked sent.

List text conversations

GET /organizations/{organizationId}/conversations

The Conversations inbox: customers' text conversations (a customer's texts, or a number's when it is nobody's) in which the customer texted or staff replied, newest first, with the last text and whether it is unread (the customer's newest text is newer than the last time anyone on the team opened it). unread=true keeps the ones waiting. unread in the answer counts every unread conversation. Read-only.

Permissionengagement.read (Owner, Admin, Staff)
Riskread, OAuth scope skuflow.read. Read: reads only.
MCP toolmessages_conversations (toolset marketing)

Parameters

NameInTypeRequiredDescription
unreadquerybooleanNoOnly conversations with an unread customer text.
queryquerystringNoA customer's name or digits of the number. Up to 200 characters.
limitqueryintegerNoPage size, 1–200. Default 50.
offsetqueryintegerNoRows to skip; the answer's total counts every match. Default 0.

Example

curl "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/conversations" \
  -H "Authorization: Bearer $SKUFLOW_API_KEY"

Response

200: the answer's fields
FieldTypeDescription
itemsarray of object
items[].keystringcustomer:<customerId>, or phone:<+E.164> for a number that is nobody's.
items[].customerIdstring or null
items[].customerNamestring or null
items[].phonestring or nullThe customer's number in the newest text.
items[].lastMessageobject
items[].lastMessage.idstring
items[].lastMessage.directionenumOne of OUTBOUND, INBOUND.
items[].lastMessage.statusenumOne of QUEUED, DEFERRED, SENDING, SENT, SENT_TEST, DELIVERED, FAILED, BOUNCED, SKIPPED, CANCELLED, RECEIVED.
items[].lastMessage.previewstringThe first 160 characters.
items[].lastMessage.createdAtstring
items[].lastAtstring
items[].lastInboundAtstring or nullThe customer's newest text (not STOP / START / HELP).
items[].lastReadAtstring or nullWhen someone on the team last opened it.
items[].unreadbooleanThe customer's newest text is newer than lastReadAt.
totalinteger
unreadintegerUnread conversations in the business.

Get a text conversation

GET /organizations/{organizationId}/conversations/thread

One text conversation, oldest first: the customer's texts and the business's (automated, sent by staff, with their status: queued, deferred, sent, delivered, failed), who sent each, whether it is unread, who a reply goes to and whether consent allows it (with the reason), and whether the business can text now (texting set up, A2P approved). Does not mark it read (POST /conversations/read). Read-only.

Permissionengagement.read (Owner, Admin, Staff)
Riskread, OAuth scope skuflow.read. Read: reads only.
MCP toolmessages_conversation (toolset marketing)

Parameters

NameInTypeRequiredDescription
customerIdquerystringNoThe customer's conversation (all their texts). Give this or phone.
phonequerystringNoA number that is nobody's (+E.164), from GET /conversations. Give this or customerId. Up to 32 characters.
limitqueryintegerNoNewest texts to return (100). 1–500.

Example

curl "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/conversations/thread" \
  -H "Authorization: Bearer $SKUFLOW_API_KEY"

Response

200: the answer's fields
FieldTypeDescription
keystring
customerIdstring or null
customerNamestring or null
phonestring or nullThe number a reply goes to.
totalintegerTexts in the conversation (the newest limit are returned).
messagesarray of objectOldest first.
messages[].idstring
messages[].directionenumOne of OUTBOUND, INBOUND.
messages[].sourceenumOne of AUTOMATION, MANUAL, TEST, INBOUND, CAMPAIGN, SYSTEM.
messages[].statusenumOne of QUEUED, DEFERRED, SENDING, SENT, SENT_TEST, DELIVERED, FAILED, BOUNCED, SKIPPED, CANCELLED, RECEIVED.
messages[].statusReasonstring or null
messages[].bodystring
messages[].fromAddressstring or null
messages[].toAddressstring or null
messages[].sentByUserIdstring or null
messages[].sentByNamestring or nullThe staff member who sent it (manual texts).
messages[].ruleIdstring or null
messages[].ruleNamestring or nullThe automation that sent it.
messages[].createdAtstring
messages[].sentAtstring or nullISO 8601 time (UTC).
messages[].deliveredAtstring or nullISO 8601 time (UTC).
messages[].failedAtstring or nullISO 8601 time (UTC).
messages[].scheduledForstring
lastReadAtstring or nullISO 8601 time (UTC).
unreadboolean
replyobject
reply.tostring or null
reply.allowedbooleanConsent allows a reply (STOP, OK to text, texts off).
reply.reasonstring or nullWhy not, in words for the person.
reply.conversationalbooleanThe number texted the business in the last 30 days: the reply needs no OK to text and is not held by quiet hours.
reply.deferredUntilstring or nullA text sent now would wait for the end of quiet hours.
textingobject
texting.readybooleanTexts can be sent now (test mode counts: they are logged).
texting.testModebooleanNo provider: texts are logged as SENT_TEST, not sent.
texting.fromstring or nullThe business's texting number.
texting.blockenum or nullOne of not_set_up, a2p_pending, suspended.
texting.reasonstring or null
texting.settingsPathstring or nullWhere to fix it in the web app, after /{organizationSlug}.

Mark a text conversation read

POST /organizations/{organizationId}/conversations/read

Opening a conversation: it is read for the whole team (until the customer texts again) and the caller's own "New text from …" notifications for it are read. Changes nothing the customer sees.

Permissionengagement.read (Owner, Admin, Staff)
Riskwrite, OAuth scope skuflow.write. Write: creates or changes records inside the business; nothing leaves it.
MCP toolmessages_markConversationRead (toolset marketing)

Request body

FieldTypeRequiredDescription
customerIdstringNoThe customer's conversation (all their texts). Give this or phone.
phonestringNoA number that is nobody's (+E.164), from GET /conversations. Give this or customerId. Up to 32 characters.

Example

curl -X POST "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/conversations/read" \
  -H "Authorization: Bearer $SKUFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"customerId":"customer_id","phone":"+15555550123"}'

Response

200: the answer's fields
FieldTypeDescription
keystring
lastReadAtstring
notificationsReadinteger

Text a customer (reply)

POST /organizations/{organizationId}/conversations/reply

Customer-facing: sends a free-form text (1–1,600 characters, sent as written; no merge fields) from the business's texting number to the customer's conversation, answering their newest text (recorded as sent by the caller). It goes to the number that last texted in, else the customer's mobile. Refused with the reason: texting not set up or the A2P 10DLC registration not approved (fix in Settings → Messaging), a number that replied STOP, a number marked not to text, texts turned off for the customer, or no OK to text when the number has not texted the business in the last 30 days (answering a customer who texted counts as consent for the reply, never for marketing). An answer goes now, even in quiet hours; a text that starts a conversation waits for the end of quiet hours (DEFERRED). In test mode it is logged as SENT_TEST. Supports Idempotency-Key.

Permissionmessages.send (Owner, Admin, Staff)
Risksensitive, OAuth scope skuflow.sensitive. Sensitive: moves money, deletes, sends or shows something to a customer, or grants or revokes access.
MCP toolmessages_reply (toolset marketing)
ApprovalOver MCP, this tool asks for your OK first (in the AI app when it supports it, otherwise in SKUFlow). See Approvals.

Request body

FieldTypeRequiredDescription
customerIdstringNoThe customer's conversation (all their texts). Give this or phone.
phonestringNoA number that is nobody's (+E.164), from GET /conversations. Give this or customerId. Up to 32 characters.
bodystringYesThe text, exactly as it should arrive. Up to 1600 characters.

Example

curl -X POST "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/conversations/reply" \
  -H "Authorization: Bearer $SKUFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"body":"string"}'

Response

200: the answer's fields
FieldTypeDescription
idstring
directionenumOne of OUTBOUND, INBOUND.
sourceenumSystem: a message the customer asked for themselves (a portal sign-in code; its code is scrubbed once sent). One of AUTOMATION, MANUAL, TEST, INBOUND, CAMPAIGN, SYSTEM.
channelenumOne of EMAIL, SMS.
purposeenumOne of TRANSACTIONAL, MARKETING.
statusenumOne of QUEUED, DEFERRED, SENDING, SENT, SENT_TEST, DELIVERED, FAILED, BOUNCED, SKIPPED, CANCELLED, RECEIVED.
statusReasonstring or null
providerstring or null
toAddressstring or null
fromAddressstring or null
subjectstring or null
previewstringThe first 160 characters of the body.
customerIdstring or null
customerNamestring or null
ruleIdstring or null
ruleNamestring or null
campaignIdstring or nullThe marketing campaign it belongs to (source CAMPAIGN).
openedAtstring or nullFirst open the email provider reported.
clickedAtstring or nullFirst click the email provider reported.
subjectTypestring or null
subjectIdstring or null
scheduledForstringISO 8601 time (UTC).
sentAtstring or nullISO 8601 time (UTC).
createdAtstringISO 8601 time (UTC).

On this page