Campaigns

The unit of calling work: who to call, with which agent, and when.

A campaign binds an agent to a set of people and a way of reaching them. Outbound campaigns dial; inbound campaigns answer a provisioned number.

type decides where the people come from. A contact campaign calls the contacts attached to it from its contact list. An api campaign holds no contacts: each call is placed with POST /api/v1/campaigns/{id}/trigger, which suits a system of record on your side.

The voice engine follows the campaign's agent; choose it by choosing the agent. Every resource a campaign names (agent, contact list, workspace, number, integration) must be in the key's organization.

An inbound campaign answers a provisioned number, or takes its calls from a contact center: with contactCenter: { integrationId } naming a Genesys Cloud integration (see Integrations), Genesys streams calls to the campaign's agent through its Audio Connector and gets the agent's outcome back when it hands over. Such a campaign has no number. In Genesys, enter contactCenter.connectorId (the campaign's id) as the Connector ID on the Architect "Call Audio Connector" action. The agent hands back with the outputs intent, targetQueue, summary, collectedData (JSON), handedBack, endReason and vocalisCallId. It must be a Vocalis agent, not a connected agent. contactCenter is the only way to bind a Genesys integration; config.integration is for post-call integrations and refuses one, and a campaign's Genesys binding is not repeated in the config you read back.

Calls are attributed to a run. Starting an outbound contact campaign opens one; an api campaign opens one per day as calls are triggered.

No outbound call is placed to a number that opted out: one that asked this organization not to call, one with a do-not-call request anywhere on the platform, or one on the national Do Not Call registry. With requireConsent: true, a campaign also calls only numbers that hold an active voice consent (see Consents). Both are checked when a call is triggered and again just before it is dialled. A queued call refused at dial time is never placed: it is cancelled with endReason dnc_blocked (opted out), registry_blocked (national registry) or consent_required, or failed with destination_not_allowed when the number is outside the regions the organization may call.

nationalRegistryExempt: true skips the national Do Not Call registry check only, for healthcare calls to existing patients. Numbers that opted out of your organization or anywhere on the platform, and requireConsent, still apply. Turning it on or changing nationalRegistryExemptReason needs a key whose owner holds campaign:manage_compliance in the campaign's organization (owners and admins have it; custom roles can be granted it; otherwise FORBIDDEN). Turning it off needs only access to the campaign and clears the reason. Every change is audited.

List campaigns#

Campaigns the key can reach, most recently updated first.

GET/api/v1/campaigns

Query parameters

pagenumber
Page number.
Default1
pageSizenumber
Campaigns per page, up to 100.
Default20
statusstring
draft, active, paused or completed.
directionstring
outbound or inbound.
searchstring
Match the name or description.
workspaceIdstring
Only campaigns in this workspace.

Responses

A page of campaigns.

curl -X GET "https://voice.evryhealth.com/api/v1/campaigns" \
-H "Authorization: Bearer YOUR_API_KEY"
Response200
{
"campaigns": [
{
"id": "357b188c-c618-4f95-84b1-cbe26b8ce3e0",
"name": "Q1 Provider Outreach",
"description": "Renewal reminders",
"status": "active",
"type": "contact",
"direction": "outbound",
"objectives": [
{
"id": "confirm",
"name": "Confirm renewal",
"description": "The member confirms they will renew"
}
],
"contactListId": "6d1f3c2a-8b4e-4f7a-9c1d-2e5b7a9f0c13",
"contactListName": "Healthcare Providers",
"workspaceId": null,
"workspaceName": null,
"agentId": "44b1d0e2-7a3c-4f5e-9b8d-1c2e3f4a5b6c",
"connectedAgentId": null,
"contactCenter": null,
"config": {
"notifications": {
"doNotCall": {
"enabled": true,
"emails": [
"compliance@example.com"
]
}
}
},
"provisionedPhoneNumberId": null,
"provisionedPhoneNumber": null,
"isTestCampaign": false,
"requireConsent": false,
"nationalRegistryExempt": false,
"nationalRegistryExemptReason": null,
"nationalRegistryExemptBy": null,
"nationalRegistryExemptAt": null,
"hasActiveRun": true,
"totalRuns": 3,
"completedRuns": 2,
"createdAt": "2026-01-15T10:30:00.000Z",
"updatedAt": "2026-01-20T08:00:00.000Z"
}
],
"pagination": {
"page": 1,
"pageSize": 20,
"total": 5,
"totalPages": 1
}
}

Create a campaign#

A campaign starts in `draft`. It needs an agent before it can place calls, and an outbound `contact` campaign needs contacts before it can start. `type` and `direction` are fixed once created. An inbound campaign needs `provisionedPhoneNumberId` or `contactCenter`; with `contactCenter`, send no number and no `connectedAgentId`, and don't also send `config.integration` (a campaign has one integration).

POST/api/v1/campaigns

Body

application/json
name
string
required
description
string
agentId
string (null clears it on update)
connectedAgentId
string | null (inbound only, in place of agentId: a connected agent answers; null clears it on update)
contactListId
string
workspaceId
string
provisionedPhoneNumberId
string (inbound: the number it answers; required unless contactCenter is set)
contactCenter
{ integrationId } | null (inbound only: take calls from a contact center that streams them, such as a Genesys Cloud integration, instead of a number; null means none on create and clears it on update). Read back as { integrationId, platform, connectorId }, where platform is the integration's providerType (the contact centers that stream calls: genesys)
objectives
{ id, name, description, sub? }[]
config
{ notifications?, phoneNumber?, businessHours?, integration?, dataCollection? } (integration is a post-call integration; a Genesys one is set with contactCenter)
isTestCampaign
boolean
requireConsent
boolean (outbound only)
nationalRegistryExempt
boolean (outbound only; turning on needs campaign:manage_compliance)
nationalRegistryExemptReason
string, 10 to 500 characters (required when turning the exemption on)
type
contact | api | provider (default contact)
direction
outbound | inbound (default outbound)

Responses

The created campaign.

curl -X POST "https://voice.evryhealth.com/api/v1/campaigns" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Q1 Provider Outreach",
"type": "contact",
"direction": "outbound",
"agentId": "44b1d0e2-7a3c-4f5e-9b8d-1c2e3f4a5b6c",
"contactListId": "6d1f3c2a-8b4e-4f7a-9c1d-2e5b7a9f0c13"
}'
Response201
{
"id": "357b188c-c618-4f95-84b1-cbe26b8ce3e0",
"name": "Q1 Provider Outreach",
"description": "Renewal reminders",
"status": "draft",
"type": "contact",
"direction": "outbound",
"objectives": [
{
"id": "confirm",
"name": "Confirm renewal",
"description": "The member confirms they will renew"
}
],
"contactListId": "6d1f3c2a-8b4e-4f7a-9c1d-2e5b7a9f0c13",
"contactListName": "Healthcare Providers",
"workspaceId": null,
"workspaceName": null,
"agentId": "44b1d0e2-7a3c-4f5e-9b8d-1c2e3f4a5b6c",
"connectedAgentId": null,
"contactCenter": null,
"config": {
"notifications": {
"doNotCall": {
"enabled": true,
"emails": [
"compliance@example.com"
]
}
}
},
"provisionedPhoneNumberId": null,
"provisionedPhoneNumber": null,
"isTestCampaign": false,
"requireConsent": false,
"nationalRegistryExempt": false,
"nationalRegistryExemptReason": null,
"nationalRegistryExemptBy": null,
"nationalRegistryExemptAt": null,
"hasActiveRun": false,
"totalRuns": 0,
"completedRuns": 0,
"createdAt": "2026-01-15T10:30:00.000Z",
"updatedAt": "2026-01-20T08:00:00.000Z"
}

