Skip to content

Public chat and embed

These routes support anonymous public chat and website embed sessions. They do not use the workspace API token — a browser or your own backend calls them directly against your Radioso host (https://api.radioso.ai, https://api-us.radioso.ai, or your self-hosted origin) using the public launch token embedded in your site, not a workspace credential.

How it works

  1. Enable public chat or website embed in general settings.
  2. Create a public chat session from the launch token.
  3. Send the returned publicSessionToken as x-radioso-public-session on chat and history requests.
  4. Store the returned resumeToken if the visitor should resume the same history after the public session expires.
  5. Preserve the server-set anonymous session cookie across requests when available.

The launch token identifies the public surface, and the public session identifies the visitor. The resume token is the only supported way to request the same visitor session in a later session exchange.

Create a public chat session

Use this route before sending chat messages:

text
POST /api/v1/public/chat/{token}/sessions

For a public link:

json
{
  "channel": "anonymous_link"
}

For an Enterprise website embed, call the route from an origin admitted by the website embed origin setting:

json
{
  "channel": "website_embed"
}

Browser clients must keep using that same origin on later chat, history, and feedback requests. Radioso checks the signed public session against the current website embed origin setting each time. Removing an origin from the list stops existing browser sessions from sending new messages. If the list contains *, any origin is admitted. If the list is empty, no origin is admitted.

To resume an existing visitor session, include the previous response’s resumeToken:

json
{
  "channel": "website_embed",
  "resumeToken": "previous-resume-token"
}

The response includes publicChatToken, publicSessionId, publicSessionToken, resumeToken, workspaceName, assistantBootstrapActive, expiresAt, and resumeExpiresAt. Internally, publicSessionId is the chat session boundary for conversation continuity.

chatSessionId and anonymousSessionId are accepted as client hints but are not trusted as resume credentials. Use resumeToken to resume and the returned publicSessionToken for chat requests.

Send a public chat message

json
{
  "message": "What can this workspace answer?",
  "stream": false,
  "conversationId": "optional-conversation-id",
  "bootstrapGreetingId": "optional-bootstrap-greeting-id"
}

The public chat request also supports startConversation: true for bootstrap greeting requests, userExpectedLocale, and inputMetadata.

Bootstrap greetings are not saved as conversations by themselves. A startConversation: true response can omit conversationId and include bootstrapGreetingId. If the visitor replies, send that bootstrapGreetingId with the first normal message and no conversationId; Radioso will save the displayed greeting before the user’s message. Use the conversationId returned by that first normal message for history and follow-up continuity.

Sending a newer message while a turn is running

If a newer message arrives for the same conversation before the current assistant reply starts, Radioso cancels the current reply and answers from the latest history. A non-streaming superseded request returns HTTP 409 with error code chat_turn_superseded.

For a streaming request, the superseded stream ends with this terminal event:

text
event: cancelled
data: {"conversationId":"...","reason":"superseded","stage":"rendering"}

Treat cancelled as terminal and clear the pending reply state. It contains no assistant copy. Before an answer or cancellation, the stream can emit status with stage interpreting, searching, or composing. Status is transient UI state and does not prevent cancellation. If the first assistant chunk has already streamed, the first turn finishes and the newer message runs next.

Answer chunks are incremental delivery. Guarded or durability-sensitive replies may be validated or committed before their first chunk, then replayed in bounded chunks. Clients should not assume every chunk is a live provider token.

If the assistant cannot produce a public response body, the route returns a JSON error instead of an empty 204 response:

json
{
  "error": {
    "code": "service_unavailable",
    "message": "Public chat response is unavailable.",
    "details": {
      "code": "public_chat_empty_response"
    }
  }
}

Chat suggestions

A chat response can include suggestions. Each suggestion has text, a provider-defined kind string, and an optional action.

Two kinds of chips can appear together:

  • Follow-up question chips have no action (or action.kind is "ask_followup"). They are pure text prompts: render suggestion.text and, when the visitor selects one, send it as a new user message with inputMetadata.method = "suggestion_click" and suggestionSourceMessageId = <assistant message id>.
  • Action chips carry action.kind === "start_intent" with an intent.skillName (and an optional intent.intentName). They route to a specific assistant skill instead of asking a follow-up question. When the visitor activates one, send inputMetadata.method = "intent_click" together with the intent object verbatim, plus suggestionSourceMessageId. Sending suggestion_click for an action chip will work as a plain text turn but will bypass the structured skill handoff the chip was meant to trigger.

At most one action chip is returned per assistant turn. Action chips are in addition to the up-to-two follow-up question chips.

Example response fragment:

json
{
  "suggestions": [
    {
      "text": "Contact us",
      "kind": "contact_human",
      "action": {
        "kind": "start_intent",
        "intent": {
          "skillName": "human_contact.request",
          "intentName": "no_context_refusal"
        }
      }
    },
    { "text": "What's covered here?", "kind": "deeper" }
  ]
}

The set of action kind values is open: new modules can register additional providers, so clients should treat kind as an opaque string and decide behavior from action rather than from kind.

History in the anonymous flow

Use GET /api/v1/public/chat/{token} to list conversations for the current anonymous session.

Use GET /api/v1/public/chat/{token}/history/{conversationId} to fetch one conversation from that same anonymous session.

Use GET /api/v1/public/chat/{token}/tail/{conversationId} to read messages created after a cursor. A conversation detail response includes tailCursor; send that cursor to receive only later visitor, AI, or human-agent messages. If cursor is omitted, tail returns the newest bounded page and a cursor for the newest returned message. Public tail responses do not include operator ownership metadata.

Use GET /api/v1/public/chat/{token}/events/{conversationId} to open a server-sent event stream for the same anonymous session. The stream emits ready after subscription and message.created when a new message is available. Browser clients that store the public session in local storage should consume this stream with fetch, not native EventSource, so they can send x-radioso-public-session.

Endpoint reference

Session, message, and embed-config payloads are typed in full in the API Reference.

text
POST /api/v1/public/chat/{token}/sessions
GET  /api/v1/public/chat/{token}
POST /api/v1/public/chat/{token}
GET  /api/v1/public/chat/{token}/history/{conversationId}
GET  /api/v1/public/chat/{token}/tail/{conversationId}
GET  /api/v1/public/chat/{token}/events/{conversationId}
GET  /api/v1/public/chat/{token}/embed-config
GET  /api/v1/public/chat/{token}/assistant-logo

Common failure modes

  • 403 on a website embed session usually means the origin is not admitted by the website embed origin setting.
  • 404 usually means the public token is unknown or not active.
  • 409 with chat_turn_superseded means a newer message replaced this turn before its reply started.
  • 503 on a public chat message means Radioso accepted the public session but could not produce a response body.
  • 429 on public chat means the optional server-side public chat rate limit has been reached.
  • If history appears empty, the browser or cookie jar is probably not preserving the HttpOnly anonymous session cookie.