Quickstart
From an application key to a call in the browser.
Your backend holds the application key. It sets up a workspace and a user, then hands that user a short-lived token. The user's browser places and answers calls with @sitecall/web.
1. Keep the application key on your server
SiteCall issues your application key (sc_live_…) with the scopes your application is allowed. Store it as a server secret, for example SITECALL_API_KEY. It authenticates as your application for every workspace it manages; never send it to a browser or a phone. Keys rotate with overlap: SiteCall issues the new key before revoking the old one.
server.ts
import { createSiteCallClient } from "@sitecall/cli";
export const sitecall = createSiteCallClient({
apiKey: process.env.SITECALL_API_KEY,
});Every example also works with plain HTTP; the typed client adds the Idempotency-Key, safe retries and the contract's types.
2. Create the workspace and the user
A workspace is one business you serve. Name it by your own id; SiteCall keeps the mapping. Then add your user to it, with the numbers it may place calls from.
server.ts
const workspace = await sitecall.numbers.ensureWorkspace({
externalWorkspaceId: "acme-42",
name: "Acme Roofing",
});
const user = await sitecall.users.upsert(
{ externalUserId: "user-7", displayName: "Dana", role: "agent", numberIds: [numberId] },
{ externalWorkspaceId: "acme-42" },
);Numbers covers buying one; routing plans decide who rings when it is called.
3. Mint an end-user token
Your backend exchanges the key for a token per user. It lasts 15 minutes and carries only the scopes you ask for (at most the key's). Serve it from an endpoint of your own that checks your user's session first.
POST /api/v1/sessions
curl https://sitecall.ai/api/v1/sessions \
-H "authorization: Bearer $SITECALL_API_KEY" \
-H "content-type: application/json" \
-d '{ "externalWorkspaceId": "acme-42",
"externalUserId": "user-7",
"scopes": ["calls:*", "devices:*"] }'
{
"token": "eyJ2Ijox…",
"expiresAt": "2026-10-12T12:15:00.000Z",
"applicationId": "acme-field",
"workspaceId": "6f1c…",
"externalWorkspaceId": "acme-42",
"externalUserId": "user-7",
"scopes": ["calls:*", "devices:*"]
}your-backend/routes/sitecall-token.ts
// GET /sitecall-token: your own route, behind your own login.
export async function GET(request: Request) {
const me = await requireSignedInUser(request); // your auth
const session = await sitecall.sessions.create({
externalWorkspaceId: me.workspaceId,
externalUserId: me.id,
scopes: ["calls:*", "devices:*"],
});
return Response.json({ token: session.token, workspaceId: session.workspaceId });
}Removing the user (DELETE /api/v1/users/{id}) or revoking the key stops new tokens at once; tokens already issued die at expiry, and SIP credentials are revoked.
4. Call from the browser
@sitecall/web registers the browser tab as the user's device, keeps its presence lease, polls its incoming offers and runs the media over WebRTC. It asks your token endpoint before each request and again (refresh: true) when SiteCall refuses a token.
browser.ts
import { createSiteCallWeb } from "@sitecall/web";
const phone = createSiteCallWeb();
await phone.configure({
baseUrl: "https://sitecall.ai",
audioElement: document.querySelector("audio"),
});
type SiteCallToken = { token: string; workspaceId: string };
let cached: SiteCallToken | null = null;
await phone.setSession({
workspaceId: myWorkspaceSiteCallId,
getToken: async ({ refresh } = {}) => {
if (!cached || refresh) {
const fresh: SiteCallToken = await fetch("/sitecall-token").then((r) => r.json());
cached = fresh;
}
return cached.token;
},
});
phone.subscribe((calls, status) => render(calls, status));
// After a click (the browser asks for the microphone):
await phone.connect();
const { callId } = await phone.startOutbound({
toNumber: "+15125550123",
fromNumberId: numberId,
});
// Incoming calls arrive as ringing snapshots:
await phone.answer(ringingCallId);
await phone.hangup(callId);React apps can use useSiteCallPhone(phone) from @sitecall/web/react. Install @telnyx/webrtc beside it (a peer dependency). On sign-out call phone.signOut() while the token still works: it unregisters the tab and revokes its credential.
5. Hear about it
Register a webhook endpoint once and receive call.started, call.answered, call.ended, recording.ready, transcript.ready and the rest, signed and in order per call. See Webhooks.
server.ts
const { endpoint, signingSecret } = await sitecall.webhooks.create({
url: "https://api.example.com/sitecall/events",
});
// signingSecret (whsec_…) is shown once: store it now.