Get a campaign#

One campaign, with its configuration and current status.

GET/api/v1/campaigns/{id}

Path parameters

idstringrequired
Campaign id.

Responses

The campaign.

curl -X GET "https://voice.evryhealth.com/api/v1/campaigns/{id}" \
-H "Authorization: Bearer YOUR_API_KEY"
Response200
{
"id": "357b188c-c618-4f95-84b1-cbe26b8ce3e0",
"name": "Q1 Provider Outreach",
"description": "Renewal reminders",
"status": "active",
"type": "contact",
"direction": "outbound",
"objectives": [
{
"id": "confirm",
"name": "Confirm renewal",
"description": "The member confirms they will renew"
}
],
"contactListId": "6d1f3c2a-8b4e-4f7a-9c1d-2e5b7a9f0c13",
"contactListName": "Healthcare Providers",
"workspaceId": null,
"workspaceName": null,
"agentId": "44b1d0e2-7a3c-4f5e-9b8d-1c2e3f4a5b6c",
"connectedAgentId": null,
"contactCenter": null,
"config": {
"notifications": {
"doNotCall": {
"enabled": true,
"emails": [
"compliance@example.com"
]
}
}
},
"provisionedPhoneNumberId": null,
"provisionedPhoneNumber": null,
"isTestCampaign": false,
"requireConsent": false,
"nationalRegistryExempt": false,
"nationalRegistryExemptReason": null,
"nationalRegistryExemptBy": null,
"nationalRegistryExemptAt": null,
"hasActiveRun": true,
"totalRuns": 3,
"completedRuns": 2,
"createdAt": "2026-01-15T10:30:00.000Z",
"updatedAt": "2026-01-20T08:00:00.000Z"
}

