Quick start

Backend middleware, one React hook, and your agents are making calls.

1. Install

Backend
npm install @dialer/node
Frontend
npm install @dialer/react

2. Add the middleware

The middleware mints short-lived agent tokens. It sits on any route your frontend can reach.

server.js
import { DialerAuth } from '@dialer/node'

app.use(DialerAuth({
  apiKey: process.env.DIALER_API_KEY,
  getAgentId: (req) => req.user.id
}))

This creates a POST /dialer-auth endpoint that the React SDK calls automatically.

3. Render the agent UI

Agent.jsx
import { useDialer, CallCard } from '@dialer/react'

export default function Agent() {
  const { activeCall, endCall, status } =
    useDialer({ authUrl: '/dialer-auth' })

  if (!activeCall) {
    return <div>Waiting for a call...</div>
  }

  return <CallCard call={activeCall} onEnd={endCall} />
}

Start a campaign from the dashboard. When a contact answers, the call appears in your agent UI.

That's it. Dialing, bridging, transcription, recording, compliance checks, and AI summaries are handled by the platform. Your code only touches the agent experience.

Authentication

Two token types, two use cases.

TokenFormatUse
API key sk_live_... Server-side. Campaign management, data access, minting agent tokens. Never expose to browsers.
Agent token at_... Client-side. Short-lived JWT for the React SDK. Minted by your backend via the middleware or POST /api/auth/token.

All API requests use the Authorization: Bearer <token> header. Rate limit: 100 req/min.

Backend setup

The @dialer/node middleware handles agent token minting. It's the only backend code you write.

DialerAuth options

OptionTypeDescription
apiKey* string Your sk_live_... API key
getAgentId* (req) => string Return the current user's member ID from your auth system
path string Endpoint path. Default: /dialer-auth
expiresIn number Token TTL in seconds. Default: 3600

Manual token minting

If you don't use the middleware, mint tokens directly:

const res = await fetch('https://api.withdialer.com/api/auth/token', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${API_KEY}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    member_id: 'mem_abc123',
    expires_in: 3600
  })
})

Frontend (React)

The @dialer/react SDK connects your UI to live calls via SSE. One hook gives you everything.

useDialer

The main hook. Connects to the SSE stream, manages call state, and exposes actions.

const {
  status,          // 'connecting' | 'available' | 'on_call' | 'away'
  activeCall,      // Call object or null
  phase,           // 'idle' | 'active' | 'post_call'
  summary,         // AI summary after call ends
  endedCallId,     // ID of the just-ended call (for disposition)
  endCall,         // () => void
  submitDisposition, // (disposition, notes?) => void
  setAvailable,    // () => void
  setAway,         // () => void
} = useDialer({ authUrl: '/dialer-auth' })
OptionTypeDescription
authUrl* string Your backend's token endpoint
token string Pass a token directly (test/playground use only)

Components

Pre-built UI components. Fully stylable via className.

ComponentPropsDescription
CallCard call, onEnd, children, className Active call display with contact info, timer, and end button
LiveTranscript callId, className Real-time dual-channel transcript
DispositionPicker onSubmit, suggestedDisposition, summaryText, className Post-call outcome selector with AI-suggested disposition
AgentStatus status, onAvailable, onAway, className Available/away toggle

Full example

import {
  useDialer, CallCard, LiveTranscript,
  DispositionPicker, AgentStatus
} from '@dialer/react'

export default function Agent() {
  const {
    activeCall, endCall, status, setAvailable, setAway,
    summary, phase, endedCallId, submitDisposition
  } = useDialer({ authUrl: '/dialer-auth' })

  return (
    <div>
      <AgentStatus
        status={status}
        onAvailable={setAvailable}
        onAway={setAway}
      />

      {phase === 'post_call' && endedCallId && (
        <DispositionPicker
          onSubmit={(d, notes) => submitDisposition(d, notes)}
          summaryText={summary?.text}
          suggestedDisposition={summary?.suggested_disposition}
        />
      )}

      {activeCall && (
        <CallCard call={activeCall} onEnd={endCall}>
          <LiveTranscript callId={activeCall.id} />
        </CallCard>
      )}

      {!activeCall && status === 'available' && (
        <div>Waiting for a call...</div>
      )}
    </div>
  )
}

Events (SSE)

