A valid request URL is required to generate request examples{
"success": true,
"data": {
"messageId": "8c1f4e2a-3b5d-4c6e-9f70-812a3b4c5d6e",
"conversationId": "conv_9d2f01",
"seq": 4,
"from": "+14155550100",
"to": "+14155551234",
"text": "Kiwi Cleaning: Hi Dana - your cleaning is confirmed for Tue 9am. Msg & data rates may apply. Reply HELP for help, STOP to opt out.",
"segments": 1,
"delivered": true,
"clientMessageId": "6f1c2d3e-4a5b-6789-abcd-ef0123456789"
}
}{
"type": "about:blank",
"title": "<string>",
"status": 123,
"detail": "<string>",
"instance": "<string>",
"code": "<string>",
"errors": [
{
"field": "<string>",
"message": "<string>"
}
]
}{
"type": "about:blank",
"title": "<string>",
"status": 123,
"detail": "<string>",
"instance": "<string>",
"code": "<string>",
"errors": [
{
"field": "<string>",
"message": "<string>"
}
]
}{
"type": "about:blank",
"title": "<string>",
"status": 123,
"detail": "<string>",
"instance": "<string>",
"code": "<string>",
"errors": [
{
"field": "<string>",
"message": "<string>"
}
]
}{
"type": "about:blank",
"title": "<string>",
"status": 123,
"detail": "<string>",
"instance": "<string>",
"code": "<string>",
"errors": [
{
"field": "<string>",
"message": "<string>"
}
]
}{
"type": "about:blank",
"title": "<string>",
"status": 123,
"detail": "<string>",
"instance": "<string>",
"code": "<string>",
"errors": [
{
"field": "<string>",
"message": "<string>"
}
]
}{
"type": "about:blank",
"title": "<string>",
"status": 123,
"detail": "<string>",
"instance": "<string>",
"code": "<string>",
"errors": [
{
"field": "<string>",
"message": "<string>"
}
]
}{
"success": false,
"code": "upstream_error",
"message": "the carrier rejected the message; nothing was sent — check the number and try again"
}{
"type": "about:blank",
"title": "<string>",
"status": 123,
"detail": "<string>",
"instance": "<string>",
"code": "<string>",
"errors": [
{
"field": "<string>",
"message": "<string>"
}
]
}Send an outbound text message from an SMS-enabled number
Send ONE text message to a person from a number your workspace owns. This is the plain, programmatic send: no AI agent composes it, no teammate has to be signed in, and no external-agent handoff is opened. The text lands on that person’s ordinary conversation for the sending number — if they reply, the reply is handled exactly like any inbound text to that number (by its assigned agent), with this message in the conversation history. Every send goes through the same compliance chokepoint as every other text the platform sends, so none of the rules below are optional or configurable per request. COMPLIANCE (all enforced server-side):
frommust be a number this workspace owns that is SMS-enabled (smsStatus: activeon getSMSRegistration) and assigned to an agent. A number you do not own is 422from_not_owned; one not yet enabled (or still pending) for SMS is 422from_not_sms_active; one with no assigned agent is 422from_has_no_agent— the assigned agent is what keeps the customer’s reply on the same conversation.tomust be a US or Canada (+1) number. The platform’s numbers and its A2P 10DLC registration are North American, so any other destination is refused 422unsupported_country— nothing is sent.tomust also be a customer: one of this workspace’s own numbers (active or not) is refused 422invalid_to, so two of your agents can never text each other in a loop.- The recipient must have a recorded consent basis:
conversational(they texted one of your numbers first) orexpress(consent your business captured — record it with PUT /sms/contacts//consent before sending). Otherwise 422consent_required. There is no per-request override; recording the consent you hold is the sanctioned path. - A recipient who replied STOP is refused 409
sms_suppresseduntil they text START (or you re-enable them with fresh consent via DELETE /sms/suppressions/). - The message is business-initiated, so it is held to the recipient’s legal texting window (8am–9pm in their local time). Outside it the answer is 409
quiet_hourswithdetails.nextOpen— retry after that instant. - Send the message ONLY. On the first text to a person (and again after 30 days without one) the platform prefixes your registered brand name and appends the required “Msg & data rates may apply. Reply HELP for help, STOP to opt out.” block. Every body is converted to plain ASCII: smart punctuation is mapped to its plain equivalent; accented letters, emoji and other non-ASCII characters are removed. A message that is empty after that conversion is refused (400). The body is then capped at two segments, truncating at a sentence boundary. The receipt’s
textis the body exactly as sent, andsegmentsis what was billed. - Volume is capped per workspace: a rolling 24-hour ceiling on API sends (500 by default). Past it, 429
daily_send_capcarriesdetails.cap,details.windowHours,details.nextOpenandRetry-After. An idempotent replay is answered from the original receipt and never counts against the cap. - A send that did NOT happen is always an explicit error, never a 201 with
delivered: false. - A send refused by a send-time compliance check — an opt-out (or an opt-out status that could not be confirmed), quiet hours, an inactive registration or sending number — is also recorded as an
sms_blockedevent in the workspace’s compliance trail, so the refusal is on record even though nothing was sent. IDEMPOTENCY.client_message_idis required and names ONE message. The key is (workspace,client_message_id), so a replay of the same request returns the original 201 receipt byte for byte and never sends a second text — including against a concurrent duplicate of itself. Reusing an id for a DIFFERENT message is 409client_message_id_reused. Use a fresh UUID per message. WHEN THE OUTCOME IS UNKNOWN. If the carrier request fails in a way that does not prove the message stayed home (a timeout, a transport failure, a carrier 5xx), the answer is 409send_state_unknowncarryingdetails.clientMessageIdanddetails.conversationId. It is the ONLY refusal here that does not promise the recipient’s phone stayed silent, so do not retry with a new id: replay the SAMEclient_message_id— a replay resolves to the same answer, or to the receipt once the state settles, and never sends a second text. If the carrier ANSWERED and rejected the request, the answer is 502upstream_errorinstead: nothing was sent and the id has been released, so a corrected retry is safe. TRANSIENT REFUSALS. 503opt_out_check_unavailablemeans the recipient’s opt-out status could not be confirmed, and 503try_againmeans the platform is busy. Neither is an opt-out or a verdict on the message: nothing was sent and theclient_message_idis not spent, so retry the same request shortly. OWNERSHIP. If someone else is already handling that person’s conversation — an inbound message is being answered right now, a teammate has taken the thread over, or an external-agent handoff owns it — the send is refused 409conversation_busywithdetails.reason(inbound_turn_in_flight,seat_owns_threadorlive_escalation) rather than talking over them.
A valid request URL is required to generate request examples{
"success": true,
"data": {
"messageId": "8c1f4e2a-3b5d-4c6e-9f70-812a3b4c5d6e",
"conversationId": "conv_9d2f01",
"seq": 4,
"from": "+14155550100",
"to": "+14155551234",
"text": "Kiwi Cleaning: Hi Dana - your cleaning is confirmed for Tue 9am. Msg & data rates may apply. Reply HELP for help, STOP to opt out.",
"segments": 1,
"delivered": true,
"clientMessageId": "6f1c2d3e-4a5b-6789-abcd-ef0123456789"
}
}{
"type": "about:blank",
"title": "<string>",
"status": 123,
"detail": "<string>",
"instance": "<string>",
"code": "<string>",
"errors": [
{
"field": "<string>",
"message": "<string>"
}
]
}{
"type": "about:blank",
"title": "<string>",
"status": 123,
"detail": "<string>",
"instance": "<string>",
"code": "<string>",
"errors": [
{
"field": "<string>",
"message": "<string>"
}
]
}{
"type": "about:blank",
"title": "<string>",
"status": 123,
"detail": "<string>",
"instance": "<string>",
"code": "<string>",
"errors": [
{
"field": "<string>",
"message": "<string>"
}
]
}{
"type": "about:blank",
"title": "<string>",
"status": 123,
"detail": "<string>",
"instance": "<string>",
"code": "<string>",
"errors": [
{
"field": "<string>",
"message": "<string>"
}
]
}{
"type": "about:blank",
"title": "<string>",
"status": 123,
"detail": "<string>",
"instance": "<string>",
"code": "<string>",
"errors": [
{
"field": "<string>",
"message": "<string>"
}
]
}{
"type": "about:blank",
"title": "<string>",
"status": 123,
"detail": "<string>",
"instance": "<string>",
"code": "<string>",
"errors": [
{
"field": "<string>",
"message": "<string>"
}
]
}{
"success": false,
"code": "upstream_error",
"message": "the carrier rejected the message; nothing was sent — check the number and try again"
}{
"type": "about:blank",
"title": "<string>",
"status": 123,
"detail": "<string>",
"instance": "<string>",
"code": "<string>",
"errors": [
{
"field": "<string>",
"message": "<string>"
}
]
}Authorizations
Flowyte secret API key (Authorization: Bearer flowyte_sk_live_…). Scope-gated; is scoped to your organization — a key can never reach another tenant. The listed scopes in each operation's apiKey requirement are the scopes that key must hold. The tokenUrl is nominal: keys are minted in the dashboard.
Body
One outbound text. Every field is required and whitespace-trimmed before validation. Unlike initiateEscalation there is no agent or destination to name: the conversation is keyed by the from number's assigned agent, so the recipient's reply lands on the same thread.
The sending number in E.164. Must be a number this workspace owns that is SMS-enabled (smsStatus: active) and assigned to an agent.
"+14155550100"
The recipient in E.164. Must be a US or Canada (+1) number, different from from and not one of this workspace's own numbers, with a recorded consent basis (conversational or express).
"+14155551234"
The message. Send the message ONLY — on a first contact the platform prefixes your brand name and appends the "Msg & data rates may apply. Reply HELP for help, STOP to opt out." block. The text is converted to plain ASCII: smart punctuation is mapped to its plain equivalent; accented letters, emoji and other non-ASCII characters are removed. A message that is empty after that conversion is refused (400). The result is capped at two segments (truncating at a sentence boundary).
4000"Hi Dana — your cleaning is confirmed for Tue 9am."
Idempotency key naming this one message (at most 200 characters; a UUID is ideal). A replay returns the original receipt and never sends a second text; reusing it for a different message is 409 client_message_id_reused.
200"6f1c2d3e-4a5b-6789-abcd-ef0123456789"