Update a campaign#

Only the fields you send are changed. `config` is replaced whole, so send all of it. An inbound campaign is answered by either an agent or a connected agent: setting `connectedAgentId` clears `agentId`, and setting `agentId` clears `connectedAgentId`, so switching between them takes one field. Where an inbound campaign's calls arrive from works the same way: setting `contactCenter` clears the number, setting `provisionedPhoneNumberId` clears `contactCenter`, and `contactCenter: null` clears it. That can only change while the campaign isn't active (`INVALID_STATE` otherwise; pause it first). A `config` sent on its own keeps the campaign's Genesys binding. Setting `contactCenter` on a campaign with a post-call integration in `config.integration` needs a `config` without it in the same request. `contactCenter: null` with no number leaves an inbound campaign with neither; it is refused when started (`CAMPAIGN_NOT_CONFIGURED`) until one is set.

PATCH/api/v1/campaigns/{id}

Path parameters

idstringrequired
Campaign id.

Body

application/json
name
string
description
string
agentId
string (null clears it on update)
connectedAgentId
string | null (inbound only, in place of agentId: a connected agent answers; null clears it on update)
contactListId
string
workspaceId
string
provisionedPhoneNumberId
string (inbound: the number it answers; required unless contactCenter is set)
contactCenter
{ integrationId } | null (inbound only: take calls from a contact center that streams them, such as a Genesys Cloud integration, instead of a number; null means none on create and clears it on update). Read back as { integrationId, platform, connectorId }, where platform is the integration's providerType (the contact centers that stream calls: genesys)
objectives
{ id, name, description, sub? }[]
config
{ notifications?, phoneNumber?, businessHours?, integration?, dataCollection? } (integration is a post-call integration; a Genesys one is set with contactCenter)
isTestCampaign
boolean
requireConsent
boolean (outbound only)
nationalRegistryExempt
boolean (outbound only; turning on needs campaign:manage_compliance)
nationalRegistryExemptReason
string, 10 to 500 characters (required when turning the exemption on)

Responses

The updated campaign.

