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_turnsand it is exhausted, the session ends as abandoned and further turns are refused. - Presentation:
startFocusedSessionresolves 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,
startFocusedSessionREJECTS 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:
variablesare 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.transcriptis the session's conversation as{ role, text, at }turns (roleis"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:
| Code | Meaning |
|---|---|
SESSION_START_INVALID | Neither or both of template_id / mission supplied |
SESSION_TEMPLATE_NOT_FOUND | Unknown or inactive template for this app |
SESSION_DOMAIN_UNKNOWN | The page's domain is not registered, so no app scope could be resolved |
SESSION_VARIABLE_MISSING | A required template variable was not supplied |
SESSION_MISSION_INVALID | Inline mission missing prompt or object outcome schema |
SESSION_RUNTIME_MISSIONS_DISABLED | Inline missions are off on this deployment |
SESSION_NOT_FOUND | Unknown session id (or wrong organization) |
SESSION_NOT_ACTIVE | Turn sent to a completed/abandoned session |
SESSION_TURNS_EXHAUSTED | The turn budget ran out; the session is now abandoned |
SESSION_PREFLIGHT_FAILED | The 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_TIMEOUT | The same, but your backend did not answer in time. Deliberately distinct: a slow backend is an outage, a missing entity is content |
SESSION_FAILED | Turn sent to a session whose mission could not be fulfilled. Distinct from SESSION_NOT_ACTIVE; reaches onEnded as status failed |