Start a conversation
Sends a first message to a phone number on WhatsApp or to an email address, and records it
as a conversation so the recipient’s reply threads into it. This is the endpoint behind
the dashboard’s “New message” composer. Use it when you have an address but not
necessarily a contact. If you already have a contactId and do not need the message in a
thread, POST /api/whatsapp/send and POST /api/email/send are the lower-level sends.
How it works.
- The recipient is validated: E.164 for
whatsapp, a valid address foremail. - The contact is looked up anywhere in the workspace. Email matching ignores case; WhatsApp
matches
whatsapp_numberexactly. An unknown recipient is created in the workspace’s “Manual Conversations” campaign. A contact already enrolled elsewhere is not moved. - The message is delivered through the channel’s provider, with every guard of the lower-level send applied (compliance, opt-out, 24-hour window, daily cap).
- Only after delivery succeeds is the conversation found, reopened or created, using the
same thread key inbound messages use, and the message stored with
metadata.manual = true.
On WhatsApp, pass templateId to reach a contact outside WhatsApp’s 24-hour window. message is
then optional, and the thread records [template] <templateId> as the content. On email,
subject is required. webchat, instagram and messenger cannot be started from here.
Side effects. Sends a real WhatsApp message or a real email from the workspace’s connected mailbox. Consumes one unit of the channel’s daily send cap. The unit is refunded if the provider rejects the send. It does not draw on the AI-message allowance. The AI reply to the contact’s answer does. May create a contact, and the “Manual Conversations” campaign on first use. Broadcasts a realtime update to the dashboard and writes an audit entry.
Idempotency. Not idempotent. A retry after a timeout delivers the message a second time.
The conversation row is reused, so both copies land in the same thread. Check
GET /api/conversations/{conversationId} before retrying.
Webhook events. audit.log_recorded for subscribers that selected it explicitly. See Webhooks.
Access
- Required scope:
write. - Rate limit: General API (120–600 requests/min per workspace by plan) and the configuration-mutations limiter — 10 requests/min per workspace, a budget shared with other configuration mutations. See Rate limits.
- Plan: Available on every plan. The workspace needs at least one agent, a WhatsApp sender that is
ONLINEfor WhatsApp, and a connected mailbox for email.
Authentication
Workspace API key: jl_ followed by 64 lowercase hex characters, created by the workspace owner in the
dashboard (Settings → API Keys) and sent as Authorization: Bearer jl_.... The plaintext is shown once,
at creation; Jelliu stores only a SHA-256 hash. A workspace can hold up to 25 active keys.
Operations restricted to admins or owners reject keys without the full scope with 403, and say so in
their description. No key, whatever its scope, can mint or revoke API keys or rotate a webhook secret —
that requires a signed-in owner session. A revoked key stops authenticating within about 10 seconds.
See Authentication.
Request
message is required unless channel is whatsapp and templateId is sent.
subject is required when channel is email. Strings are trimmed before validation.
Who to write to. An E.164 phone number (+ and country code) for whatsapp; an
email address for email. An invalid value returns 400 with
Phone number must be E.164 format (e.g. +14155551234) or Invalid email address.
Channel to start the conversation on.
whatsapp: sent from the workspace’s WhatsApp sender.email: sent from the workspace’s connected Gmail, Outlook or Zoho Mail mailbox.
Text of the message, sent exactly as written. Required unless a WhatsApp
templateId is sent. On WhatsApp freeform text is only accepted inside the
24-hour window.
Email subject. Required when channel is email; ignored on WhatsApp.
WhatsApp only. Id of one of the workspace’s WhatsApp templates. It must be
approved, or the call fails with 422 TEMPLATE_NOT_APPROVED. Templates can be
sent regardless of the 24-hour window.
Values for the template placeholders. Either an ordered list, where the first item
fills {{1}}, or an object keyed by placeholder number. Ignored without templateId.

