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.
| Operation | Endpoint | Risk |
|---|---|---|
| List messages | GET /organizations/{organizationId}/messages | read |
| Get a message | GET /organizations/{organizationId}/messages/{messageId} | read |
| Preview a manual send | POST /organizations/{organizationId}/messages/send/preview | write |
| Send a message to a customer | POST /organizations/{organizationId}/messages/send | sensitive |
| List text conversations | GET /organizations/{organizationId}/conversations | read |
| Get a text conversation | GET /organizations/{organizationId}/conversations/thread | read |
| Mark a text conversation read | POST /organizations/{organizationId}/conversations/read | write |
| Text a customer (reply) | POST /organizations/{organizationId}/conversations/reply | sensitive |
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.
| Permission | engagement.read (Owner, Admin, Staff) |
| Risk | read, OAuth scope skuflow.read. Read: reads only. |
| MCP tool | messages_list (toolset marketing) |
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
status | query | enum | No | One of QUEUED, DEFERRED, SENDING, SENT, SENT_TEST, DELIVERED, FAILED, BOUNCED, SKIPPED, CANCELLED, RECEIVED. |
channel | query | enum | No | One of EMAIL, SMS. |
direction | query | enum | No | INBOUND = the replies inbox. One of OUTBOUND, INBOUND. |
customerId | query | string | No | |
ruleId | query | string | No | |
campaignId | query | string | No | A marketing campaign's messages. |
query | query | string | No | Text in the address, subject, body or customer name. Up to 200 characters. |
from | query | string (date-time) | No | |
to | query | string (date-time) | No | |
limit | query | integer | No | Page size, 1–200. Default 50. |
offset | query | integer | No | Rows 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
| Field | Type | Description |
|---|---|---|
items | array of object | |
items[].id | string | |
items[].direction | enum | One of OUTBOUND, INBOUND. |
items[].source | enum | System: 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[].channel | enum | One of EMAIL, SMS. |
items[].purpose | enum | One of TRANSACTIONAL, MARKETING. |
items[].status | enum | One of QUEUED, DEFERRED, SENDING, SENT, SENT_TEST, DELIVERED, FAILED, BOUNCED, SKIPPED, CANCELLED, RECEIVED. |
items[].statusReason | string or null | |
items[].provider | string or null | |
items[].toAddress | string or null | |
items[].fromAddress | string or null | |
items[].subject | string or null | |
items[].preview | string | The first 160 characters of the body. |
items[].customerId | string or null | |
items[].customerName | string or null | |
items[].ruleId | string or null | |
items[].ruleName | string or null | |
items[].campaignId | string or null | The marketing campaign it belongs to (source CAMPAIGN). |
items[].openedAt | string or null | First open the email provider reported. |
items[].clickedAt | string or null | First click the email provider reported. |
items[].subjectType | string or null | |
items[].subjectId | string or null | |
items[].scheduledFor | string | ISO 8601 time (UTC). |
items[].sentAt | string or null | ISO 8601 time (UTC). |
items[].createdAt | string | ISO 8601 time (UTC). |
total | integer |
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.
| Permission | engagement.read (Owner, Admin, Staff) |
| Risk | read, OAuth scope skuflow.read. Read: reads only. |
| MCP tool | messages_get (toolset marketing) |
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
messageId | path | string | Yes | From 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
| Field | Type | Description |
|---|---|---|
id | string | |
direction | enum | One of OUTBOUND, INBOUND. |
source | enum | System: 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. |
channel | enum | One of EMAIL, SMS. |
purpose | enum | One of TRANSACTIONAL, MARKETING. |
status | enum | One of QUEUED, DEFERRED, SENDING, SENT, SENT_TEST, DELIVERED, FAILED, BOUNCED, SKIPPED, CANCELLED, RECEIVED. |
statusReason | string or null | |
provider | string or null | |
toAddress | string or null | |
fromAddress | string or null | |
subject | string or null | |
customerId | string or null | |
customerName | string or null | |
ruleId | string or null | |
ruleName | string or null | |
campaignId | string or null | The marketing campaign it belongs to (source CAMPAIGN). |
openedAt | string or null | First open the email provider reported. |
clickedAt | string or null | First click the email provider reported. |
subjectType | string or null | |
subjectId | string or null | |
scheduledFor | string | ISO 8601 time (UTC). |
sentAt | string or null | ISO 8601 time (UTC). |
createdAt | string | ISO 8601 time (UTC). |
body | string | |
html | string or null | |
providerMessageId | string or null | |
contactMethodId | string or null | |
templateId | string or null | |
variantId | string or null | |
stepIndex | integer or null | |
sentByUserId | string or null | |
deliveredAt | string or null | ISO 8601 time (UTC). |
failedAt | string or null | ISO 8601 time (UTC). |
attemptCount | integer | |
timeline | array of object | |
timeline[].at | string | |
timeline[].status | string | |
timeline[].note | string | |
context | object | |
inReplyTo | object or null | |
inReplyTo.id | string | |
inReplyTo.direction | enum | One of OUTBOUND, INBOUND. |
inReplyTo.source | enum | System: 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.channel | enum | One of EMAIL, SMS. |
inReplyTo.purpose | enum | One of TRANSACTIONAL, MARKETING. |
inReplyTo.status | enum | One of QUEUED, DEFERRED, SENDING, SENT, SENT_TEST, DELIVERED, FAILED, BOUNCED, SKIPPED, CANCELLED, RECEIVED. |
inReplyTo.statusReason | string or null | |
inReplyTo.provider | string or null | |
inReplyTo.toAddress | string or null | |
inReplyTo.fromAddress | string or null | |
inReplyTo.subject | string or null | |
inReplyTo.preview | string | The first 160 characters of the body. |
inReplyTo.customerId | string or null | |
inReplyTo.customerName | string or null | |
inReplyTo.ruleId | string or null | |
inReplyTo.ruleName | string or null | |
inReplyTo.campaignId | string or null | The marketing campaign it belongs to (source CAMPAIGN). |
inReplyTo.openedAt | string or null | First open the email provider reported. |
inReplyTo.clickedAt | string or null | First click the email provider reported. |
inReplyTo.subjectType | string or null | |
inReplyTo.subjectId | string or null | |
inReplyTo.scheduledFor | string | ISO 8601 time (UTC). |
inReplyTo.sentAt | string or null | ISO 8601 time (UTC). |
inReplyTo.createdAt | string | ISO 8601 time (UTC). |
replies | array of object | |
replies[].id | string | |
replies[].direction | enum | One of OUTBOUND, INBOUND. |
replies[].source | enum | System: 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[].channel | enum | One of EMAIL, SMS. |
replies[].purpose | enum | One of TRANSACTIONAL, MARKETING. |
replies[].status | enum | One of QUEUED, DEFERRED, SENDING, SENT, SENT_TEST, DELIVERED, FAILED, BOUNCED, SKIPPED, CANCELLED, RECEIVED. |
replies[].statusReason | string or null | |
replies[].provider | string or null | |
replies[].toAddress | string or null | |
replies[].fromAddress | string or null | |
replies[].subject | string or null | |
replies[].preview | string | The first 160 characters of the body. |
replies[].customerId | string or null | |
replies[].customerName | string or null | |
replies[].ruleId | string or null | |
replies[].ruleName | string or null | |
replies[].campaignId | string or null | The marketing campaign it belongs to (source CAMPAIGN). |
replies[].openedAt | string or null | First open the email provider reported. |
replies[].clickedAt | string or null | First click the email provider reported. |
replies[].subjectType | string or null | |
replies[].subjectId | string or null | |
replies[].scheduledFor | string | ISO 8601 time (UTC). |
replies[].sentAt | string or null | ISO 8601 time (UTC). |
replies[].createdAt | string | ISO 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.
| Permission | messages.send (Owner, Admin, Staff) |
| Risk | write, OAuth scope skuflow.write. Write: creates or changes records inside the business; nothing leaves it. |
| MCP tool | messages_previewSend (toolset marketing, in the default set) |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
kind | enum | Yes | quote | invoice | invoiceReminder | portal One of quote, invoice, invoiceReminder, portal, updateCard. |
recordId | string | Yes | The quote, invoice or customer (portal) id. |
channel | enum | Yes | One of EMAIL, SMS. |
contactMethodId | string | No | Send 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
| Field | Type | Description |
|---|---|---|
customerId | string | |
channel | enum | One of EMAIL, SMS. |
recipient | object | |
recipient.contactMethodId | string or null | |
recipient.name | string or null | |
recipient.address | string or null | |
recipient.allowed | boolean | Whether consent allows this send. |
recipient.reason | string or null | |
options | array of object | |
options[].contactMethodId | string | |
options[].label | string | |
options[].smsConsent | string or null | |
subject | string or null | |
body | string | The rendered message to edit before sending. |
unresolved | array of string | |
deferredUntil | string or null | Set when quiet hours hold it: when it will go. |
marksAs | string or null | What sending changes on the record. |
sms | object or null | |
sms.characters | integer | |
sms.segments | integer | |
sms.encoding | string |
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.
| Permission | messages.send (Owner, Admin, Staff) |
| Risk | sensitive, OAuth scope skuflow.sensitive. Sensitive: moves money, deletes, sends or shows something to a customer, or grants or revokes access. |
| MCP tool | messages_send (toolset marketing, in the default set) |
| Approval | Over MCP, this tool asks for your OK first (in the AI app when it supports it, otherwise in SKUFlow). See Approvals. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
kind | enum | Yes | One of quote, invoice, invoiceReminder, portal, updateCard. |
recordId | string | Yes | |
channel | enum | Yes | One of EMAIL, SMS. |
subject | string or null | No | Email only. Up to 200 characters. |
body | string | Yes | The message as it should be sent (merge fields still allowed). |
contactMethodId | string | No |
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
| Field | Type | Description |
|---|---|---|
message | object | |
message.id | string | |
message.direction | enum | One of OUTBOUND, INBOUND. |
message.source | enum | System: 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.channel | enum | One of EMAIL, SMS. |
message.purpose | enum | One of TRANSACTIONAL, MARKETING. |
message.status | enum | One of QUEUED, DEFERRED, SENDING, SENT, SENT_TEST, DELIVERED, FAILED, BOUNCED, SKIPPED, CANCELLED, RECEIVED. |
message.statusReason | string or null | |
message.provider | string or null | |
message.toAddress | string or null | |
message.fromAddress | string or null | |
message.subject | string or null | |
message.preview | string | The first 160 characters of the body. |
message.customerId | string or null | |
message.customerName | string or null | |
message.ruleId | string or null | |
message.ruleName | string or null | |
message.campaignId | string or null | The marketing campaign it belongs to (source CAMPAIGN). |
message.openedAt | string or null | First open the email provider reported. |
message.clickedAt | string or null | First click the email provider reported. |
message.subjectType | string or null | |
message.subjectId | string or null | |
message.scheduledFor | string | ISO 8601 time (UTC). |
message.sentAt | string or null | ISO 8601 time (UTC). |
message.createdAt | string | ISO 8601 time (UTC). |
marked | boolean | The 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.
| Permission | engagement.read (Owner, Admin, Staff) |
| Risk | read, OAuth scope skuflow.read. Read: reads only. |
| MCP tool | messages_conversations (toolset marketing) |
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
unread | query | boolean | No | Only conversations with an unread customer text. |
query | query | string | No | A customer's name or digits of the number. Up to 200 characters. |
limit | query | integer | No | Page size, 1–200. Default 50. |
offset | query | integer | No | Rows 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
| Field | Type | Description |
|---|---|---|
items | array of object | |
items[].key | string | customer:<customerId>, or phone:<+E.164> for a number that is nobody's. |
items[].customerId | string or null | |
items[].customerName | string or null | |
items[].phone | string or null | The customer's number in the newest text. |
items[].lastMessage | object | |
items[].lastMessage.id | string | |
items[].lastMessage.direction | enum | One of OUTBOUND, INBOUND. |
items[].lastMessage.status | enum | One of QUEUED, DEFERRED, SENDING, SENT, SENT_TEST, DELIVERED, FAILED, BOUNCED, SKIPPED, CANCELLED, RECEIVED. |
items[].lastMessage.preview | string | The first 160 characters. |
items[].lastMessage.createdAt | string | |
items[].lastAt | string | |
items[].lastInboundAt | string or null | The customer's newest text (not STOP / START / HELP). |
items[].lastReadAt | string or null | When someone on the team last opened it. |
items[].unread | boolean | The customer's newest text is newer than lastReadAt. |
total | integer | |
unread | integer | Unread 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.
| Permission | engagement.read (Owner, Admin, Staff) |
| Risk | read, OAuth scope skuflow.read. Read: reads only. |
| MCP tool | messages_conversation (toolset marketing) |
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
customerId | query | string | No | The customer's conversation (all their texts). Give this or phone. |
phone | query | string | No | A number that is nobody's (+E.164), from GET /conversations. Give this or customerId. Up to 32 characters. |
limit | query | integer | No | Newest 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
| Field | Type | Description |
|---|---|---|
key | string | |
customerId | string or null | |
customerName | string or null | |
phone | string or null | The number a reply goes to. |
total | integer | Texts in the conversation (the newest limit are returned). |
messages | array of object | Oldest first. |
messages[].id | string | |
messages[].direction | enum | One of OUTBOUND, INBOUND. |
messages[].source | enum | One of AUTOMATION, MANUAL, TEST, INBOUND, CAMPAIGN, SYSTEM. |
messages[].status | enum | One of QUEUED, DEFERRED, SENDING, SENT, SENT_TEST, DELIVERED, FAILED, BOUNCED, SKIPPED, CANCELLED, RECEIVED. |
messages[].statusReason | string or null | |
messages[].body | string | |
messages[].fromAddress | string or null | |
messages[].toAddress | string or null | |
messages[].sentByUserId | string or null | |
messages[].sentByName | string or null | The staff member who sent it (manual texts). |
messages[].ruleId | string or null | |
messages[].ruleName | string or null | The automation that sent it. |
messages[].createdAt | string | |
messages[].sentAt | string or null | ISO 8601 time (UTC). |
messages[].deliveredAt | string or null | ISO 8601 time (UTC). |
messages[].failedAt | string or null | ISO 8601 time (UTC). |
messages[].scheduledFor | string | |
lastReadAt | string or null | ISO 8601 time (UTC). |
unread | boolean | |
reply | object | |
reply.to | string or null | |
reply.allowed | boolean | Consent allows a reply (STOP, OK to text, texts off). |
reply.reason | string or null | Why not, in words for the person. |
reply.conversational | boolean | The number texted the business in the last 30 days: the reply needs no OK to text and is not held by quiet hours. |
reply.deferredUntil | string or null | A text sent now would wait for the end of quiet hours. |
texting | object | |
texting.ready | boolean | Texts can be sent now (test mode counts: they are logged). |
texting.testMode | boolean | No provider: texts are logged as SENT_TEST, not sent. |
texting.from | string or null | The business's texting number. |
texting.block | enum or null | One of not_set_up, a2p_pending, suspended. |
texting.reason | string or null | |
texting.settingsPath | string or null | Where 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.
| Permission | engagement.read (Owner, Admin, Staff) |
| Risk | write, OAuth scope skuflow.write. Write: creates or changes records inside the business; nothing leaves it. |
| MCP tool | messages_markConversationRead (toolset marketing) |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
customerId | string | No | The customer's conversation (all their texts). Give this or phone. |
phone | string | No | A 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
| Field | Type | Description |
|---|---|---|
key | string | |
lastReadAt | string | |
notificationsRead | integer |
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.
| Permission | messages.send (Owner, Admin, Staff) |
| Risk | sensitive, OAuth scope skuflow.sensitive. Sensitive: moves money, deletes, sends or shows something to a customer, or grants or revokes access. |
| MCP tool | messages_reply (toolset marketing) |
| Approval | Over MCP, this tool asks for your OK first (in the AI app when it supports it, otherwise in SKUFlow). See Approvals. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
customerId | string | No | The customer's conversation (all their texts). Give this or phone. |
phone | string | No | A number that is nobody's (+E.164), from GET /conversations. Give this or customerId. Up to 32 characters. |
body | string | Yes | The 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
| Field | Type | Description |
|---|---|---|
id | string | |
direction | enum | One of OUTBOUND, INBOUND. |
source | enum | System: 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. |
channel | enum | One of EMAIL, SMS. |
purpose | enum | One of TRANSACTIONAL, MARKETING. |
status | enum | One of QUEUED, DEFERRED, SENDING, SENT, SENT_TEST, DELIVERED, FAILED, BOUNCED, SKIPPED, CANCELLED, RECEIVED. |
statusReason | string or null | |
provider | string or null | |
toAddress | string or null | |
fromAddress | string or null | |
subject | string or null | |
preview | string | The first 160 characters of the body. |
customerId | string or null | |
customerName | string or null | |
ruleId | string or null | |
ruleName | string or null | |
campaignId | string or null | The marketing campaign it belongs to (source CAMPAIGN). |
openedAt | string or null | First open the email provider reported. |
clickedAt | string or null | First click the email provider reported. |
subjectType | string or null | |
subjectId | string or null | |
scheduledFor | string | ISO 8601 time (UTC). |
sentAt | string or null | ISO 8601 time (UTC). |
createdAt | string | ISO 8601 time (UTC). |
Message templates API
REST endpoints for message templates in SKUFlow: 7 operations with parameters, request and response fields, permissions and curl examples.
Customer messaging API
REST endpoints for customer messaging in SKUFlow: 2 operations with parameters, request and response fields, permissions and curl examples.