Skip to main content
Version: 0.9.14

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.

Pseudo-shapes

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:

  1. Capture the requestId from the response body.
  2. File a ticket per Getting help with the requestId, the time window, and a redacted sample of the request.
  3. Your platform operator can find the request directly in SigNoz — much faster than reconstructing from logs.

See also