Examples
Concrete recipes that pull together the rest of this section. Each one is short, copy-pasteable, and links back to the conceptual pages where the fields are explained.
Until the generated reference lands, the exact path and field names below are written as placeholders (/v1/call-tokens, data: {...}). Replace them with the values from your platform's Swagger UI at /documentation (TelAPI ships it) once you confirm the version.
1. Mint a call token from a Node.js backend
import {fetch} from 'undici';
const API_BASE = process.env.TELAPI_BASE_URL!; // https://api.your-delphi.example
const API_KEY = process.env.TELAPI_KEY!;
async function mintCallToken(endpointId: string) {
const res = await fetch(`${API_BASE}/v1/call-tokens`, {
method: 'POST',
headers: {
Authorization: `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({endpointId}),
});
if (!res.ok) {
const body = await res.text();
throw new Error(`Token mint failed: ${res.status} ${body}`);
}
return res.json() as Promise<{token: string; endpointId: string; expiresAt: string}>;
}
Then expose it to your client behind your own auth:
// Express-style handler
app.post('/api/browser-sdk/call-tokens', requireUser, async (req, res) => {
const {endpointId} = req.body;
const token = await mintCallToken(endpointId);
res.json(token);
});
See Call tokens for the lifecycle.
2. Use the minted token from the client SDK
import {DelphiClient} from '@ki-kombinat/delphi-client-js-sdk';
const client = new DelphiClient({baseUrl: 'https://api.your-delphi.example'});
async function startCall() {
const {token, endpointId} = await fetch('/api/browser-sdk/call-tokens', {
method: 'POST',
body: JSON.stringify({endpointId: 'ep_abc123'}),
headers: {'Content-Type': 'application/json'},
}).then((r) => r.json());
const session = await client.openVoiceCall({endpointId, token});
session.on('transcript', (t) => console.log('they said:', t.text));
session.on('end', () => console.log('call ended'));
}
The SDK handles the WebSocket channel on your behalf. See SDK quick start → Voice call for the full version.
3. Discover the team's apps before minting
When your backend picks the right app/endpoint dynamically rather than hard-coding an endpoint ID, give the key the READ_TEAM_APPS scope:
async function pickEndpoint(appName: string) {
const res = await fetch(`${API_BASE}/v1/apps`, {
headers: {Authorization: `Bearer ${API_KEY}`},
});
if (!res.ok) throw new Error(`list apps failed: ${res.status}`);
const apps = (await res.json()) as Array<{id: string; name: string; endpoints: {id: string}[]}>;
const app = apps.find((a) => a.name === appName);
return app?.endpoints[0]?.id;
}
Cache the result for the lifetime of your process; app metadata changes rarely.
4. Respond to an async LLM/tool request
This is the inbound /api/v1/webhooks/* endpoint. When Delphi calls your async LLM/tool, the original request gives you a callback URL and a one-shot callback token. Use both verbatim — do not invent your own.
import {fetch} from 'undici';
// Inside your async worker, after producing the answer
async function deliverAsyncResult(callback: {url: string; token: string}, result: unknown) {
const res = await fetch(callback.url, {
method: 'POST',
headers: {
'X-Delphi-Callback-Token': callback.token, // illustrative — real header name comes from the outbound request (TOBi uses `token`)
'Content-Type': 'application/json',
},
body: JSON.stringify(result),
});
if (res.status === 410) {
// Conversation already ended — drop the result.
return;
}
if (!res.ok) {
// Retry with backoff (token is still valid within its TTL).
throw new Error(`callback failed: ${res.status}`);
}
}
The callback credential is one-shot per work item and tied to the conversation that is waiting. Don't log it, don't reuse it. The header name and format are integration-specific — for example TOBi requires a literal token header (see Webhooks); other flows may use a different header name from the original outbound request.
5. Retry with backoff and idempotency
async function callWithRetry<T>(fn: () => Promise<T>, max = 5): Promise<T> {
let attempt = 0;
let lastError: unknown;
while (attempt < max) {
try {
return await fn();
} catch (err: any) {
lastError = err;
const status = err?.status ?? 0;
const retryable = status === 429 || (status >= 500 && status < 600);
if (!retryable) throw err;
const delay = Math.min(8_000, 250 * 2 ** attempt) + Math.random() * 250;
await new Promise((r) => setTimeout(r, delay));
attempt++;
}
}
throw lastError;
}
See Errors for which codes are retryable.
6. Look up a request when something goes wrong
When you get a 5xx or unexpected 4xx from TelAPI:
- Capture the
requestIdfrom the response body. - File a ticket per Getting help with the
requestId, the time window, and a redacted sample of the request. - Your platform operator can find the request directly in SigNoz — much faster than reconstructing from logs.
See also
- Call tokens · Webhooks · Errors
- Client SDK — the typed client to use from any browser/mobile app.