Start a conversation
Open a conversation on behalf of one of your users, record their first message, and mint the visitor token for it.
POST /api/v1/public/conversationsOpens a conversation on behalf of an end user, records their first message, and mints the visitor token that authorizes every later read and write on that conversation.
This is the only Public API endpoint that creates a conversation, and the only one that ever returns a visitor token.
Authentication
Publishable key: Authorization: Bearer pk_….
A secret key (sk_) is rejected here, exactly as an unknown or revoked key is — see
Authentication.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
message | string | yes | The user's first message. |
external_id | string | no | Your own stable ID for this user. Strongly recommended — see Identity resolution below. |
name | string | no | Display name. Defaults to Visitor when blank. |
email | string | no | Must be a valid email address if provided. |
{
"external_id": "app-user-8471",
"name": "Ama",
"email": "[email protected]",
"message": "My order never arrived."
}Response
201 Created.
data carries the new conversation_id, the visitor_token, and the message you
just recorded.
{
"success": true,
"request_id": "6d7979ce52075b24979cd5b415319339",
"data": {
"conversation_id": "019f1c4d-6e7f-7a80-91b2-c3d4e5f60718",
"visitor_token": "vt_example_7Jq4Xm2Pz9Ln6Rd3Wb5T",
"message": {
"id": "019f1c50-1111-7a80-91b2-c3d4e5f60718",
"author": "customer",
"body": "My order never arrived.",
"created_at": "2026-07-11T10:00:00.000000Z"
}
}
}The visitor token is returned once
visitor_token appears in this response and nowhere else. It is stored only as a
hash, so it cannot be looked up, listed, or re-sent. If you lose it, the only recovery
is to start a new conversation — which gives the user a new, empty thread. Persist it
server-side, against the user it belongs to, before you do anything else with the
response.
The token expires after 30 days of disuse, on a sliding window that every read and post pushes forward — and an agent can revoke it from the dashboard. There's no refresh flow: if it stops working, you call this endpoint again to get a new one. See Lifetime.
Identity resolution
Every call resolves which customer record the conversation belongs to, in this exact order:
external_idis given and matches an existing customer in your organization → reuse that customer.- Otherwise
emailis given and matches an existing customer → reuse that customer. - Otherwise → create a new customer.
The practical consequence: pass external_id if you want a returning user to stay
linked to one customer record. Because your stable ID is checked first, a user who
later changes their name or email in your app still maps to the same Assistive
customer, and the agent sees one continuous history. Send only name/email — or
nothing — and you risk fragmenting one human across many customer records, each with a
slice of their history.
Routing happens on creation
Conversations started here are recorded on the api channel, and your organization's
auto-routing rules run as the conversation is created. A rule may assign the
conversation to a team, and that decision is written to the conversation timeline and
the audit trail.
You neither control nor observe this from the API — it's simply why a conversation may already belong to a team by the time an agent opens it. (Only team routing applies to a conversation; priority and tag rules are ticket concerns and do nothing here.)
Errors
| Status | error.code | When |
|---|---|---|
401 | UNAUTHORIZED | The key is missing, unknown, revoked, or is an sk_ key. |
422 | VALIDATION_ERROR | message is absent, or email isn't a valid address. See error.details. |
429 | RATE_LIMITED | Your organization's publishable-key budget is exhausted. |
{
"success": false,
"request_id": "6d7979ce52075b24979cd5b415319339",
"error": {
"code": "VALIDATION_ERROR",
"message": "the request contains invalid fields",
"details": [{ "field": "message", "message": "message is required" }]
}
}Examples
curl -X POST "{BASE_URL}/api/v1/public/conversations" \
-H "Authorization: Bearer pk_example_k3Yb9wQ2rTfA8sLdN1vH" \
-H "Content-Type: application/json" \
-d '{
"external_id": "app-user-8471",
"name": "Ama",
"email": "[email protected]",
"message": "My order never arrived."
}'