Get started

The Conversational agent API (also referred to as CoCoAaS — Conversational Commerce as a Service) powers the chat experience that shoppers see embedded in a storefront. This page covers what you need to start a frontend integration: your identifiers, the two core endpoints, and how the event-based chat model works.

🚧

Warning

The current API is in Beta. The stable API is expected to differ slightly in paths, and have a token-based authentication mechanism. These changes will require small implementation changes when migrating.

For the full frontend integration guide, OpenAPI spec (request/response payloads, and status codes), and a ready-to-run reference implementation, see the conversational-agent-api GitHub repo:

This page is a quickstart, and the repo is the source of truth for implementation details.

Prerequisites

You'll get three values from Bloomreach during onboarding:

ValueDescription
apiUrlYour backend base URL.
projectIdYour project identifier.
personaIdThe persona (assistant) identifier to chat with.

The following two values are owned by your storefront:

ValueDescription
currencyISO 4217 currency code (USD, GBP, etc.) from your storefront config. Used to format prices and passed in FE.SET_CONTEXT.
endCustomerIdA stable shopper identifier, typically from a cookie. Identifies the shopper across chats — it doesn't change when a new chat starts.

Finally, your client generates and owns one more identifier:

ValueDescription
chatIdIdentifies a single conversation. Generate it with a UUID v7 library the first time the chat surface opens, persist it in localStorage, and rotate it on "New chat." One shopper can have many chats over time.
import { v7 } from 'uuid'

const chatId =
  localStorage.getItem('CHAT_ID') ??
  (() => { const id = v7(); localStorage.setItem('CHAT_ID', id); return id })()

Core endpoints

The whole integration is essentially two calls, plus optional conversation-starter endpoints:

MethodEndpointPurpose
GET.../persona/{personaId}/general-settingsBranding and the full i18n translation dictionary. Call once on mount, cache for the session.
POST.../chat/{chatId}/send-eventThe entire chat channel — one outbound event per request, a streamed array of inbound events in response.
GET/POST.../clarity-search/...Optional conversation-starter questions for PDP, PLP, and search/autosuggest surfaces. See the repo's integration guide for details.

Initialization flow

  1. Fetch branding & translations. Call general-settings (Get assistant settings) once on mount and cache the response in localStorage for up to 6 hours, keyed by projectId + personaId. Branding fields (assistantLogo, assistantName, agentPersonaName, description) can be null, so always provide neutral fallbacks. On a failed fetch, fall back to a stale cache entry rather than losing branding.

  2. Set the current context. Send an FE.SET_CONTEXT event via send-event so replies stay relevant to what the shopper is looking at — pass currency plus whatever the page knows (product IDs, cart contents, category, filters).

    sendEvent({
      type: 'FE.SET_CONTEXT',
      currentItemIds: ['prod-123'],
      currentItemIdsType: 'product_id',
      currency: 'USD',
    })
  3. Restore existing history (if reopening a chat). If the shopper is reopening a conversation with an existing chatId, send SYNC_EVENT_LOG to replay past events. Skip this for a freshly rotated chatId — there's nothing to replay. De-duplicate replayed events by _id.

    if (!isBrandNewChat) {
      sendEvent({ type: 'SYNC_EVENT_LOG' })
    }

From here, every interaction is an event. Each send-event request carries one outbound event, the current page url, and endCustomerId; the server streams back a growing JSON array of inbound events that you render into the conversation thread.

The event model

Events flow in one of three directions:

  • Outbound: client → server (e.g. ADD_MESSAGE.USER.TEXT, FE.SET_CONTEXT)
  • Inbound: server → client (e.g. ADD_MESSAGE.ASSISTANT.TEXT, ADD_MESSAGE.ASSISTANT.CAROUSEL)
  • Both: replayed from history or echoed back by the server

The send-event response is a single JSON array written incrementally. Parse it as it streams rather than waiting for the connection to close:

const reader = response.body.getReader()
let buffer = '', lastIndex = -1, parsed = []
for (;;) {
  const { done, value } = await reader.read()
  if (done) break
  buffer += decoder.decode(value)
  try { parsed = JSON.parse(buffer) }
  catch { try { parsed = JSON.parse(buffer + ']') } catch { continue } }
  while (lastIndex + 1 < parsed.length) await onItem(parsed[++lastIndex])
}

Only one send-event request should be in flight per chat at a time — disable the input and visible quick replies while streaming, and match the backend's 60-second connection hold with a client-side AbortController so a stalled stream can't lock the UI.

For the complete event catalog (basic messages, rich messages like carousels and quick replies, and advanced events like USER.DIRECT_CALL and USER.FEEDBACK), see the Frontend Integration Guide in the repo.


© Bloomreach, Inc. All rights reserved.