Skip to main content

Overview

@akapulu/server is the server-side package in the Akapulu Web SDK. Use it in your backend to call Akapulu APIs from your own server routes. This is the recommended way to keep your API key on the server while letting your frontend call simple local endpoints.

Installation

npm install @akapulu/server

Client constructor

The entry point is createAkapuluServerClient().
import { createAkapuluServerClient } from "@akapulu/server";

const akapulu = createAkapuluServerClient();
The client reads AKAPULU_API_KEY from the environment. If it is missing, client creation throws an error.

Main responsibility

@akapulu/server sits between your application routes and the Akapulu API. It is responsible for:
  • creating the server client
  • calling the connect endpoint
  • polling conversation updates
  • fetching conversation detail data
  • fetching conversation recording responses
  • mapping your app-specific request body into Akapulu request fields like scenario_id, avatar_id, runtime_vars, and record_conversation

Method reference

connectConversation(body)

connectConversation(body: ConnectConversationRequest): Promise<ConnectConversationResponse>
ConnectConversationRequest:
scenario_id
string
required
Scenario id to launch.
avatar_id
string
required
Avatar id to use for the conversation.
runtime_vars
Record<string, string>
Optional runtime variables passed into the scenario.
stt_keywords
string[]
Optional speech-to-text keyword hints.
record_conversation
boolean
Optional recording toggle.
wait_for_client_ready
boolean
Optional override. The client defaults this to true before making the request.
ConnectConversationResponse:
  • room_url
  • token
  • conversation_session_id

pollConversationUpdates(conversationSessionId)

pollConversationUpdates(conversationSessionId: string): Promise<ConversationUpdatesResponse>
ConversationUpdatesResponse:
  • conversation_session_id
  • call_is_ready
  • completion_percent
  • latest_update_text

getConversationDetail(conversationSessionId)

getConversationDetail(conversationSessionId: string): Promise<ConversationDetailResponse>
ConversationDetailResponse includes:
  • id
  • created_at
  • created_at_text
  • duration_text
  • avatar
  • scenario_id
  • recording
  • transcript_rows

getConversationRecording(conversationSessionId)

getConversationRecording(conversationSessionId: string): Promise<ConversationRecordingResponse>
ConversationRecordingResponse is a union of:
  • redirect response: { kind: "redirect", status, location }
  • json response: { kind: "json", status, payload }
  • binary response: { kind: "binary", status, body, contentType, contentDisposition }

Main types

The package also exports the main request and response types used by those methods, including:
  • ConnectConversationRequest
  • ConnectConversationResponse
  • ConversationUpdatesResponse
  • ConversationDetailResponse
  • ConversationRecordingResponse
  • AkapuluApiErrorResponse
The package also exports AkapuluApiError, which is thrown for non-success API responses from connectConversation, pollConversationUpdates, and getConversationDetail.

Typical route pattern

Most apps create local routes like:
  • POST /api/akapulu/connect
  • GET /api/akapulu/updates?conversation_session_id=...
  • GET /api/akapulu/conversation-details?conversation_session_id=...
  • GET /api/akapulu/view-recording?conversation_session_id=...
Those routes then call the Akapulu APIs with @akapulu/server.

How it fits with the rest of the SDK

In a typical app:
  1. The browser calls your local backend routes.
  2. Your backend uses @akapulu/server.
  3. @akapulu/server calls Akapulu APIs.
  4. Your frontend then uses @akapulu/react or @akapulu/react-ui to join and render the conversation.

What your local routes return

AkapuluProvider (from @akapulu/react) reads config.endpoints.connectPath and config.endpoints.updatesPath, sends a POST request to the connect URL, then sends GET requests to the updates URL while the session is joining. Those handlers must respond with snake_case JSON in the same shape Akapulu returns. See Customize Conversation UI. The usual pattern is to return the resolved promise from @akapulu/server so the response types stay aligned.

Connect route → connectConversation

Your POST connect handler should return a body typed as ConnectConversationResponse:
  • room_url — Daily room URL for media
  • token — join token for that room
  • conversation_session_id — id used for updates polling
return await akapulu.connectConversation({ ... }); // satisfies ConnectConversationResponse

Updates route → pollConversationUpdates

Your GET updates handler should return a body typed as ConversationUpdatesResponse:
  • conversation_session_id — same session the client is polling
  • call_is_ready — when true, the client can treat the call as ready
  • completion_percent0100 progress from the scenario
  • latest_update_text — short status string for UI
return await akapulu.pollConversationUpdates(conversationSessionId); // satisfies ConversationUpdatesResponse
Both response shapes are exported from @akapulu/server. The AkapuluProvider contract for those URLs is summarized under Connect and updates response shape in Customize Conversation UI.

Custom connect payloads

One common pattern is to accept app-specific JSON in your local connect route, then map it into Akapulu request fields like runtime_vars and record_conversation.
/* ===================== Client ===================== */

"use client";

import { AkapuluProvider } from "@akapulu/react";
import { AkapuluConversation } from "@akapulu/react-ui";

// `connectBody` is sent as the POST JSON body to `connectPath` when starting a session.
export function App() {
  return (
    <AkapuluProvider
      config={{
        endpoints: {
          connectPath: "/api/akapulu/connect",
          updatesPath: "/api/akapulu/updates",
        },
        connectBody: {
          tenant_id: "acme-health",
          patient_id: "patient_001",
          should_record: true,
        },
      }}
    >
      <AkapuluConversation title="Akapulu demo" />
    </AkapuluProvider>
  );
}

/* ===================== Server ===================== */

import { createAkapuluServerClient } from "@akapulu/server";

const akapulu = createAkapuluServerClient();

// Matches the JSON sent by client (e.g. `AkapuluProvider` `config.connectBody`) so the route can parse it safely.
type LocalConnectBody = {
  tenant_id?: string;
  patient_id?: string;
  should_record?: boolean;
};

// Use the incoming request body to build the Akapulu connect call; return that result as-is so the browser gets `room_url`, `token`, and `conversation_session_id`.
export async function connectRoute(body: LocalConnectBody) {
  return await akapulu.connectConversation({
    scenario_id: "YOUR_SCENARIO_ID",
    avatar_id: "YOUR_AVATAR_ID",
    runtime_vars: {
      patient_id: body.patient_id || "patient_001",
      tenant_id: body.tenant_id || "default_tenant",
    },
    record_conversation: body.should_record === true,
  });
}