---
title: "Dialer SDK Live Calling: Twilio and Credentials | Trellus"
description: "Connect the Dialer SDK to live Trellus calling: createBrowserDialer, transport options, the Twilio Voice SDK, rep credentials and custom transports."
source: "https://www.trellus.ai/developers/dialer-sdk/live-calling"
note: "Text-only copy of the page for AI assistants and agents. Link to the source address, not this file."
site_summary: "https://www.trellus.ai/llms.txt"
---

# Live calling

Connect the SDK to Trellus telephony with the browser factory or an explicit transport. Live calling needs a Trellus account, a rep API key, the Twilio Voice SDK and enablement by Trellus.

By arrangement with Trellus

## What live calling needs

Anyone can install the packages and build against the mock transport. Placing a real call needs all of the following:

- A Trellus account.
- A rep API key. Your application supplies it through `getApiKey`.
- The Twilio Voice SDK, `@twilio/voice-sdk` version `>=2.18.0 <3`, installed alongside the SDK.
- Enablement by Trellus. A line setting in code does not grant entitlement.

**This is an integration preview.** It is a standalone subset of the Trellus Chrome extension and does not claim full parity with it. Your application stays responsible for which contacts may be called and when. See [status and scope](https://www.trellus.ai/developers/dialer-sdk#status).

Talk to Trellus about live access. Already have a Trellus account? [Contact Trellus support](https://app.trellus.ai/support).

## createBrowserDialer

The browser entry point is the smallest live integration. `createBrowserDialer` imports the Twilio Voice SDK lazily and wires the headless SDK to the live transport.

Create a live dialer in the browser

```javascript
import { createBrowserDialer } from '@trellusrelease/dialer-sdk/browser';

const dialer = await createBrowserDialer({ getApiKey: getAuthenticatedRepTrellusKey, maxLines: 3 });
// A user action starts registration; creating the object has no calling side effects.
await dialer.connect();
```

`getAuthenticatedRepTrellusKey` is yours to implement. The package does not supply it.

- Creating the dialer has no calling side effects. It does not request a credential or register a device until `connect()`.
- `connect()` requests microphone access and prepares input audio. Call it from a real user action so the browser can activate audio.
- Each instance registers under its own generated suffix. That keeps audio routing separate. It does not lock queue ownership across tabs.

createBrowserDialer options

| Option | Type and meaning |
| --- | --- |
| `getApiKey` Required | `() => string \| Promise<string>` Your function that returns the signed-in rep’s Trellus API key. Raw credential options are rejected. |
| `maxLines` Optional | `number` Client line ceiling, 1 to 4. Default 1. Configuration does not grant entitlement. |
| `platform` Optional | `string` The platform value agreed with Trellus. Defaults to `TRELLUS_DIALER`. |
| `onDisposition` Optional | `(result: { call: DialCall; disposition: Disposition }) => void \| Promise<void>` Saves a completed wrap-up through your application. |
| `Device` Optional | `Twilio Device constructor` Pass it to reuse a Twilio import your application already owns. When omitted, the factory imports `@twilio/voice-sdk` lazily. |
| `transportOptions` Optional | `object` Service and runtime configuration only: `routerUrl`, `bootstrapUrl`, `fetch`, `WebSocket`, `mediaDevices`, `deviceOptions`, `deviceSuffix` and `timeoutMs`. Credentials, `Device`, `platform` and `maxLines` belong at the top level. |

## createTrellusTransport

Build the transport yourself when you want to pass your own Twilio `Device` import or keep the transport and the dialer separate. Set `maxLines` on both.

Explicit transport construction

```javascript
import { Device } from '@twilio/voice-sdk';
import { createDialer } from '@trellusrelease/dialer-sdk';
import { createTrellusTransport } from '@trellusrelease/dialer-sdk/transport';

const transport = createTrellusTransport({
  Device,
  getApiKey: async () => getAuthenticatedRepTrellusKey(),
  platform: 'TRELLUS_DIALER',
  maxLines: 3,
  deviceSuffix: `sdk-${crypto.randomUUID()}`,
});

const dialer = createDialer({
  transport,
  maxLines: 3,
  onDisposition: async ({ call, disposition }) => {
    // Your authenticated application persists the exact task/attempt outcome.
    // Resolve only when your own save contract succeeds.
    await saveCallOutcome({
      taskId: call.taskId,
      attemptId: call.attemptId,
      sessionId: call.sessionId,
      ...disposition,
    });
  },
});

// Invoke from a genuine user action so browser audio can activate.
await dialer.connect();
await dialer.dial({ id: 'task-1', name: 'Alex Example', phoneNumber: '+12025550123' });
```

`getAuthenticatedRepTrellusKey` and `saveCallOutcome` are yours to implement. They are not endpoints supplied by the package.

createTrellusTransport options

| Option | Type and meaning |
| --- | --- |
| `getApiKey` Required | `() => string \| Promise<string>` Returns the signed-in rep’s credential. The transport never persists it. |
| `Device` Required | `Twilio Device constructor` The `Device` constructor from `@twilio/voice-sdk`. The package does not bundle Twilio. |
| `platform` Required | `string` The platform value agreed with Trellus, usually `TRELLUS_DIALER`. |
| `maxLines` Required | `number` Line cap, 1 to 4. Configuration does not grant entitlement. |
| `timeoutMs` Optional | `number` Time limit in milliseconds for credential retrieval, HTTP exchanges and device setup. Default 15000. Accepts 1 to 120000. |
| `deviceOptions` Optional | `Record<string, unknown>` Options for the Twilio `Device`. |
| `deviceSuffix` Optional | `string` A distinct client registration: 1 to 64 letters, digits, underscores or hyphens. The transport adds no suffix unless you supply one. |
| `mediaDevices` Optional | `Pick<MediaDevices, 'getUserMedia'>` Used for the microphone permission and capture check during `connect()`. |
| `fetch, WebSocket` Optional | `typeof fetch, typeof WebSocket` Injectable for controlled testing. |
| `routerUrl, bootstrapUrl` Optional | `string` Service URL overrides for controlled testing. They must use HTTPS. Treat them as trusted deployment configuration, never as end-user or contact input. |

The transport is a browser adapter. It needs no extension, DOM interface or Chrome APIs. It reports its line cap to the dialer when it connects.

In this preview the transport delivers call status, transcripts and detection values. It does not deliver call summaries. Integrate durable final call data separately, through a reporting or webhook path agreed with Trellus.

## The Twilio Voice SDK

Live audio runs through the Twilio Voice SDK in the rep’s browser. Install `@twilio/voice-sdk`, version `>=2.18.0 <3`, alongside the SDK.

Install the SDK and the Twilio Voice SDK

```bash
npm install @trellusrelease/dialer-sdk @twilio/voice-sdk
```

- The packages do not bundle Twilio. It is an optional peer dependency.
- `createBrowserDialer` imports it lazily. Pass `Device` to reuse an import your application already owns.
- `createTrellusTransport` takes the `Device` constructor as a required option.
- If the dialer runs inside an iframe, the iframe must permit microphone use.
- The mock transport does not contact Twilio.

## Credentials

The SDK authenticates as one rep. Your application supplies that rep’s Trellus API key through `getApiKey`.

- **Supply the key through `getApiKey` only.** It is your function. `createBrowserDialer` rejects raw credential options, and `transportOptions` accepts none.
- **Keep the key in memory.** The transport never persists it.
- **Keep the key out of URLs, task metadata, logs and telemetry.** Public snapshots, session views and event payloads never contain API keys, device tokens or transport session handles.
- **One active dialer per rep.** A distinct device registration avoids identity collisions. It does not authorize concurrent dialers for one rep, and the SDK has no cross-tab ownership lease. Your application coordinates one active owner per rep.
- **Do not surface provider errors.** Audio and device controls can reject with errors from the audio provider. Do not show those messages to users or write them to telemetry. Use the sanitized SDK code and message.

Review credential handling with Trellus before you put a rep key in a browser application.

## Line limits

- The headless core accepts `maxLines` from 1 to 10. The default is 1.
- The supplied Trellus transport and `createBrowserDialer` accept 1 to 4.
- The effective limit is the smaller of the dialer’s setting and the cap the transport reports. Read it from `snapshot.maxLines`.
- A line setting in code does not grant entitlement.
- The preview does not fetch a team’s or a rep’s dialing settings, and it does not authorize a requested line count. Confirm the line count with Trellus before you set it.

## Custom transports

The headless SDK talks to any object that implements `DialerTransport`. Use the supplied Trellus transport for live calling and the mock transport for local work. Implement the interface yourself only for a server-mediated integration agreed with Trellus.

DialerTransport

```typescript
interface TransportSession { readonly id: string }

interface DialerTransport<Session extends TransportSession = TransportSession> {
  connect(): Promise<void | { maxLines?: number }>;
  prepare?(options: { mode: DialMode; lines: number }): Promise<void>;
  createCall(options: {
    attemptId: string;
    task: DialTask;
    mode: DialMode;
    lines: number;
    canStartCall: () => boolean;
  }): Promise<Session>;
  subscribe(session: Session, listener: (event: TransportEvent) => void): (() => void) | void;
  hangup(session: Session): Promise<void>;
  mute?(session: Session, muted: boolean): Promise<void>;
  sendDigits?(session: Session, digits: string): Promise<void>;
  getAudioDevices?(): Promise<AudioDevices>;
  setInputDevice?(id: string): Promise<void>;
  setOutputDevice?(id: string): Promise<void>;
  dispose(): void | Promise<void>;
}
```

A transport reports what happens to a call with this small set of events:

Transport events

```typescript
{ type: 'ringing' }
{ type: 'connected' } // Rep actually bridged; not merely destination answered.
{ type: 'ended' }
{ type: 'transcript', segments: TransportTranscriptSegment[] }
{ type: 'detection', value: string }
{ type: 'summary', text: string }
{ type: 'error', error?: { code?: string, outcomeUnknown?: boolean } }
```

### Rules a transport must follow

- Each session handle needs a stable, nonempty `id` of at most 256 characters. The SDK keeps the handle private. Only the `id` is exposed, as `sessionId`.
- Check `canStartCall()` immediately before dispatch, after any asynchronous preparation. A cancellation before dispatch must not place a call.
- If a create may have been dispatched but its result is uncertain, reject with `outcomeUnknown: true`.
- Emit `connected` only with evidence that this rep joined this exact session. A destination that answers is not enough.
- Correlate every event to its exact handle, and keep events that arrive before `subscribe` is called.
- Never report a lost event stream as `ended`.
- A `transcript` event supplies the current segment set, not an append instruction. Final transcript updates may arrive after `ended`.
- Set `isFinal: true` on a segment only when finality is confirmed. When it is omitted, the public output shows `false`.
- Reject unsupported features explicitly instead of pretending success.

When a transport returns an invalid or reused session ID, the SDK treats the result as uncertain. It keeps the capacity as `unknown`, emits no `session.created` and does not subscribe to that handle. See [uncertain outcomes](https://www.trellus.ai/developers/dialer-sdk/lifecycle#uncertain-outcomes).
