Messages
Send text message
Sends a normalized text message through the selected provider account. message.text must be non-empty; provider_data carries provider-specific fields such as parse_mode, and quoted replies use the top-level reply_to field.
https://api.unifyport.ai/v1/messagesSupported channels
Before you call
Use a server-side X-Api-Key for the workspace that owns the resource. Replace every placeholder before running a sample.
Complete the selected authorization flow and check runtime_status. Authorization and connection state are separate; HTTP success alone does not establish readiness.
Prepare your parameters
- account_id
- Use data.id from account creation or an account query. Account identifiers belong to the workspace selected by X-Api-Key. Get account
- to.id · to.type
- For a reply, copy data.conversation.id and data.conversation.type from message.received into to.id and to.type. For a new recipient, follow the channel-specific identifier rules. Unified message sending support
Request parameters
Headers
X-Api-KeyWorkspace API key. The workspace is resolved from this header.
Content-TypeUse application/json when sending a JSON request body.
Request body
provider_dataobjectProvider-specific send options, effective only for supported providers and message types. WhatsApp audio/video seconds is a non-negative integer. For video only, use 0 to 4294967295; waveform is audio-only. Use top-level reply_to for quoted replies where supported. For sticker, omit provider_data or use an empty object; a non-object returns 400 invalid_request and a non-empty object returns 400 provider_invalid_request.
provider_dataProvider-specific send options, effective only for supported providers and message types. WhatsApp audio/video seconds is a non-negative integer. For video only, use 0 to 4294967295; waveform is audio-only. Use top-level reply_to for quoted replies where supported. For sticker, omit provider_data or use an empty object; a non-object returns 400 invalid_request and a non-empty object returns 400 provider_invalid_request.
secondsOptional non-negative integer duration in seconds for WhatsApp audio and video messages. For video only, the allowed range is 0 to 4294967295.
minimum: 0
waveformOptional waveform data for WhatsApp audio messages. Non-empty values must use standard Base64 encoding and decode to parseable JSON waveform data; an empty string is treated as omitted, and invalid formats return HTTP 400 provider_invalid_request.
account_idProvider account that sends the message.
minLength: 1
toobjectrequiredRecipient target with id and type.
toRecipient target with id and type.
idChannel-side recipient identifier.
minLength: 1
typeStandard conversation type: user, group, or channel. WhatsApp community announcement groups continue to use group.
enum: user, group, channel
messageobjectrequiredNormalized message payload. Text uses message.text; media uses message.url.
messageNormalized message payload. Text uses message.text; media uses message.url.
typeMessage type: text, image, video, audio, document, file, contact, link_preview, poll, or sticker.
enum: text, image, video, audio, document, file, contact, link_preview, poll, sticker
textNon-empty body required for text and link_preview. For link_preview it must contain a non-whitespace character; WhatsApp also requires the exact link_preview.url in the body.
minLength: 1
captionOptional caption for image, video, document, or file messages.
urlNon-empty absolute HTTP(S) media URL. Media messages require url, file_url, or file_key.
format: uri · pattern: ^[Hh][Tt][Tt][Pp][Ss]?://
file_urlNon-empty alternative absolute HTTP(S) media URL. Media messages require url, file_url, or file_key.
format: uri · pattern: ^[Hh][Tt][Tt][Pp][Ss]?://
file_keyNon-empty provider or storage file reference. Media messages require url, file_url, or file_key.
contacts[]object[]Non-empty array of structured contact cards required when message.type is contact.
minItems: 1
contacts[]Non-empty array of structured contact cards required when message.type is contact.
minItems: 1
nameNon-empty contact display name required for every contact card.
minLength: 1
phones[]object[]Phone entries included in the contact card.
phones[]Phone entries included in the contact card.
numberPhone number; required for every phone entry.
typeOptional phone label such as CELL or WORK.
emails[]object[]Email entries included in the contact card.
emails[]Email entries included in the contact card.
addressEmail address; required for every email entry.
format: email
typeOptional email label such as WORK or HOME.
organizationOptional organization associated with the contact.
titleOptional job title associated with the contact.
link_previewobjectRequired only for message.type=link_preview, together with text; cannot be mixed with other payloads. The public structure requires only url; optional title and description allow empty strings, not null. WhatsApp requires non-empty title/image_url and limits title to 255 and description to 1024 Unicode characters under this API send policy.
link_previewRequired only for message.type=link_preview, together with text; cannot be mixed with other payloads. The public structure requires only url; optional title and description allow empty strings, not null. WhatsApp requires non-empty title/image_url and limits title to 255 and description to 1024 Unicode characters under this API send policy.
urlRequired target link: an absolute HTTP(S) URI with a hostname and no whitespace; bracketed addresses must be IPv6 literals. Preserved verbatim; resource accessibility is not guaranteed.
pattern: ^[Hh][Tt][Tt][Pp][Ss]?://(?:[^/?#@\s]*@)?(?:\[[0-9A-Fa-f:.]+\]|[^/?#:@\s\[\]]+)(?::[0-9]*)?(?:[/?#]|$)
titleOptional preview title; empty string allowed, null forbidden. WhatsApp requires a non-empty title of at most 255 Unicode characters.
descriptionOptional preview summary; empty string allowed, null forbidden. WhatsApp allows at most 1024 Unicode characters.
image_urlOptional image URI, required and non-empty for WhatsApp. Must be an absolute HTTP(S) URI with a hostname and no whitespace; bracketed addresses must be IPv6 literals. Preserved verbatim; resource accessibility is not guaranteed.
pattern: ^[Hh][Tt][Tt][Pp][Ss]?://(?:[^/?#@\s]*@)?(?:\[[0-9A-Fa-f:.]+\]|[^/?#:@\s\[\]]+)(?::[0-9]*)?(?:[/?#]|$)
pollobjectRequired only for message.type=poll; cannot be mixed with text or other payloads. Requires question, options and max_selections. Question and option titles are preserved verbatim. The public structure sets no question length limit, maximum option count or duplicate-title restriction; providers may impose additional limits.
pollRequired only for message.type=poll; cannot be mixed with text or other payloads. Requires question, options and max_selections. Question and option titles are preserved verbatim. The public structure sets no question length limit, maximum option count or duplicate-title restriction; providers may impose additional limits.
questionRequired poll question containing a non-whitespace character; preserved verbatim.
minLength: 1 · pattern: \S
options[]object[]Required array of at least two options, each with a title.
minItems: 2
options[]Required array of at least two options, each with a title.
minItems: 2
titleRequired option title containing a non-whitespace character. Sent without trimming, case changes or Unicode normalization.
minLength: 1 · pattern: \S
max_selectionsRequired maximum selections per voter, not the number of voters. Integer from 1 to the options count, validated by the API. Integer-valued numbers such as 1.0 and 1e0 equal 1; fractional values are invalid.
minimum: 1
reply_toobjectQuoted-reply target. Copy data.message.reply_token from a referable message event or data.reply_token from a send response into reply_to.reply_token unchanged, using the original account and conversation; never substitute a message ID.
reply_toQuoted-reply target. Copy data.message.reply_token from a referable message event or data.reply_token from a send response into reply_to.reply_token unchanged, using the original account and conversation; never substitute a message ID.
reply_tokenOpaque handle from a referable message event data.message.reply_token or send response data.reply_token. Use unchanged with the original account and conversation; never substitute a message ID.
minLength: 1
mentions[]object[]Group-message @ target list. type=member uses the corresponding Provider member id; type=all mentions everyone only when the Provider supports it, otherwise unsupported_message_type is returned.
mentions[]Group-message @ target list. type=member uses the corresponding Provider member id; type=all mentions everyone only when the Provider supports it, otherwise unsupported_message_type is returned.
typeMention target type. Omit it for member compatibility; type=member requires id, while type=all omits id and only applies to group chats.
enum: member, all
idProvider member identifier required when type=member.
minLength: 1
Understand the result
Save message_id and provider_ref to correlate later events or troubleshoot. accepted is not a delivery receipt. Use receipt events only where the channel supports them; identifier formats vary by channel.
reply_token — Optional non-empty opaque WhatsApp handle returned after successful sending when the quoting context is complete, including newly sent polls that ordinary text can quote. Omitted when unavailable without changing accepted status. Use unchanged with the original account and conversation; it is not a message ID.
Response 200 OK
{
"request_id": "<REQUEST_ID>",
"data": {
"message_id": "msg_example",
"account_id": "acc_example",
"status": "accepted",
"provider_ref": "provider_msg_example",
"reply_token": "<opaque WhatsApp reply handle>"
}
}
Response body
message_idUnifyPort message identifier (msg_...) for the accepted message.
account_idProvider account this response refers to.
statusAcceptance status; accepted means the message was queued for delivery to the provider.
provider_refProvider-side message reference, once the provider assigns one.
reply_tokenOptional non-empty opaque WhatsApp handle returned after successful sending when the quoting context is complete, including newly sent polls that ordinary text can quote. Omitted when unavailable without changing accepted status. Use unchanged with the original account and conversation; it is not a message ID.
minLength: 1
Responses
200200 OK
Request succeeded. See the example response body.
400Bad Request
The request body, path, or parameters are invalid.
401Unauthorized
The X-Api-Key header is missing or invalid.
403Forbidden
The request is authenticated but is not allowed.
404Not Found
The requested resource was not found. Check error.code and the current operation.
409Conflict
The requested operation conflicts with an existing provider account or resource.
500Internal Server Error
The service encountered an unexpected error.
501Not Implemented
The selected provider does not implement this operation.
502Bad Gateway
The provider adapter or upstream provider could not complete the operation.
If the request fails
Inspect HTTP status and error.code/numeric_code, and keep request_id for diagnosis. Correct invalid parameters, complete required authorization or check runtime state as appropriate. Confirm the outcome before retrying a send or another write. Error reference
Read HTTP status, error.code and error.numeric_code together in the context of the current operation. Numeric codes are strings. Keep request_id for diagnosis; it is not an idempotency key or a task identifier.
- invalid_reply_token · 36006 · 400
The reply token cannot be read. Copy data.message.reply_token from the inbound event unchanged.
What to do: Correct the parameters for this operation and channel before requesting again. Repeating an unchanged invalid request will not resolve the error.
Illustrative JSON; message varies with the actual cause · 400 · 36006
{ "request_id": "req_example", "error": { "code": "invalid_reply_token", "numeric_code": "36006", "message": "Invalid or unreadable reply_token" } }- invalid_request · 10000 · 400
Check the JSON body and required fields.
What to do: Correct the parameters for this operation and channel before requesting again. Repeating an unchanged invalid request will not resolve the error.
Example · 400 · 10000
{ "request_id": "req_message_invalid_example", "error": { "code": "invalid_request", "numeric_code": "10000", "message": "Invalid message request body" } }- invalid_target_workspace · 11003 · 400
The workspace override target is empty or malformed. Remove the override header and use the API key belonging to the intended workspace.
When it occurs: Applies when the request includes the X-Workspace-Id workspace override header.
What to do: Correct the parameters for this operation and channel before requesting again. Repeating an unchanged invalid request will not resolve the error.
Illustrative JSON; message varies with the actual cause · 400 · 11003
{ "request_id": "req_example", "error": { "code": "invalid_target_workspace", "numeric_code": "11003", "message": "Invalid target workspace" } }- provider_invalid_request · 30001 · 400
Check the channel-specific fields and formats.
What to do: Correct the parameters for this operation and channel before requesting again. Repeating an unchanged invalid request will not resolve the error.
Example · 400 · 30001
{ "request_id": "req_sticker_rejected_example", "error": { "code": "provider_invalid_request", "numeric_code": "30001", "message": "sticker upload failed: media is unsupported" } }- provider_invalid_request · 36007 · 400
Sticker media failed validation before the provider send. The response may give a specific preparation reason; numeric_code=36007 does not mean a send was attempted.
When it occurs: Applies when sticker media or its URL is rejected during validation before sending to the channel.
What to do: Fix the reported sticker media issue before sending again. This classification is returned during media preparation before the provider send; it must not be presented as an unknown completed send.
Example · 400 · 36007
{ "request_id": "req_sticker_invalid_example", "error": { "code": "provider_invalid_request", "numeric_code": "36007", "message": "invalid sticker media: invalid WebP container length" } }- unsupported_message_type · 36000 · 400
Choose a message.type supported by this channel.
What to do: Use a channel and action listed as supported. Repeating the same request does not add capabilities or grant beta access.
Illustrative JSON; message varies with the actual cause · 400 · 36000
{ "request_id": "req_example", "error": { "code": "unsupported_message_type", "numeric_code": "36000", "message": "Message type not supported" } }- invalid_api_key · 11001 · 401
Check whether the API key is correct and enabled.
What to do: Keep request_id and the structured codes, and investigate the current operation before taking another action.
Illustrative JSON; message varies with the actual cause · 401 · 11001
{ "request_id": "req_example", "error": { "code": "invalid_api_key", "numeric_code": "11001", "message": "Invalid API key" } }- missing_api_key · 11000 · 401
Add the X-Api-Key header.
What to do: Keep request_id and the structured codes, and investigate the current operation before taking another action.
Example · 401 · 11000
{ "request_id": "req_xxx", "error": { "code": "missing_api_key", "numeric_code": "11000", "message": "Missing X-Api-Key header" } }- workspace_override_forbidden · 11002 · 403
The request attempts a workspace override that this API key is not allowed to use. Remove the override header and use the API key belonging to the intended workspace.
When it occurs: Applies when the request includes the X-Workspace-Id workspace override header.
What to do: Keep request_id and the structured codes, and investigate the current operation before taking another action.
Illustrative JSON; message varies with the actual cause · 403 · 11002
{ "request_id": "req_example", "error": { "code": "workspace_override_forbidden", "numeric_code": "11002", "message": "Workspace override is not allowed" } }- target_workspace_not_found · 11004 · 404
The overridden workspace was not found or is unavailable. Verify workspace ownership, remove the override header and use the intended workspace’s API key; repeated requests do not grant access.
When it occurs: Applies when the request includes the X-Workspace-Id workspace override header.
What to do: Check the target identifier, resource ownership and current workspace. Do not treat a confirmed missing or inaccessible target as a temporary service failure to retry automatically.
Illustrative JSON; message varies with the actual cause · 404 · 11004
{ "request_id": "req_example", "error": { "code": "target_workspace_not_found", "numeric_code": "11004", "message": "Target workspace not found" } }- account_binding_not_found · 30006 · 409
The account has no active channel connection binding; check its status.
What to do: Query authorization and runtime status, then choose refresh, start or reconnect only when appropriate for the intended state and channel. A connection problem alone does not prove logout. Confirm running before dependent business operations.
Illustrative JSON; message varies with the actual cause · 409 · 30006
{ "request_id": "req_example", "error": { "code": "account_binding_not_found", "numeric_code": "30006", "message": "Account is not bound to an available server" } }- provider_not_ready · 30009 · 409
The connection is not ready. Check authorization and runtime_status before retrying.
What to do: Query authorization and runtime status, then choose refresh, start or reconnect only when appropriate for the intended state and channel. A connection problem alone does not prove logout. Confirm running before dependent business operations.
Illustrative JSON; message varies with the actual cause · 409 · 30009
{ "request_id": "req_example", "error": { "code": "provider_not_ready", "numeric_code": "30009", "message": "Provider client is not ready" } }- provider_not_ready · 30014 · 409
The provider instance needed by this account is missing. This is a runtime availability problem, not proof that authorization expired.
What to do: Query authorization and runtime status, then choose refresh, start or reconnect only when appropriate for the intended state and channel. A connection problem alone does not prove logout. Confirm running before dependent business operations.
Channels: WhatsApp
Illustrative JSON; message varies with the actual cause · 409 · 30014
{ "request_id": "req_example", "error": { "code": "provider_not_ready", "numeric_code": "30014", "message": "Provider instance is missing" } }- provider_not_ready · 30015 · 409
The provider runtime is stopped. Confirm whether that is intentional before starting it; a stopped runtime does not itself mean logout.
What to do: Query authorization and runtime status, then choose refresh, start or reconnect only when appropriate for the intended state and channel. A connection problem alone does not prove logout. Confirm running before dependent business operations.
Channels: WhatsApp
Illustrative JSON; message varies with the actual cause · 409 · 30015
{ "request_id": "req_example", "error": { "code": "provider_not_ready", "numeric_code": "30015", "message": "Provider runtime is stopped" } }- send_message_failed · 20020 · 500
Check account_id and use an X-Api-Key for the workspace that owns the account. An account not found in this workspace cannot be accessed with this key.
When it occurs: Applies when the account is not found; the existing HTTP status and operation code are preserved, while numeric_code identifies the missing account.
What to do: Check the target identifier, resource ownership and current workspace. Do not treat a confirmed missing or inaccessible target as a temporary service failure to retry automatically.
Illustrative JSON; message varies with the actual cause · 500 · 20020
{ "request_id": "req_example", "error": { "code": "send_message_failed", "numeric_code": "20020", "message": "Account not found" } }- send_message_failed · 30010 · 500
The provider could not complete the operation. message can include the original cause after “Provider operation failed:”; the reason text varies and is not a stable program interface.
When it occurs: Applies when the channel supplies no more specific failure classification; the current operation’s error is preserved.
What to do: Confirm whether the original message or reply was created before sending again. A timeout or failed response can leave the outcome unknown; a later query with no matching reply does not prove nothing was written. Do not automatically resend.
Channels: WhatsApp
Illustrative JSON; message varies with the actual cause · 500 · 30010
{ "request_id": "req_example", "error": { "code": "send_message_failed", "numeric_code": "30010", "message": "Provider operation failed" } }- send_message_failed · 36001 · 500
Message sending failed or its result could not be confirmed.
What to do: Confirm whether the original message or reply was created before sending again. A timeout or failed response can leave the outcome unknown; a later query with no matching reply does not prove nothing was written. Do not automatically resend.
Example · 500 · 36001
{ "request_id": "req_sticker_prepare_failed_example", "error": { "code": "send_message_failed", "numeric_code": "36001", "message": "download sticker: media download returned HTTP 503" } }- unsupported_by_provider · 30003 · 501
The channel does not support this operation; choose a supported operation.
What to do: Use a channel and action listed as supported. Repeating the same request does not add capabilities or grant beta access.
Example · 501 · 30003
{ "request_id": "req_xxx", "error": { "code": "unsupported_by_provider", "numeric_code": "30003", "message": "Operation not supported by this provider" } }- provider_adapter_not_found · 30004 · 502
The channel service is unavailable. Check status or try again later; confirm the result before retrying writes.
What to do: Confirm whether the original message or reply was created before sending again. A timeout or failed response can leave the outcome unknown; a later query with no matching reply does not prove nothing was written. Do not automatically resend.
Illustrative JSON; message varies with the actual cause · 502 · 30004
{ "request_id": "req_example", "error": { "code": "provider_adapter_not_found", "numeric_code": "30004", "message": "Provider adapter unavailable" } }- provider_unavailable · 20102 · 502
WhatsApp synchronous message sending identified an account login restriction: HTTP 502, provider_unavailable, 20102. The same numeric meaning can appear as account_restricted in a WhatsApp failure event; the HTTP response does not itself update account state or create an extra event.
What to do: Stop automatic sends and retries, and ask the user to inspect and resolve the restriction in the official WhatsApp app. Resume only after resolution and any required reauthorization. A previously running connection does not prove the restriction has cleared.
Channels: WhatsApp
Example · 502 · 20102
{ "request_id": "req_example_whatsapp_account_restricted", "error": { "code": "provider_unavailable", "numeric_code": "20102", "message": "Account login is restricted. Check the official WhatsApp app for details." } }- provider_unavailable · 30002 · 502
The channel service is unavailable. Check status or try again later; confirm the result before retrying writes.
What to do: Confirm whether the original message or reply was created before sending again. A timeout or failed response can leave the outcome unknown; a later query with no matching reply does not prove nothing was written. Do not automatically resend.
Example · 502 · 30002
{ "request_id": "req_example_provider_unavailable", "error": { "code": "provider_unavailable", "numeric_code": "30002", "message": "Provider service unavailable" } }- provider_unavailable · 30010 · 502
The provider could not complete the operation. message can include the original cause after “Provider operation failed:”; the reason text varies and is not a stable program interface.
What to do: Confirm whether the original message or reply was created before sending again. A timeout or failed response can leave the outcome unknown; a later query with no matching reply does not prove nothing was written. Do not automatically resend.
Channels: WhatsApp
Example · 502 · 30010
{ "request_id": "req_example_message_failed", "error": { "code": "provider_unavailable", "numeric_code": "30010", "message": "Provider operation failed: Example operation could not be completed" } }- provider_unavailable · 30019 · 502
The platform explicitly restricted this message send: HTTP 502, provider_unavailable, 30019. This does not establish a permanent account ban or a fixed time when sending will be allowed again.
What to do: Stop automatic retries for this message restriction. Resume sending only after the platform restriction is confirmed lifted; there is no fixed release time, and this error alone does not require reauthorization.
Channels: WhatsApp
Example · 502 · 30019
{ "request_id": "req_example_message_restricted", "error": { "code": "provider_unavailable", "numeric_code": "30019", "message": "Message sending is restricted by the platform" } }- provider_unavailable · 30020 · 502
A synchronous request using whatsapp-protocol identified an account login restriction with numeric_code=30020. This can affect session import, runtime, sending and contact queries while retaining the current operation’s HTTP status and code. Do not substitute the WhatsApp 20102 event contract for this channel.
What to do: Stop automatic sends and retries, and ask the user to inspect and resolve the restriction in the official WhatsApp app. Resume only after resolution and any required reauthorization. A previously running connection does not prove the restriction has cleared.
Channels: WhatsApp Protocol
Example · 502 · 30020
{ "request_id": "req_example_account_restricted", "error": { "code": "provider_unavailable", "numeric_code": "30020", "message": "Account login is restricted. Check the official WhatsApp app for details." } }- provider_unavailable · 30021 · 502
WhatsApp was not connected when this operation ran. The original connection message is retained. This does not prove logout or expired authorization and does not automatically reconnect, resend or change account state.
What to do: Pause operations that require the connection. First call POST /v1/accounts/{account_id}/runtime/refresh, recover according to the latest state and confirm runtime_status=running before business requests. Confirm the previous write result before resending.
Channels: WhatsApp
Example · 502 · 30021
{ "request_id": "req_example_connection_disconnected", "error": { "code": "provider_unavailable", "numeric_code": "30021", "message": "example operation: websocket not connected" } }- provider_unavailable · 34012 · 502
WhatsApp requires group administrator privileges to send this message. Adjust the account's group role before retrying.
What to do: Keep request_id and the structured codes, and investigate the current operation before taking another action.
Channels: WhatsApp
Example · 502 · 34012
{ "request_id": "req_example_group_admin_required", "error": { "code": "provider_unavailable", "numeric_code": "34012", "message": "Group administrator privileges are required to send messages to this group" } }