Skip to content

Public endpoints

The keyless /api/public surface for building a custom chat frontend — every endpoint, the binding token, the domain allowlist, and the per-endpoint rate limits.

If the embedded widget is not the interface you want, build your own against the public chatbot endpoints. This is the supported path for a custom chat frontend.

Three things define this surface:

  • No API key. These endpoints are called from a visitor's browser, so they

    carry no secret. They are scoped by the chatbot's publicId.

  • The domain allowlist applies. Every request is checked against the

    chatbot's allowed domains via its Origin. A disallowed origin gets DOMAIN_NOT_ALLOWED. See Domain allowlist.

  • CORS is handled. Each endpoint answers preflight requests, so a browser

    call from an allowed origin works directly.

They share the { "success": …, "data" | "error" } envelope.

The binding token

Creating a conversation returns a binding token alongside the conversation id. Every endpoint that reads or changes an existing conversation requires it, passed as a ?bt= query parameter.

The token proves this client owns that conversation. Without it — or with a guessed conversation id — the request is a 404, which is what stops a visitor from reading someone else's thread. Store it with the conversation id for the session and replay it on every subsequent call.

The endpoints

GET /config

The chatbot's public configuration: branding, greeting, suggested questions, lead-capture mode, AI disclosure, and the online flag with any away message. Cached for up to a minute.

POST /conversations

Starts a conversation. Body fields are all optional: visitorId (≤ 100 chars) and pageUrl (≤ 2,000 chars). An empty body is fine.

Keep both values. Limit: 20 per chatbot per IP per minute.

POST /chat

Sends a visitor message and streams the answer back.

Limit: 20 per chatbot per IP per minute.

Note:

When the workspace is out of budget the turn still proceeds — the visitor's message is recorded, a handoff request still escalates, and you still get a real conversation id and binding token. Only the paid AI work is withheld, and the static "assistant is unavailable" reply is returned instead. Handle it as an ordinary reply, not as an error.

GET /conversations/{id}/stream?bt=…

A server-sent event stream of agent replies and handoff state during a live takeover. Limit: 60 stream opens per chatbot per IP per minute, with a cap on concurrently held-open streams.

GET /conversations/{id}/poll?bt=…

The polling alternative to the stream: current status, whether a human is live, the agent's display name, and recent messages. This is what the shipped widget uses. Limit: 90 per minute.

Prefer polling unless you specifically need the stream — it is simpler, it survives flaky networks, and it is what the reference implementation exercises.

POST /conversations/{id}/resume?bt=…

Opts the visitor back into AI answers after they asked for a human — useful when nobody is available. Flips the conversation back to open and unassigns it. Limit: 10 per minute.

POST /conversations/{id}/close?bt=…

Ends the conversation from the visitor's side. Limit: 10 per minute.

POST /leads

Submits a lead. name (≤ 200), email, phone, company (≤ 200), message (≤ 2,000), pageUrl, conversationId, visitorId — all optional, though a lead with no contact detail is not much use. Limit: 5 per chatbot per minute.

Emits the `lead.created` webhook.

POST /csat

Submits a satisfaction rating: conversationId, rating (an integer 1–5), an optional comment (≤ 2,000), and the binding token. Limit: 10 per minute. The first rating for a conversation emits the csat.received webhook.

POST /feedback

Per-message thumbs: messageId, rating of -1, 0, or 1, an optional comment, and the binding token. Limit: 30 per minute.

GET /conversations/{id}/attachments/{attachmentId}?bt=…

Fetches a file an agent attached to a reply.

Building against it

A minimal client is: create a conversation, keep the id and binding token, send messages to /chat, and poll /poll so agent replies appear during a handoff. Everything else — CSAT, feedback, leads, resume, close — is optional polish.

What you take on by building your own frontend is everything the embedded widget does for free: persistence across reloads, streaming, accessibility, the away state, retry on failure, and transcript download. See Widget embed before deciding.

Where to go next

Last updated