Skip to main content
This guide is based on the Akapulu Labs Web SDK.

Packages

  • @akapulu/react
  • @akapulu/react-ui
  • @akapulu/server

Quickstart (React)

AkapuluProvider takes paths to your local API routes:
  • connectPath: your local connect endpoint
  • updatesPath: your local updates endpoint
The SDK calls these local routes from the browser. Your server routes then call Akapulu Labs APIs using @akapulu/server.

Backend connect and updates routes

Your local connect route should:
  • call akapulu.connectConversation(...) with the Akapulu Labs connect schema
    • scenario_id: string
    • avatar_id: string
    • runtime_vars?: Record<string, string>
    • stt_keywords?: string[]
    • record_conversation?: boolean
  • return the connect response fields required by the SDK:
    • room_url
    • token
    • conversation_session_id
Your local updates route should:
  • read conversation_session_id from the query param
  • call akapulu.pollConversationUpdates(conversationSessionId)
  • return the updates payload fields required by the SDK:
    • call_is_ready
    • completion_percent
    • latest_update_text
    • conversation_session_id
The SDK always calls your updatesPath with conversation_session_id in the URL query string. Install @akapulu/server in your backend and create two routes:
  • POST /api/akapulu/connect
  • GET /api/akapulu/updates?conversation_session_id=...
Then point AkapuluProvider to those local paths:
  • connectPath: "/api/akapulu/connect"
  • updatesPath: "/api/akapulu/updates"

Browser vs server

  • In the browser: AkapuluProvider (via config.endpoints.connectPath and config.endpoints.updatesPath) requests your connect and updates backend routes.
  • On your server: those routes use @akapulu/server, read the Akapulu Labs API key from server-side environment variable, and call the Akapulu Labs HTTP API. That way your api key stays on the server and does not get exposed in the browser
AkapuluProvider expects your local connect and updates endpoints to return the JSON schema described under Backend connect and updates routes above.

Passing custom payload to your local connect route

If you want, AkapuluProvider can send a custom JSON payload to your local connectPath route via config.connectBody.
  • If connectBody is provided, the SDK sends it as the POST JSON body to your local connect route.
  • If connectBody is omitted, the SDK sends POST /connectPath with no body.
Frontend example:
Backend example (your local connect route):

Styling AkapuluConversation

@akapulu/react-ui supports slot-level customization via:
  • className for the root
  • classes for slot class names
  • styles for slot inline style overrides

Example 1: Slot key overrides (classes + styles)

Use slot keys when you want to target specific UI parts directly from React props.

Example 2: Default class overrides (global CSS)

Use built-in default classes when you want to apply theme-like global styles from CSS.

Example 3: data-slot overrides (stable CSS selectors)

Use data-slot selectors when you want explicit, inspectable selectors in DevTools.

Slot map

Each slot has both a default class and a data-slot marker so users can inspect and target it in DevTools without guessing.

Layout

Loading

Error modal

Tool events

Video + controls

Transcript

Behavior customization (handlers + custom elements)

Beyond styles, @akapulu/react-ui lets you customize behavior and rendering for transcript/tool events directly on AkapuluConversation.

Built-in handler props on AkapuluConversation

  • transcriptFilter(entry) to hide transcript rows
  • renderTranscriptEntry(entry) to render transcript rows with your own JSX
  • onToolEvent(tool) to run side effects when a tool event arrives
  • renderToolEvent(tool) to replace the default tool toast element
  • toolEventTimeoutMs to control how long the tool toast stays visible
Callback signatures:
toolEventTimeoutMs behavior:
  • default: 4000
  • pass a number to customize the auto-hide timeout
  • pass null to disable auto-hide
TranscriptEntry shape (entry): NormalizedToolEvent shape (tool) by tool type:

RAG tool event

vision tool event

http tool event

Handling all conversation events while keeping prebuilt UI

For full event handling (node changes, bot speaking state, transcript updates, tool calls, and timeout), add a small sibling listener component that uses useAkapuluEvents.
Event schema by event.type:

status_changed

bot_speaking_state_changed

node_changed

tool_event

transcript_updated

call_ready

timeout

Using @akapulu/react without @akapulu/react-ui

