Skip to main content

API reference

All Focused Sessions APIs are imported from @betterknow/appilot.

startFocusedSession(request)

Starts a session through the assistant surface embedded on the page (widget, or the Appilot browser extension). Returns a promise of a session handle.

interface FocusedSessionStartRequest {
/** Session Template to instantiate (recommended). */
template_id?: string;
/** Inline mission (development deployments only). */
mission?: FocusedSessionMission;
/** Values for the template's declared variable slots. */
variables?: Record<string, string>;
}

Exactly one of template_id / mission is required. Variable values are plain strings; they are sanitized server-side before entering the mission.

The handle

interface FocusedSessionPageHandle {
focusedSessionId: string;
conversationId: number;
name: string; // resolved display name
onOutcome(cb: (outcome: Record<string, unknown>) => void): () => void;
end(): Promise<void>;
}
  • onOutcome(cb) fires at most once per session, with the schema-validated outcome object. Returns an unsubscribe function.
  • end() abandons the session. Abandoned sessions produce no outcome.

Inline missions (development)

interface FocusedSessionMission {
name: string;
prompt: string; // the mission text
allowed_tools?: string[]; // tool names; empty = none
kb_doc_ids?: string[]; // knowledge allowlist
outcome_schema: object; // JSON Schema for the outcome
max_turns?: number; // turn budget
}

Rejected unless the deployment enables runtime missions. Production integrations always use Session Templates.

Session semantics

  • Identity: sessions require an identified user; anonymous visitors cannot start one.
  • Conversation: each session owns its conversation. Turns sent while the session is active belong to it regardless of any other conversation state on the page.
  • Scope: the assistant can only use the mission's allowed tools and knowledge scope. It redirects requests outside the mission back to it.
  • Completion: the assistant produces the outcome only when the mission's completion criteria are met, and the outcome always satisfies the declared schema.
  • Turn budget: when the template sets max_turns and it is exhausted, the session ends as abandoned and further turns are refused.
  • Presentation: startFocusedSession resolves the template's presentation and returns it on the result. The assistant surface applies it (immersive chrome, an agent-spoken opening, activity-specific input and exit copy); your page does not have to do anything with it. It is a closed set of copy and behavior slots by design, so it never restyles the panel.
  • Preflight: when the template declares mandatory grounding, it runs before the session exists. If it fails, startFocusedSession REJECTS and no session is created, so your page never enters its running state and can render its own error. Handle the rejection; do not assume a resolved promise means the assistant is ready.

Verifying outcomes server-to-server

The outcome your page receives via onOutcome traveled through the user's browser, which makes it fine to RENDER but not to TRUST: anything your frontend later posts to your backend could have been altered by the user. When an outcome feeds a decision (an assessment result, a record, anything evidence-grade), your backend must re-read the authoritative copy from Appilot before storing it:

GET {APPILOT_API_URL}/widget/focused-sessions/{focused_session_id}
X-Widget-Key: wk_live_... (your publishable key)
X-Widget-Secret: wsk_secret_... (your SERVER secret; never in page code)

Response:

{
"focused_session_id": "fs_...",
"status": "completed",
"mission_source": "template",
"mission_name": "Conversational assessment",
"template_semantic_id": "conversational-assessment",
"variables": { "learner_ref": "your-app:learner-123" },
"outcome": { "...": "the schema-validated result" },
"transcript": [
{ "role": "assistant", "text": "Let's begin. Tell me about...", "at": "..." },
{ "role": "user", "text": "Well, first I would...", "at": "..." }
],
"turns_used": 8,
"started_at": "...", "ended_at": "...",
"user_external_id": "your-app:user-123",
"user_display_name": "Alex",
"user_id": null
}

The recommended pattern: the page posts only IDs to your backend (focused_session_id plus your own references), your backend calls this endpoint, checks status === 'completed' and user_external_id against the expected user, and stores the outcome from THIS response, never the page's copy. This authenticates the outcome; whether its content is CORRECT is still your review model's call (the result is AI output).

Two fields exist for that review:

  • variables are the sanitized start variables the session ran with, so your backend can bind the outcome to the right record (the learner, the case, the order) without trusting anything the page posted.
  • transcript is the session's conversation as { role, text, at } turns (role is "user" or "assistant"; the assistant's hidden mission kickoff is omitted). The outcome is the assistant's account of the session; the transcript is what actually happened. When the outcome feeds an evidence-grade decision, store both: when a reviewer questions the result, the transcript is the record they read.

Error codes

Surfaced on the start/end calls (HTTP) and mapped to the SDK's rejections:

CodeMeaning
SESSION_START_INVALIDNeither or both of template_id / mission supplied
SESSION_TEMPLATE_NOT_FOUNDUnknown or inactive template for this app
SESSION_DOMAIN_UNKNOWNThe page's domain is not registered, so no app scope could be resolved
SESSION_VARIABLE_MISSINGA required template variable was not supplied
SESSION_MISSION_INVALIDInline mission missing prompt or object outcome schema
SESSION_RUNTIME_MISSIONS_DISABLEDInline missions are off on this deployment
SESSION_NOT_FOUNDUnknown session id (or wrong organization)
SESSION_NOT_ACTIVETurn sent to a completed/abandoned session
SESSION_TURNS_EXHAUSTEDThe turn budget ran out; the session is now abandoned
SESSION_PREFLIGHT_FAILEDThe template's mandatory grounding could not be fetched, so NO session was created. Carries the failing tool, the host status, and your backend's own code / request_id. Branch on this to show your own "not available" state
SESSION_PREFLIGHT_TIMEOUTThe same, but your backend did not answer in time. Deliberately distinct: a slow backend is an outage, a missing entity is content
SESSION_FAILEDTurn sent to a session whose mission could not be fulfilled. Distinct from SESSION_NOT_ACTIVE; reaches onEnded as status failed