curl -X PATCH "https://voice.evryhealth.com/api/v1/campaigns/{id}" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Q1 Provider Outreach (Updated)",
"description": "Updated description"
}'
Response200
{
"id": "357b188c-c618-4f95-84b1-cbe26b8ce3e0",
"name": "Q1 Provider Outreach (Updated)",
"description": "Updated description",
"status": "active",
"type": "contact",
"direction": "outbound",
"objectives": [
{
"id": "confirm",
"name": "Confirm renewal",
"description": "The member confirms they will renew"
}
],
"contactListId": "6d1f3c2a-8b4e-4f7a-9c1d-2e5b7a9f0c13",
"contactListName": "Healthcare Providers",
"workspaceId": null,
"workspaceName": null,
"agentId": "44b1d0e2-7a3c-4f5e-9b8d-1c2e3f4a5b6c",
"connectedAgentId": null,
"contactCenter": null,
"config": {
"notifications": {
"doNotCall": {
"enabled": true,
"emails": [
"compliance@example.com"
]
}
}
},
"provisionedPhoneNumberId": null,
"provisionedPhoneNumber": null,
"isTestCampaign": false,
"requireConsent": false,
"nationalRegistryExempt": false,
"nationalRegistryExemptReason": null,
"nationalRegistryExemptBy": null,
"nationalRegistryExemptAt": null,
"hasActiveRun": true,
"totalRuns": 3,
"completedRuns": 2,
"createdAt": "2026-01-15T10:30:00.000Z",
"updatedAt": "2026-01-20T08:00:00.000Z"
}

Delete a campaign#

Removes the campaign. Its calls and runs remain in the call history.

DELETE/api/v1/campaigns/{id}

Path parameters

idstringrequired
Campaign id.

Responses

Deleted.

curl -X DELETE "https://voice.evryhealth.com/api/v1/campaigns/{id}" \
-H "Authorization: Bearer YOUR_API_KEY"
Response200
{
"message": "Campaign deleted successfully"
}

Start a campaign#

Outbound `contact` campaigns open a run (or return the one already running) and need contacts. `api` campaigns just become active. Inbound campaigns start answering their number, or, with `contactCenter`, start taking calls from Genesys Cloud (Genesys calls to a campaign that isn't active are refused). Starting does not itself dial: queue calls on the run with `POST /api/v1/campaigns/{id}/calls`.

POST/api/v1/campaigns/{id}/start

Path parameters

idstringrequired
Campaign id.

Responses

Started. Outbound campaigns carry `run`; inbound ones that answer a number carry `deployment` instead; a contact center campaign carries neither.

curl -X POST "https://voice.evryhealth.com/api/v1/campaigns/{id}/start" \
-H "Authorization: Bearer YOUR_API_KEY"
Response202
{
"message": "Campaign started successfully",
"campaignId": "357b188c-c618-4f95-84b1-cbe26b8ce3e0",
"status": "starting",
"run": {
"id": "9f8e7d6c-5b4a-4321-8fed-cba987654321",
"runNumber": 4,
"status": "in_progress",
"startedAt": "2026-01-20T10:30:00.000Z",
"isNew": true
}
}

Pause a campaign#

Stops new calls. Outbound: queued calls are cancelled; calls in progress carry on. Inbound: the number stops being answered, or Genesys Cloud calls stop reaching the agent.

POST/api/v1/campaigns/{id}/pause

Path parameters

idstringrequired
Campaign id.

Responses

Paused. Inbound campaigns that answer a number carry `deployment` instead of `cancelledCalls`; a contact center campaign carries neither.

curl -X POST "https://voice.evryhealth.com/api/v1/campaigns/{id}/pause" \
-H "Authorization: Bearer YOUR_API_KEY"
Response200
{
"message": "Campaign paused successfully",
"campaignId": "357b188c-c618-4f95-84b1-cbe26b8ce3e0",
"status": "paused",
"previousStatus": "active",
"cancelledCalls": 12
}

Trigger a call#

Places one call through an `api` campaign, right away. The campaign's agent runs it unless you pass `agentId` (another agent in this organization) or `systemPrompt` (your own prompt); not both. Activates a draft campaign.

POST/api/v1/campaigns/{id}/trigger

Path parameters

idstringrequired
Campaign id.

Body

application/json
phoneNumber
string (E.164)
required
calleeName
string
agentId
string
systemPrompt
string
promptAppend
string
variables
Record<string, unknown>
fromPhoneNumberId
string
metadata
Record<string, unknown>
callbackUrl
string (https)

Responses

Queued. `runId` is the day's run the call belongs to.

curl -X POST "https://voice.evryhealth.com/api/v1/campaigns/{id}/trigger" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"phoneNumber": "+14155551234",
"calleeName": "Jane Doe",
"variables": {
"memberId": "M-10293",
"renewalDate": "2026-03-01"
},
"callbackUrl": "https://hooks.example.com/vocalis"
}'
Response202
{
"callId": "5bbdd475-38f6-4211-9361-de482d105b81",
"campaignId": "357b188c-c618-4f95-84b1-cbe26b8ce3e0",
"runId": "9f8e7d6c-5b4a-4321-8fed-cba987654321",
"phoneNumber": "+14155551234",
"status": "queued",
"createdAt": "2026-01-20T10:30:00.000Z"
}