The same connect/updates route pattern applies: AkapuluProvider points at your local connectPath and updatesPath, your server uses @akapulu/server. What changes is UI: you build layout yourself and pull state from hooks instead of mounting AkapuluConversation. How it fits together
  1. Wrap your tree in AkapuluProvider (as in the Quickstart).
  1. Call useAkapuluSession() anywhere under that provider. It exposes everything in the session store plus start / end: Lifecycle & connection
    • status — where the client is in the join/leave flow: "idle""connecting""connected""disconnecting" / "ended", or "error" if something failed.
    • start / end — async actions that begin the conversation (connect + Daily join) or hang up and reset session state.
    • error — when status is "error", structured details (code optional, message required) for your error UI or logging.
    Call readiness & updates (from your updates route)
    • callIsReady — whether the backend considers the call ready (often used while status === "connecting" so you are not stuck on a spinner forever).
    • completionPercent — numeric progress through the scenario (0–100).
    • latestUpdateText — short human-readable status line for loading/progress copy.
    Scenario / flow
    • currentNode — the active scenario node ({ key, label }) or null if none.
    Transcript & bot
    • transcripts — ordered list of rows (id, text, speaker, timestamp, isFinal) for your own transcript UI.
    • botSpeakingState"idle", "speaking", or "listening" for indicators or turn-taking UI.
    Correlation
    • conversationSessionId — Akapulu Labs conversation session id from connect (used when polling updates; also useful if your app logs or links out to dashboard/API records).
  1. The provider joins the Daily room for realtime media. Install @daily-co/daily-react (and peer @daily-co/daily-js) and use its primitives to draw video—for example DailyVideo, useDaily, and useVideoTrack. For assistant video selection, use useAkapuluParticipantRoles from @akapulu/react.
  1. Use useAkapuluMediaControls() for in-call mic and camera toggles (wired to that Daily session).
  1. Render <AkapuluBotAudio /> once in the tree so assistant audio plays (small hidden element; required for typical voice/video bots).
  1. Optionally useAkapuluEvents(callback) to react to the full event stream (transcript_updated, tool_event, node_changed, timeout, etc.) while still rendering your own UI.
Rough outline
Main exports from @akapulu/react Ending a call: When your user ends the call in your app, call end() from useAkapuluSession(). That leaves the Daily room from the browser and runs the SDK teardown so the session is no longer live. If the assistant disconnects from Daily first, AkapuluProvider calls end() for you so status does not stay connected with no assistant in the room. Types and shared logic Use AkapuluEvent from @akapulu/react. For TranscriptEntry, NormalizedToolEvent, and related handler shapes on AkapuluConversation, align with the prop types exported from @akapulu/react-ui.

Conversation detail and recording

Separate from the live call connect and updates loop, @akapulu/server can fetch post-call (or in-progress) conversation detail and recording responses from your backend—for example dedicated HTTP handlers your UI calls after conversation_session_id is known.

Conversation detail retrieval

@akapulu/server also exposes getConversationDetail(conversationSessionId) for fetching a completed or in-progress conversation detail payload from your backend.
JSON body shape (matches ConversationDetailResponse from @akapulu/server):

avatar

recording (detail payload)

transcript_rows[]

Conversation recording retrieval

@akapulu/server also exposes getConversationRecording(conversationSessionId) for fetching the recording response for a conversation from your backend.
Return value shape (discriminated union, ConversationRecordingResponse from @akapulu/server):

kind: "redirect"

kind: "json"

kind: "binary"

Examples

Runnable apps that mirror this guide are available as standalone Vite + Express repos. Each has an Express backend/ with @akapulu/server route handlers for POST /api/connect and GET /api/updates?conversation_session_id=…, plus a React frontend/.
  • Prebuilt UIAkapulu/prebuilt-ui: @akapulu/react (AkapuluProvider with local connectPath / updatesPath) plus @akapulu/react-ui (AkapuluConversation for the full default layout: video, transcript, controls). Useful when you want to ship quickly and customize via props or CSS rather than rebuilding layout.
  • Prebuilt UI (styled)Akapulu/prebuilt-ui-styled: the same prebuilt path with a dark theme, a custom tool toast, and a post-call review screen (recording + transcript).
  • Customized UIAkapulu/customized-ui: @akapulu/react only—same provider pattern, then useAkapuluSession, useAkapuluMediaControls, useAkapuluParticipantRoles, useAkapuluEvents, AkapuluBotAudio, and @daily-co/daily-react (DailyVideo, track hooks) for your own chrome. Useful when you need full control over markup and state wiring.