Start a widget session

View as Markdown
Opens (or resumes) a visitor session and returns what the chat UI needs to render: greeting, branding and whether voice is on. Call it once when the chat loads, then use the returned `session_id` with `POST /widget/message`, `GET /widget/poll` and `GET /widget/history`. The standard embed script does all of this for you. **Credentials.** This is a public endpoint. It does **not** accept a dashboard session or a `jl_` API key. Name the widget with exactly one of these, checked in this order: 1. `x-widget-api-key` header: the secret `plaintext_api_key` (server-side callers only); 2. `x-widget-id` header: the public widget id (what the embed uses); 3. `wid` query parameter: the public widget id. The API key is never accepted in the query string. A value that looks like a UUID is treated as a widget id, whichever header carries it. Every call must also send `x-widget-parent-origin` with the origin of the page that embeds the widget; it must match one of the widget's `allowed_origins` (scheme and port exactly, hostname modulo one leading `www.`). The check does not use the browser's `Origin` header, and CORS is open (`Access-Control-Allow-Origin` reflects any origin, no credentials). **Sessions.** Sessions are keyed by `visitor_id`. If the same visitor had a session on this widget that was active in the last 30 days, it is returned with `resumed: true` and any new `metadata` is merged into it (keys are added or overwritten, never removed); otherwise a new session is created. Send `metadata.email` (and `metadata.name`) to link the visitor's conversation to a contact. **Side effects.** Creates or updates a `widget_sessions` row (with the caller's IP and user agent on creation). No conversation is created until the first message; no AI usage is recorded. **Idempotency.** Safe to retry with the same `visitor_id`: a retry resumes the session the first call created. Two truly concurrent first calls for a new visitor can create two sessions. **Access** - **Required scope:** None; `jl_` API keys are not used. The widget credential plus a matching `x-widget-parent-origin` authorise the call. - **Rate limit:** Widget public — 60 requests/min per IP, plus the widget's `rate_limit_rpm` (default 30) per credential and 300 requests/min per workspace across all its widgets; the general limiter also counts 120 requests/min per IP. See [Rate limits](/rate-limits). - **Plan:** Available on every plan.

Headers

x-widget-api-keystringOptionalformat: "^[0-9a-f]{64}$"

Secret widget API key (plaintext_api_key from POST /api/widgets). Takes precedence over x-widget-id and wid. Send one of the three. Never expose it in a browser.

x-widget-idstringOptionalformat: "uuid"

Public widget id, the credential the embed script uses. Used when x-widget-api-key is absent.

x-widget-parent-originstringRequiredformat: "uri"
Origin (or any URL; only its origin is used) of the page embedding the widget. Must match one of the widget's `allowed_origins`. Missing returns 403; an unparseable value returns 403 `Invalid parent origin`. Only the dashboard preview, which sends a short-lived `x-widget-preview-token`, may omit it.

Query parameters

widstringOptionalformat: "uuid"
Public widget id as a query parameter. Used when neither header is present.

Request

This endpoint expects an object.
visitor_idstringRequired>=1 character

Stable id the embedding page keeps for this visitor (the embed stores a UUID in localStorage). Reusing it within 30 days resumes the same session and conversation.

metadatamap from strings to anyOptional

Free-form visitor data stored on the session (merged on resume). email links the conversation to a contact (created in the workspace’s manual-conversations campaign if none matches, case-insensitively); name fills the contact’s name when it is empty.

Response

Session created or resumed (both return 201).

dataobject

Errors

400
Bad Request Error
401
Unauthorized Error
403
Forbidden Error
429
Too Many Requests Error
503
Service Unavailable Error