List campaign contacts#

The contacts attached to a campaign, with where each one is in it. `summary` counts every contact on the campaign, ignoring filters.

GET/api/v1/campaigns/{id}/contacts

Path parameters

idstringrequired
Campaign id.

Query parameters

pagenumber
Page number.
Default1
pageSizenumber
Contacts per page, up to 100.
Default50
callStatusstring
not_called, queued, called, reached or failed.
sortBystring
name, phoneNumber, callStatus or lastCallAttemptAt. Defaults to newest added first.
sortOrderstring
asc or desc.
Defaultasc
searchstring
Match a name or phone number.

Responses

A page of campaign contacts.

curl -X GET "https://voice.evryhealth.com/api/v1/campaigns/{id}/contacts" \
-H "Authorization: Bearer YOUR_API_KEY"
Response200
{
"contacts": [
{
"campaignContactId": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f",
"contactId": "e3c9a1b7-2d4f-4a6e-8b0c-1f2e3d4c5b6a",
"phoneNumber": "4155551234",
"fullName": "Jane Doe",
"email": "jane@example.com",
"data": {
"memberId": "M-10293"
},
"callStatus": "reached",
"callAttemptCount": 1,
"lastCallAttemptAt": "2026-01-20T10:34:05.000Z"
}
],
"pagination": {
"page": 1,
"pageSize": 50,
"total": 500,
"totalPages": 10
},
"summary": {
"total": 500,
"notCalled": 320,
"queued": 10,
"called": 60,
"reached": 100,
"failed": 10
}
}

Add contacts to a campaign#

Writes new contacts to the campaign's contact list and attaches them to the campaign. The campaign needs a contact list. A contact that cannot be added (for example a duplicate) is counted in `skippedCount`. To import a file, use the contact list import.

POST/api/v1/campaigns/{id}/contacts

Path parameters

idstringrequired
Campaign id.

Body

application/json
contacts
{ phoneNumber, fullName, firstName?, lastName?, email?, data? }[]
required

Responses

Added.

curl -X POST "https://voice.evryhealth.com/api/v1/campaigns/{id}/contacts" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"contacts": [
{
"phoneNumber": "+14155551234",
"fullName": "Jane Doe",
"data": {
"memberId": "M-10293"
}
}
]
}'
Response201
{
"message": "1 contacts added to campaign",
"addedCount": 1,
"skippedCount": 0,
"status": "completed"
}

Remove a contact from a campaign#

Detaches one contact. The contact itself stays in its list. Only its place in this campaign is removed.

DELETE/api/v1/campaigns/{id}/contacts/{contactId}

Path parameters

idstringrequired
Campaign id.
contactIdstringrequired
Contact id.

Responses

Removed.

curl -X DELETE "https://voice.evryhealth.com/api/v1/campaigns/{id}/contacts/{contactId}" \
-H "Authorization: Bearer YOUR_API_KEY"
Response200
{
"message": "Contact removed from campaign successfully"
}