Getting started
Quickstart
Five requests: a key, a list, some contacts, a call, and the transcript it produced.
Before you start
You need an API key and a phone number on the account. Everything else below is created as you go.
Node.js and TypeScript
In Node.js, Bun, Deno or an edge runtime, use the TypeScript SDK. It types every request and response, retries safely and follows calls live. The cURL walkthrough below makes the same requests by hand.
npm install @pendral/vocalis
import Vocalis from "@pendral/vocalis";const vocalis = new Vocalis(); // reads VOCALIS_API_KEYconst call = await vocalis.campaigns.trigger("cmp_...", {phoneNumber: "+14155551234",calleeName: "Dr. Jane Smith",variables: { memberId: "M-10293" },});console.log(call.callId, call.status); // "queued"
Then read the conversation back once it ends:
const transcript = await vocalis.calls.transcript(callId);const details = await vocalis.calls.get(callId);
The walkthrough
Create a key
Keys live under Settings → API Keys. A key belongs to the organization you were in when you made it and reads nothing outside it. Keep it in an environment variable. It is a password, not an identifier.
export VOCALIS_API_KEY="dk_..."Check that it works
The cheapest possible request. A
200here means the key is valid and scoped to read calls.curl "https://vocalis.io/api/v1/calls?pageSize=1" \-H "Authorization: Bearer $VOCALIS_API_KEY"Create a contact list
A list carries a schema, and the schema is what a campaign can say out loud: every field you declare here is available to the agent as a variable when it calls someone on the list.
curl -X POST "https://vocalis.io/api/v1/contact-lists" \-H "Authorization: Bearer $VOCALIS_API_KEY" \-H "Content-Type: application/json" \-d '{"name": "Q1 renewals","schema": {"fields": [{ "fieldName": "memberId", "displayName": "Member ID", "dataType": "string", "isRequired": true },{ "fieldName": "renewalDate", "displayName": "Renewal date", "dataType": "date" }]}}'Add contacts
Send the rows inline, or a URL to a JSON or CSV file if there are more than a few thousand. Either way the response is an import job rather than the contacts themselves.
curl -X POST "https://vocalis.io/api/v1/contact-lists/{listId}/contacts" \-H "Authorization: Bearer $VOCALIS_API_KEY" \-H "Content-Type: application/json" \-d '{"contacts": [{ "phoneNumber": "+14155551234", "fullName": "John Doe", "data": { "memberId": "M-10293", "renewalDate": "2026-04-01" } },{ "phoneNumber": "+14155555678", "fullName": "Jane Smith", "data": { "memberId": "M-10294", "renewalDate": "2026-04-18" } }]}'Poll the job until it settles. Rows that fail validation are reported individually; the rest still land.
GET /api/v1/import-jobs/{jobId}{"id": "job_abc123","status": "completed","progress": { "total": 100, "processed": 100, "successful": 98, "failed": 2 },"completedAt": "2026-03-04T10:30:45Z"}Read what was said
Once the campaign runs, each conversation is a call. Analysis is not immediate: a call ends, then the recording is fetched, transcribed and analysed, so check
isAnalyzedbefore readinganalysis.curl "https://vocalis.io/api/v1/calls/{callId}" \-H "Authorization: Bearer $VOCALIS_API_KEY"Do not poll for this
Subscribe tocall.completedinstead and you will be told the moment the analysis lands. Webhooks.