Create conversation
Start a direct message conversation with a user. If a conversation with that recipient already exists, the message is added to the existing thread.
Supported platforms: X, Bluesky, Reddit, WhatsApp, SMS, Slack, and iMessage. Other platforms return PLATFORM_NOT_SUPPORTED.
iMessage. Pass the recipient as participantId: a phone number in international format (+15551234567) or an iMessage email. Senders ordered through Zernio can message contacts who never wrote to them. The first message to such a contact must be plain text from an identifiable person or business opening a conversation; a cold introduction, including a sales one, is fine ("Hi Mark, this is John from Pratt Media. I help local roofers get more leads. Would you be open to a quick chat?"). It may not contain links, email addresses, phone numbers, prices or attachments (the provider's own rule for opening messages). One that does returns 400 with code invalid_content, a message saying what was flagged, and nothing is sent. Each sender may open at most one new conversation every 15 minutes, and a new sender warms up: 2 new conversations per 24 hours on days 1-2 after activation, 5 on days 3-4, 10 on days 5-7, 20 on days 8-14, 30 on days 15-21 and 50 after that. Beyond that the request returns 429 with code new_contact_limit and a time to retry. The warm-up counts from when a sender ordered through Zernio went live; senders registered from your own provider account are not warmed up by Zernio. Until the contact replies, the thread accepts the opening and at most two follow-ups, at least 24 hours apart: a follow-up sooner returns 429 new_contact_limit with the time to retry, and a fourth message returns 409 recipient_must_message_first. The provider treats every message to a contact who has not replied as a new conversation, so follow-ups follow the same content rule and count toward the 15-minute spacing and the daily warm-up like an opening. Once the contact has written, the thread behaves like any other and is not limited. Recipients can report a first message as junk, and reports can get a sender blocked permanently, so keep the reply rate high (above roughly 30%). A sender registered with your own provider account may lack the add-on for new contacts; its sends to them return 409 recipient_must_message_first, and an opt-in link is the way in. A contact who opted out returns 409 recipient_opted_out.
Slack. Pass a workspace member id as participantId (list them with GET /v1/accounts/{accountId}/slack-members). Zernio opens the DM channel with that member and sends the message; the thread then behaves like any other Slack conversation in the inbox. The member must belong to the connected workspace.
WhatsApp. This is the endpoint for sending an approved template message to a phone number. Provide templateName, templateLanguage, and templateParams (variable values for the text header, body and dynamic URL buttons, in that order), with the recipient phone in participantId. A template is required because WhatsApp does not permit freeform messages to open a conversation; a missing template returns TEMPLATE_REQUIRED.
Before sending, Zernio must resolve an exact APPROVED template definition matching both templateName and templateLanguage. If Meta rejects that lookup, or the exact approved definition is absent, Zernio sends no message and returns the canonical platform error instead. Lookup errors use code platform_api_error, type platform_error, and platform whatsapp. Sanitized Meta code, message, and error_data.details are returned in platformError; details identifies phase: template_lookup, the query-free endpoint, upstream status, and only safe provider usage or retry headers.
- Templates with media headers (image, video, document) are handled automatically: Zernio reads the approved template definition and fills the header at send time with the template's approved sample asset. To send a DIFFERENT asset per message (e.g. a distinct invoice PDF for each recipient), pass the headerMedia field with a public link (or a Meta media id); it overrides the sample for that send.
- A template whose approved header format is LOCATION has no header asset to reconstruct at all: Meta only accepts the location at send time, so pass headerLocation (latitude and longitude required) whenever such a template is sent; headerMedia and headerLocation cannot both be supplied.
- A button that carries its own value at send time (a copy-code button holding a Pix payment code or a coupon, a flow token) is sent with templateButtonParams, addressed by the button's index; templateParams covers text variables and dynamic URL buttons only.
- CAROUSEL templates take per-card overrides via templateCards, each addressed by the card's card_index, because card body variables restart at {{1}} per card and cannot be expressed in the flat templateParams order.
- Template fields are accepted on the JSON body only, not on multipart requests.
For a number you already have a thread with, this sends the template into that thread, which also makes it the way to re-engage a contact after the 24-hour customer-service window has closed. Once the recipient replies (opening the 24h window), send freeform messages with the send-message endpoint (POST /v1/inbox/conversations/{conversationId}/messages).
Alternatively, WhatsApp Business Accounts eligible for Meta Direct Send can open a conversation with a business-initiated utility text message and no template: pass category: 'utility' together with message (and no templateName). See the category field below.
DM eligibility (X). Before sending, the endpoint checks if the recipient accepts DMs from your account (via the receives_your_dm field). If not, a 422 error with code DM_NOT_ALLOWED is returned. You can skip this check with skipDmCheck: true if you have already verified eligibility.
X API tier requirement. DM write endpoints require X API Pro tier ($5,000/month) or Enterprise access. This applies to BYOK (Bring Your Own Key) users who provide their own X API credentials.
Rate limits (X only). X's DM API enforces 200 requests per 15 minutes, 1,000 per 24 hours per connected X account, and 15,000 per 24 hours per X developer app (shared across all DM endpoints). These limits do NOT apply to other platforms. WhatsApp sends are governed by Meta's per-number messaging tiers (unique business-initiated conversations per 24 hours) and per-number throughput instead.
Request body
Response
Conversation created successfully
Example response
{
"success": true
}Changes
Changed in 8 of the 56 revisions of this API.41812
- ●
added the new
invalid_contentenum value to the/response property for the response statusresponse-property-enum-value-added
- ●
added the new
new_contact_limitenum value to the/response property for the response statusresponse-property-enum-value-added
- ○
added the non-success response with the status
response-non-success-status-added
- ●
- ▲
the response's body type changed from
objectto no type for statusresponse-body-type-changed
- ▲
the response's body type changed from
objectto no type for statusresponse-body-type-changed
- ▲
the response's body type changed from
objectto no type for statusresponse-body-type-changed
- ▲
the response's body type changed from
objectto no type for statusresponse-body-type-changed
- ●
removed the optional property
from the response with the statusresponse-optional-property-removed
- ●
removed the optional property
from the response with the statusresponse-optional-property-removed
- ●
removed the optional property
from the response with the statusresponse-optional-property-removed
- ●
removed the optional property
from the response with the statusresponse-optional-property-removed
- ●
removed the optional property
from the response with the statusresponse-optional-property-removed
- ●
removed the optional property
from the response with the statusresponse-optional-property-removed
- ●
removed the optional property
from the response with the statusresponse-optional-property-removed
- ●
removed the optional property
from the response with the statusresponse-optional-property-removed
- ○
added
subschema #1to the response bodyanyOflist for the response statusresponse-body-any-of-added
- ○
added
subschema #1to the response bodyanyOflist for the response statusresponse-body-any-of-added
- ○
added
subschema #1to the response bodyanyOflist for the response statusresponse-body-any-of-added
- ○
added
subschema #1to the response bodyanyOflist for the response statusresponse-body-any-of-added
- ○
added the non-success response with the status
response-non-success-status-added
- ▲
- ●
added the new
INVALID_TEMPLATE_CARD_PARAMenum value to theresponse property for the response statusresponse-property-enum-value-added
- ○
added the new optional request property
new-optional-request-property
- ●
- ●
added the new
DIRECT_SEND_BLOCKEDenum value to theresponse property for the response statusresponse-property-enum-value-added
- ●
added the new
DIRECT_SEND_LIMITEDenum value to theresponse property for the response statusresponse-property-enum-value-added
- ●
added the new
DIRECT_SEND_NOT_ELIGIBLEenum value to theresponse property for the response statusresponse-property-enum-value-added
- ●
added the new
INVALID_TEMPLATE_BUTTON_PARAMenum value to theresponse property for the response statusresponse-property-enum-value-added
- ●
added the new
INVALID_TEMPLATE_PARAMSenum value to theresponse property for the response statusresponse-property-enum-value-added
- ●
added the new
PLATFORM_LIMITATIONenum value to theresponse property for the response statusresponse-property-enum-value-added
- ●
added the new
TEMPLATE_REQUIREDenum value to theresponse property for the response statusresponse-property-enum-value-added
- ●
- ○
added the new optional request property
new-optional-request-property
This revision also has 1 change that name no endpoint, such as unreferenced schemas being removed. See the revision's changelog
- ○
- ○
added the new optional request property
new-optional-request-property
- ○
- ○
added the media type
application/jsonfor the response with the statusresponse-media-type-added
- ○
added the media type
application/jsonfor the response with the statusresponse-media-type-added
- ○
- ○
added the new optional request property
new-optional-request-property
- ○