Server sessions (API keys)
What this is
A server session is minted by your backend with a developer
API key via POST /v1/sessions. The browser never sees the key. This
is the preferred mint path whenever you can run server-side code.
When to use
- You have a backend (SvelteKit, Next.js, or any server)
- Per-user session context or app-owned authorization
- Tighter control over who can start sessions and with which overrides
- Per-user context via your same-origin
sessionEndpoint
For client-only pages with no server, use
browser embeds with allowed origins instead.
Player chrome (returnUrl, closeButton) is configured via SDK
attach, not mint.
Flow
Your backend → POST /v1/sessions → SessionLaunchResponse ↓ Your frontend ← sessionEndpoint (preferred) ← SDK
1. Mint on your server
Call the Liforma API with your developer API key. Never expose the key to the browser.
curl -X POST https://api.liforma.ai/v1/sessions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"experienceId": "exp_t0i7acmq"}'2. Pass to the client
Recommended: use a same-origin sessionEndpoint so credentials never touch SSR HTML.
Svelte
<Experience
experienceId="exp_t0i7acmq"
sessionEndpoint="/api/liforma-session"
/>React
import { Experience } from '@liforma/client/react';
export function PrivateLesson() {
return (
<Experience
experienceId="exp_t0i7acmq"
sessionEndpoint="/api/liforma-session"
/>
);
}3. Implement sessionEndpoint
SvelteKit
// src/routes/api/liforma-session/+server.ts (SvelteKit)
import { json } from '@sveltejs/kit';
export async function POST({ request }) {
const { experienceId, language, mode, speechInputMode, speechOnly } = await request.json();
const res = await fetch('https://api.liforma.ai/v1/sessions', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.LIFORMA_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
experienceId,
...(language ? { language } : {}),
...(mode ? { mode } : {}),
...(speechInputMode ? { speechInputMode } : {}),
...(speechOnly === true ? { speechOnly: true } : {})
})
});
if (!res.ok) {
return json({ message: 'Failed to mint session' }, { status: res.status });
}
return json(await res.json(), {
headers: { 'Cache-Control': 'no-store, private' }
});
}Next.js App Router
// app/api/liforma-session/route.ts (Next.js App Router)
import { createLiformaSessionRouteHandler } from '@liforma/client/next';
export const POST = createLiformaSessionRouteHandler({
// Production: supply authorize() that checks your app session.
// Demos only:
allowUnauthenticated: true
});Contract: POST with experienceId (and optional launch fields) →
SessionLaunchResponse with Cache-Control: no-store, private.
If you know the visitor is already registered, include
alreadyConverted: [‘registration’] so conversion rate excludes them.
Resolve the signed-in customer on this route and send runtimeContext on
POST /v1/sessions. The browser then receives only the opaque launch. Example body
fields: experienceId, runtimeContext: { first_name, account: { plan } }.
user and metadata stay identity and analytics data.
variables remains accepted and is not used in prompts.
SSR warning
Do not put opaque launch through server load functions — it would be embedded in
HTML. Prefer sessionEndpoint so the browser fetches the launch after load.
API key
Obtain a developer API key from the
developer portal
(app.liforma.ai). Store it in server environment variables only.