All state changes stream over Server-Sent Events. No webhooks, no public URLs. Works on localhost.

Endpoints

EndpointAuthScope
GET /api/events API key All org events. For backend integrations.
GET /api/agent/events Agent token Events for one agent. Used by the React SDK automatically.

Reconnect with Last-Event-Id header to resume from where you left off. Heartbeat every 30s.

Event types

EventDescription
call.ringingContact is being dialed
call.connectedCall bridged to agent. Includes contact name, phone, metadata.
call.endedCall completed. Includes duration and disposition.
call.voicemailAMD detected voicemail. Call dropped silently.
call.recording_readyRecording URL available (pre-signed, 1hr expiry).
call.summaryAI summary and suggested disposition ready (~30s after call).
transcript.partialLive transcription chunk. Speaker labeled.
transcript.finalFinal transcription line with confidence score.
agent.availableAgent went available for calls.
agent.on_callAgent took a call.
agent.offlineAgent disconnected.
campaign.startedCampaign started dialing.
campaign.pausedCampaign paused.
campaign.exhaustedAll contacts dialed. Includes connect rate and total talk time.
sms.sentSMS follow-up sent (voicemail/no-answer).
sms.replyInbound SMS from contact.

Event shape

{
  "id": "evt_01hx...",
  "type": "call.connected",
  "data": {
    "call_id": "call_abc",
    "campaign_id": "camp_xyz",
    "contact": {
      "name": "Sarah Chen",
      "phone": "+14155550182",
      "metadata": { "deal_stage": "Negotiation" }
    }
  },
  "timestamp": "2026-03-31T14:22:08Z"
}

Campaigns

Create a campaign, upload contacts, assign agents, start dialing.

POST
/api/campaigns
Create a campaign
GET
/api/campaigns
List all campaigns
GET
/api/campaigns/:id
Get campaign
PATCH
/api/campaigns/:id
Update campaign settings
POST
/api/campaigns/:id/contacts
Add contacts (up to 10k per request)
POST
/api/campaigns/:id/agents
Assign agents
POST
/api/campaigns/:id/start
Start dialing
POST
/api/campaigns/:id/pause
Pause (reversible)
POST
/api/campaigns/:id/stop
Stop campaign
GET
/api/campaigns/:id/stats
Live stats (dials in flight, connect rate)
GET
/api/campaigns/:id/agents
Agent statuses for campaign
GET
/api/campaigns/:id/contacts
List campaign contacts

Campaign states

run_statusDescription
pausedDefault. Campaign is idle.
runningActively dialing contacts.
stoppedPermanently stopped. Cannot restart.
progress_statusDescription
not_startedNo contacts have been dialed.
in_progressSome contacts remain.
exhaustedAll contacts attempted (with retries).

Calls

GET
/api/calls
List calls (filter by campaign, agent, status, disposition, date)
GET
/api/calls/:id
Get call
GET
/api/calls/:id/transcript
Full dual-channel transcript
GET
/api/calls/:id/summary
AI summary + suggested disposition
GET
/api/calls/:id/recording
Pre-signed recording URL (1hr expiry)
PATCH
/api/calls/:id/disposition
Submit disposition

Dispositions

answered callback_requested not_interested converted voicemail no_answer other

Contacts

GET
/api/contacts
Search contacts (by name, phone, status)
GET
/api/contacts/:id
Get contact
PATCH
/api/contacts/:id
Update name or metadata

Contacts are uploaded via POST /api/campaigns/:id/contacts. Phone numbers must be E.164 format (+14155550182).

Agents

GET
/api/members
List org members
POST
/api/auth/token
Mint agent token
POST
/api/agent/available
Set agent available
POST
/api/agent/away
Set agent away
GET
/api/agent/presence
Get agent presence status
POST
/api/agent/voice-token
Generate browser voice token

Agent statuses

connecting available on_call away offline

DNC (Do Not Call)

DNC is checked before every dial. Adding a number blocks it immediately across all campaigns.

POST
/api/dnc
Add number to DNC list
GET
/api/dnc
List DNC entries
DNC is append-only. Contacts who reply STOP to any SMS are added automatically.

SMS

SMS follow-ups are sent automatically on voicemail or no-answer (when enabled on the campaign). You can also send manually.

POST
/api/sms
Send SMS
GET
/api/sms
List SMS messages