Skip to content

Campaigns ​

16 endpoints. Every path below is relative to the API base URL and already includes the /api/v1 prefix — see conventions for authentication, pagination, errors and idempotency.

Endpoint
GET/campaignsList the organization's campaigns newest first, filterable by status or assistant_id
POST/campaignsCreate a campaign in draft from its target, origination, calling-window and retry settings
GET/campaigns/{campaignId}Retrieve one campaign with its settings, calling window and progress counters
PATCH/campaigns/{campaignId}Update a campaign's settings, refusing any edit once it has reached a terminal status
DELETE/campaigns/{campaignId}Delete a campaign and return an empty body
POST/campaigns/{campaignId}/cancelCancel a campaign, cancelling every undialled contact and queued attempt, and return it
GET/campaigns/{campaignId}/contactsList a campaign's contacts with their state, attempt count, next attempt and last outcome
POST/campaigns/{campaignId}/contactsAdd up to 5000 contacts inline and return the imported, duplicate, invalid and suppressed tally
POST/campaigns/{campaignId}/contacts/previewParse an uploaded CSV's first 10 rows and return the header, sample, suggested mapping and row errors
POST/campaigns/{campaignId}/contacts/uploadImport a CSV of contacts into the campaign and return the batch tally with per-row errors
POST/campaigns/{campaignId}/pausePause a campaign, halting new dials and returning claimed-but-undialled contacts to the queue
GET/campaigns/{campaignId}/progressReturn a campaign's live progress: status, totals, attempts, percent complete and counts by contact state
GET/campaigns/{campaignId}/resultsStream every contact's outcome as a CSV download so a finished 500k-contact campaign never buffers
POST/campaigns/{campaignId}/resumeResume a paused campaign, clear its stop signal and re-dispatch the dialler
POST/campaigns/{campaignId}/startStart a campaign, refusing one with no contacts, no target, or a recording policy it cannot announce
POST/campaigns/preview-contactsIntended CSV preview, but the collection route is unreachable because the handler demands a campaign id

Endpoints ​

List the organization's campaigns newest first, filterable by status or assistant_id ​

GET/campaigns
  • Requires campaign.read.
  • Page size comes from limit (default 25, max 100), not per_page as on the billing and customer endpoints
bash
curl -X GET "$CALLAGENT_API_URL/campaigns" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Accept: application/json"
js
const response = await fetch(`${process.env.CALLAGENT_API_URL}/campaigns`, {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
    Accept: 'application/json',
  },
});

if (!response.ok) {
  const { error } = await response.json();
  throw new Error(`${error.type}: ${error.message}`);
}

const { data } = await response.json();
php
<?php

$handle = curl_init(getenv('CALLAGENT_API_URL') . '/campaigns');

curl_setopt_array($handle, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('CALLAGENT_API_KEY'),
        'Accept: application/json',
    ],
]);

$body = json_decode(curl_exec($handle), true);
$status = curl_getinfo($handle, CURLINFO_HTTP_CODE);
curl_close($handle);

if ($status >= 400) {
    throw new RuntimeException("{$body['error']['type']}: {$body['error']['message']}");
}

$data = $body['data'];
python
import os, requests

response = requests.get(
    f"{os.environ['CALLAGENT_API_URL']}/campaigns",
    headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
    timeout=30,
)
response.raise_for_status()
data = response.json()["data"]

Responses

StatusBody
200data-wrapped array

Errors

StatusWhen it happens
403The API key does not carry the campaign.read scope

Example response, generated from the schema:

json
{
  "data": [
    "string"
  ],
  "meta": {
    "total": 1,
    "per_page": 25,
    "current_page": 1
  }
}

Create a campaign in draft from its target, origination, calling-window and retry settings ​

POST/campaigns
  • Requires campaign.write.
  • Always created with status draft. webhook_secret is accepted and stored encrypted; it is never read back.

Request body

FieldTypeRequiredNotes
namestringyesmax 255 chars
descriptionstring | null—
target_typeassistant · squad · null | null—
assistant_idstring | null—max 26 chars. min 26 chars
squad_idstring | null—max 26 chars. min 26 chars
origination_typephone_number · trunk · null | null—
phone_number_idstring | null—max 26 chars. min 26 chars
sip_trunk_idstring | null—max 26 chars. min 26 chars
caller_id_numberstring | null—max 32 chars
caller_id_namestring | null—max 64 chars
timezonestring | null—
window_start_timestring | null—
window_end_timestring | null—
calling_daysinteger[] | null—
starts_atstring | null—date-time
ends_atstring | null—date-time
max_concurrencyinteger | null—min 1. max 1000
dispatch_batch_sizeinteger | null—min 1. max 500
retry_policystring[] | null—
voicemail_behaviorhangup · leave_message · retry · null | null—
voicemail_messagestring | null—
detect_voicemailboolean | null—
screening_modenone · amd · human_only · null | null—
answer_timeout_secondsinteger | null—min 5. max 120
max_call_secondsinteger | null—min 10
compliancestring[] | null—
suppression_list_idsstring[] | null—
variablesstring[] | null—
metadatastring[] | null—
webhook_urlstring | null—uri. max 1024 chars
webhook_secretstring | null—max 255 chars
webhook_eventsstring[] | null—
bash
curl -X POST "$CALLAGENT_API_URL/campaigns" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
       "name": "Support Line",
       "target_type": "assistant",
       "assistant_id": "01JQ0000000000000000000000",
       "squad_id": "01JQ0000000000000000000000",
       "origination_type": "phone_number",
       "phone_number_id": "01JQ0000000000000000000000",
       "sip_trunk_id": "01JQ0000000000000000000000"
     }'
js
const response = await fetch(`${process.env.CALLAGENT_API_URL}/campaigns`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
    Accept: 'application/json',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "name": "Support Line",
    "target_type": "assistant",
    "assistant_id": "01JQ0000000000000000000000",
    "squad_id": "01JQ0000000000000000000000",
    "origination_type": "phone_number",
    "phone_number_id": "01JQ0000000000000000000000",
    "sip_trunk_id": "01JQ0000000000000000000000"
  }),
});

if (!response.ok) {
  const { error } = await response.json();
  throw new Error(`${error.type}: ${error.message}`);
}

const { data } = await response.json();
php
<?php

$handle = curl_init(getenv('CALLAGENT_API_URL') . '/campaigns');

curl_setopt_array($handle, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('CALLAGENT_API_KEY'),
        'Accept: application/json',
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
    'name' => 'Support Line',
    'target_type' => 'assistant',
    'assistant_id' => '01JQ0000000000000000000000',
    'squad_id' => '01JQ0000000000000000000000',
    'origination_type' => 'phone_number',
    'phone_number_id' => '01JQ0000000000000000000000',
    'sip_trunk_id' => '01JQ0000000000000000000000',
]),
]);

$body = json_decode(curl_exec($handle), true);
$status = curl_getinfo($handle, CURLINFO_HTTP_CODE);
curl_close($handle);

if ($status >= 400) {
    throw new RuntimeException("{$body['error']['type']}: {$body['error']['message']}");
}

$data = $body['data'];
python
import os, requests

response = requests.post(
    f"{os.environ['CALLAGENT_API_URL']}/campaigns",
    headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
    json={
      "name": "Support Line",
      "target_type": "assistant",
      "assistant_id": "01JQ0000000000000000000000",
      "squad_id": "01JQ0000000000000000000000",
      "origination_type": "phone_number",
      "phone_number_id": "01JQ0000000000000000000000",
      "sip_trunk_id": "01JQ0000000000000000000000"
    },
    timeout=30,
)
response.raise_for_status()
data = response.json()["data"]

Responses

StatusBody
201Refreshed so the presenter reads the columns the database defaulted (counters, window times) rather than nulls.

Errors

StatusWhen it happens
403The API key does not carry the campaign.write scope
422Field validation failed, or the target/origination pair is incomplete

Example response, generated from the schema:

json
{
  "data": {
    "id": "01JQ0000000000000000000000",
    "name": "Support Line",
    "description": "string",
    "status": "string",
    "target_type": "string",
    "assistant_id": "01JQ0000000000000000000000",
    "squad_id": "01JQ0000000000000000000000",
    "origination_type": "string",
    "phone_number_id": "01JQ0000000000000000000000",
    "sip_trunk_id": "01JQ0000000000000000000000",
    "caller_id_number": "+40712345678",
    "timezone": "string",
    "window": {
      "start": "string",
      "end": "string",
      "days": {}
    },
    "starts_at": "2026-01-01T12:00:00+00:00",
    "ends_at": "2026-01-01T12:00:00+00:00",
    "max_concurrency": 1,
    "retry_policy": {},
    "voicemail_behavior": "ops@example.com",
    "detect_voicemail": true,
    "screening_mode": "string",
    "controls": {},
    "suppression_list_ids": {},
    "variables": {},
    "progress": {
      "total": 1,
      "pending": 1,
      "completed": 1,
      "failed": 1,
      "skipped": 1,
      "attempts": 1,
      "percent": 1
    },
    "started_at": "2026-01-01T12:00:00+00:00",
    "completed_at": "2026-01-01T12:00:00+00:00",
    "created_at": "2026-01-01T12:00:00+00:00"
  }
}

Retrieve one campaign with its settings, calling window and progress counters ​

GET/campaigns/{campaignId}
  • Requires campaign.read.
  • The compliance settings are returned under the key controls, deliberately renamed

Path parameters

NameTypeRequiredNotes
campaignIdstringyes
bash
curl -X GET "$CALLAGENT_API_URL/campaigns/01JQ0000000000000000000000" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Accept: application/json"
js
const response = await fetch(`${process.env.CALLAGENT_API_URL}/campaigns/01JQ0000000000000000000000`, {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
    Accept: 'application/json',
  },
});

if (!response.ok) {
  const { error } = await response.json();
  throw new Error(`${error.type}: ${error.message}`);
}

const { data } = await response.json();
php
<?php

$handle = curl_init(getenv('CALLAGENT_API_URL') . '/campaigns/01JQ0000000000000000000000');

curl_setopt_array($handle, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('CALLAGENT_API_KEY'),
        'Accept: application/json',
    ],
]);

$body = json_decode(curl_exec($handle), true);
$status = curl_getinfo($handle, CURLINFO_HTTP_CODE);
curl_close($handle);

if ($status >= 400) {
    throw new RuntimeException("{$body['error']['type']}: {$body['error']['message']}");
}

$data = $body['data'];
python
import os, requests

response = requests.get(
    f"{os.environ['CALLAGENT_API_URL']}/campaigns/01JQ0000000000000000000000",
    headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
    timeout=30,
)
response.raise_for_status()
data = response.json()["data"]

Responses

StatusBody
200data-wrapped object

Errors

StatusWhen it happens
403The API key does not carry the campaign.read scope
404No campaign with that id in this organization

Example response, generated from the schema:

json
{
  "data": {
    "id": "01JQ0000000000000000000000",
    "name": "Support Line",
    "description": "string",
    "status": "string",
    "target_type": "string",
    "assistant_id": "01JQ0000000000000000000000",
    "squad_id": "01JQ0000000000000000000000",
    "origination_type": "string",
    "phone_number_id": "01JQ0000000000000000000000",
    "sip_trunk_id": "01JQ0000000000000000000000",
    "caller_id_number": "+40712345678",
    "timezone": "string",
    "window": {
      "start": "string",
      "end": "string",
      "days": {}
    },
    "starts_at": "2026-01-01T12:00:00+00:00",
    "ends_at": "2026-01-01T12:00:00+00:00",
    "max_concurrency": 1,
    "retry_policy": {},
    "voicemail_behavior": "ops@example.com",
    "detect_voicemail": true,
    "screening_mode": "string",
    "controls": {},
    "suppression_list_ids": {},
    "variables": {},
    "progress": {
      "total": 1,
      "pending": 1,
      "completed": 1,
      "failed": 1,
      "skipped": 1,
      "attempts": 1,
      "percent": 1
    },
    "started_at": "2026-01-01T12:00:00+00:00",
    "completed_at": "2026-01-01T12:00:00+00:00",
    "created_at": "2026-01-01T12:00:00+00:00"
  }
}

Update a campaign's settings, refusing any edit once it has reached a terminal status ​

PATCH/campaigns/{campaignId}
  • Requires campaign.write.
  • No request validation at all: the whole body is passed to the service, which keeps a whitelist and silently drops every other key

Path parameters

NameTypeRequiredNotes
campaignIdstringyes
bash
curl -X PATCH "$CALLAGENT_API_URL/campaigns/01JQ0000000000000000000000" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Accept: application/json"
js
const response = await fetch(`${process.env.CALLAGENT_API_URL}/campaigns/01JQ0000000000000000000000`, {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
    Accept: 'application/json',
  },
});

if (!response.ok) {
  const { error } = await response.json();
  throw new Error(`${error.type}: ${error.message}`);
}

const { data } = await response.json();
php
<?php

$handle = curl_init(getenv('CALLAGENT_API_URL') . '/campaigns/01JQ0000000000000000000000');

curl_setopt_array($handle, [
    CURLOPT_CUSTOMREQUEST => 'PATCH',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('CALLAGENT_API_KEY'),
        'Accept: application/json',
    ],
]);

$body = json_decode(curl_exec($handle), true);
$status = curl_getinfo($handle, CURLINFO_HTTP_CODE);
curl_close($handle);

if ($status >= 400) {
    throw new RuntimeException("{$body['error']['type']}: {$body['error']['message']}");
}

$data = $body['data'];
python
import os, requests

response = requests.patch(
    f"{os.environ['CALLAGENT_API_URL']}/campaigns/01JQ0000000000000000000000",
    headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
    timeout=30,
)
response.raise_for_status()
data = response.json()["data"]

Responses

StatusBody
200data-wrapped object

Errors

StatusWhen it happens
403The API key does not carry the campaign.write scope
404No campaign with that id in this organization
422The campaign is terminal, or the caller id is not valid E.164

Example response, generated from the schema:

json
{
  "data": {
    "id": "01JQ0000000000000000000000",
    "name": "Support Line",
    "description": "string",
    "status": "string",
    "target_type": "string",
    "assistant_id": "01JQ0000000000000000000000",
    "squad_id": "01JQ0000000000000000000000",
    "origination_type": "string",
    "phone_number_id": "01JQ0000000000000000000000",
    "sip_trunk_id": "01JQ0000000000000000000000",
    "caller_id_number": "+40712345678",
    "timezone": "string",
    "window": {
      "start": "string",
      "end": "string",
      "days": {}
    },
    "starts_at": "2026-01-01T12:00:00+00:00",
    "ends_at": "2026-01-01T12:00:00+00:00",
    "max_concurrency": 1,
    "retry_policy": {},
    "voicemail_behavior": "ops@example.com",
    "detect_voicemail": true,
    "screening_mode": "string",
    "controls": {},
    "suppression_list_ids": {},
    "variables": {},
    "progress": {
      "total": 1,
      "pending": 1,
      "completed": 1,
      "failed": 1,
      "skipped": 1,
      "attempts": 1,
      "percent": 1
    },
    "started_at": "2026-01-01T12:00:00+00:00",
    "completed_at": "2026-01-01T12:00:00+00:00",
    "created_at": "2026-01-01T12:00:00+00:00"
  }
}

Delete a campaign and return an empty body ​

DELETE/campaigns/{campaignId}
  • Requires campaign.write.
  • This response is not data-wrapped.
  • Deletes at any status, including a running campaign, with no state check

Path parameters

NameTypeRequiredNotes
campaignIdstringyes
bash
curl -X DELETE "$CALLAGENT_API_URL/campaigns/01JQ0000000000000000000000" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Accept: application/json"
js
const response = await fetch(`${process.env.CALLAGENT_API_URL}/campaigns/01JQ0000000000000000000000`, {
  method: 'DELETE',
  headers: {
    Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
    Accept: 'application/json',
  },
});

if (!response.ok) {
  const { error } = await response.json();
  throw new Error(`${error.type}: ${error.message}`);
}

const { data } = await response.json();
php
<?php

$handle = curl_init(getenv('CALLAGENT_API_URL') . '/campaigns/01JQ0000000000000000000000');

curl_setopt_array($handle, [
    CURLOPT_CUSTOMREQUEST => 'DELETE',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('CALLAGENT_API_KEY'),
        'Accept: application/json',
    ],
]);

$body = json_decode(curl_exec($handle), true);
$status = curl_getinfo($handle, CURLINFO_HTTP_CODE);
curl_close($handle);

if ($status >= 400) {
    throw new RuntimeException("{$body['error']['type']}: {$body['error']['message']}");
}

$data = $body['data'];
python
import os, requests

response = requests.delete(
    f"{os.environ['CALLAGENT_API_URL']}/campaigns/01JQ0000000000000000000000",
    headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
    timeout=30,
)
response.raise_for_status()
data = response.json()["data"]

Responses

StatusBody
204No content

Errors

StatusWhen it happens
403The API key does not carry the campaign.write scope
404No campaign with that id in this organization

Cancel a campaign, cancelling every undialled contact and queued attempt, and return it ​

POST/campaigns/{campaignId}/cancel
  • Requires campaign.write.
  • Optional body: reason, farewell, and endLiveCalls — camelCase on a snake_case API — which also hangs up calls already in progress

Path parameters

NameTypeRequiredNotes
campaignIdstringyes

Request body

FieldTypeRequiredNotes
reasonstring—
endLiveCallsboolean—default false
bash
curl -X POST "$CALLAGENT_API_URL/campaigns/01JQ0000000000000000000000/cancel" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
       "endLiveCalls": false
     }'
js
const response = await fetch(`${process.env.CALLAGENT_API_URL}/campaigns/01JQ0000000000000000000000/cancel`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
    Accept: 'application/json',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "endLiveCalls": false
  }),
});

if (!response.ok) {
  const { error } = await response.json();
  throw new Error(`${error.type}: ${error.message}`);
}

const { data } = await response.json();
php
<?php

$handle = curl_init(getenv('CALLAGENT_API_URL') . '/campaigns/01JQ0000000000000000000000/cancel');

curl_setopt_array($handle, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('CALLAGENT_API_KEY'),
        'Accept: application/json',
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
    'endLiveCalls' => false,
]),
]);

$body = json_decode(curl_exec($handle), true);
$status = curl_getinfo($handle, CURLINFO_HTTP_CODE);
curl_close($handle);

if ($status >= 400) {
    throw new RuntimeException("{$body['error']['type']}: {$body['error']['message']}");
}

$data = $body['data'];
python
import os, requests

response = requests.post(
    f"{os.environ['CALLAGENT_API_URL']}/campaigns/01JQ0000000000000000000000/cancel",
    headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
    json={
      "endLiveCalls": False
    },
    timeout=30,
)
response.raise_for_status()
data = response.json()["data"]

Responses

StatusBody
200data-wrapped object

Errors

StatusWhen it happens
403The API key does not carry the campaign.write scope
404No campaign with that id in this organization
422The campaign cannot move from its current status to cancelled

Example response, generated from the schema:

json
{
  "data": {
    "id": "01JQ0000000000000000000000",
    "name": "Support Line",
    "description": "string",
    "status": "string",
    "target_type": "string",
    "assistant_id": "01JQ0000000000000000000000",
    "squad_id": "01JQ0000000000000000000000",
    "origination_type": "string",
    "phone_number_id": "01JQ0000000000000000000000",
    "sip_trunk_id": "01JQ0000000000000000000000",
    "caller_id_number": "+40712345678",
    "timezone": "string",
    "window": {
      "start": "string",
      "end": "string",
      "days": {}
    },
    "starts_at": "2026-01-01T12:00:00+00:00",
    "ends_at": "2026-01-01T12:00:00+00:00",
    "max_concurrency": 1,
    "retry_policy": {},
    "voicemail_behavior": "ops@example.com",
    "detect_voicemail": true,
    "screening_mode": "string",
    "controls": {},
    "suppression_list_ids": {},
    "variables": {},
    "progress": {
      "total": 1,
      "pending": 1,
      "completed": 1,
      "failed": 1,
      "skipped": 1,
      "attempts": 1,
      "percent": 1
    },
    "started_at": "2026-01-01T12:00:00+00:00",
    "completed_at": "2026-01-01T12:00:00+00:00",
    "created_at": "2026-01-01T12:00:00+00:00"
  }
}

List a campaign's contacts with their state, attempt count, next attempt and last outcome ​

GET/campaigns/{campaignId}/contacts
  • Requires campaign.read.
  • Page size comes from limit (default 50, max 500)

Path parameters

NameTypeRequiredNotes
campaignIdstringyes
bash
curl -X GET "$CALLAGENT_API_URL/campaigns/01JQ0000000000000000000000/contacts" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Accept: application/json"
js
const response = await fetch(`${process.env.CALLAGENT_API_URL}/campaigns/01JQ0000000000000000000000/contacts`, {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
    Accept: 'application/json',
  },
});

if (!response.ok) {
  const { error } = await response.json();
  throw new Error(`${error.type}: ${error.message}`);
}

const { data } = await response.json();
php
<?php

$handle = curl_init(getenv('CALLAGENT_API_URL') . '/campaigns/01JQ0000000000000000000000/contacts');

curl_setopt_array($handle, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('CALLAGENT_API_KEY'),
        'Accept: application/json',
    ],
]);

$body = json_decode(curl_exec($handle), true);
$status = curl_getinfo($handle, CURLINFO_HTTP_CODE);
curl_close($handle);

if ($status >= 400) {
    throw new RuntimeException("{$body['error']['type']}: {$body['error']['message']}");
}

$data = $body['data'];
python
import os, requests

response = requests.get(
    f"{os.environ['CALLAGENT_API_URL']}/campaigns/01JQ0000000000000000000000/contacts",
    headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
    timeout=30,
)
response.raise_for_status()
data = response.json()["data"]

Responses

StatusBody
200data-wrapped array

Errors

StatusWhen it happens
403The API key does not carry the campaign.read scope
404No campaign with that id in this organization

Example response, generated from the schema:

json
{
  "data": [
    {
      "id": "01JQ0000000000000000000000",
      "phone_number": "+40712345678",
      "name": "Support Line",
      "email": "ops@example.com",
      "external_id": "01JQ0000000000000000000000",
      "country": "string",
      "timezone": "string",
      "state": "string",
      "state_reason": "string",
      "state_detail": "string",
      "attempts": 1,
      "last_attempt_at": "2026-01-01T12:00:00+00:00",
      "next_attempt_at": "2026-01-01T12:00:00+00:00",
      "last_outcome": "string",
      "last_call_id": "01JQ0000000000000000000000",
      "variables": {}
    }
  ],
  "meta": {
    "total": 1,
    "per_page": 25
  }
}

Add up to 5000 contacts inline and return the imported, duplicate, invalid and suppressed tally ​

POST/campaigns/{campaignId}/contacts
  • Requires campaign.write.
  • Each contact's number is written as phone on the request but read back as phone_number on the response. Row errors are capped at 500 with errors_truncated set.

Path parameters

NameTypeRequiredNotes
campaignIdstringyes

Request body

FieldTypeRequiredNotes
contactsobject[]yesmin 1 items. max 5000 items
default_countrystring | null—max 8 chars
default_timezonestring | null—
skip_suppressedboolean | null—
bash
curl -X POST "$CALLAGENT_API_URL/campaigns/01JQ0000000000000000000000/contacts" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
       "contacts": [
         {
           "phone": "+40712345678",
           "name": "Support Line",
           "email": "ops@example.com",
           "timezone": "string",
           "country": "string",
           "external_id": "01JQ0000000000000000000000",
           "variables": [],
           "consent": []
         }
       ],
       "skip_suppressed": true
     }'
js
const response = await fetch(`${process.env.CALLAGENT_API_URL}/campaigns/01JQ0000000000000000000000/contacts`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
    Accept: 'application/json',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "contacts": [
      {
        "phone": "+40712345678",
        "name": "Support Line",
        "email": "ops@example.com",
        "timezone": "string",
        "country": "string",
        "external_id": "01JQ0000000000000000000000",
        "variables": [],
        "consent": []
      }
    ],
    "skip_suppressed": true
  }),
});

if (!response.ok) {
  const { error } = await response.json();
  throw new Error(`${error.type}: ${error.message}`);
}

const { data } = await response.json();
php
<?php

$handle = curl_init(getenv('CALLAGENT_API_URL') . '/campaigns/01JQ0000000000000000000000/contacts');

curl_setopt_array($handle, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('CALLAGENT_API_KEY'),
        'Accept: application/json',
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
    'contacts' => [
        [
            'phone' => '+40712345678',
            'name' => 'Support Line',
            'email' => 'ops@example.com',
            'timezone' => 'string',
            'country' => 'string',
            'external_id' => '01JQ0000000000000000000000',
            'variables' => [],
            'consent' => [],
        ],
    ],
    'skip_suppressed' => true,
]),
]);

$body = json_decode(curl_exec($handle), true);
$status = curl_getinfo($handle, CURLINFO_HTTP_CODE);
curl_close($handle);

if ($status >= 400) {
    throw new RuntimeException("{$body['error']['type']}: {$body['error']['message']}");
}

$data = $body['data'];
python
import os, requests

response = requests.post(
    f"{os.environ['CALLAGENT_API_URL']}/campaigns/01JQ0000000000000000000000/contacts",
    headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
    json={
      "contacts": [
        {
          "phone": "+40712345678",
          "name": "Support Line",
          "email": "ops@example.com",
          "timezone": "string",
          "country": "string",
          "external_id": "01JQ0000000000000000000000",
          "variables": [],
          "consent": []
        }
      ],
      "skip_suppressed": True
    },
    timeout=30,
)
response.raise_for_status()
data = response.json()["data"]

Responses

StatusBody
201data-wrapped object

Errors

StatusWhen it happens
403The API key does not carry the campaign.write scope
404No campaign with that id in this organization
422Validation failed on the contacts array or an individual contact

Example response, generated from the schema:

json
{
  "data": {
    "batch_id": "01JQ0000000000000000000000",
    "read": 1,
    "imported": 1,
    "duplicates": 1,
    "invalid": 1,
    "suppressed": 1,
    "errors": [
      "string"
    ],
    "errors_truncated": true
  }
}

Parse an uploaded CSV's first 10 rows and return the header, sample, suggested mapping and row errors ​

POST/campaigns/{campaignId}/contacts/preview
  • Requires campaign.read.
  • column_map and default_country shape the returned errors but are not validated. An empty file returns empty header/rows rather than an error.

Path parameters

NameTypeRequiredNotes
campaignIdstringyes

Request body

FieldTypeRequiredNotes
filestringyesMaximum file size: 204800 kilobytes.. binary
bash
curl -X POST "$CALLAGENT_API_URL/campaigns/01JQ0000000000000000000000/contacts/preview" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
       "file": "string"
     }'
js
const response = await fetch(`${process.env.CALLAGENT_API_URL}/campaigns/01JQ0000000000000000000000/contacts/preview`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
    Accept: 'application/json',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "file": "string"
  }),
});

if (!response.ok) {
  const { error } = await response.json();
  throw new Error(`${error.type}: ${error.message}`);
}

const { data } = await response.json();
php
<?php

$handle = curl_init(getenv('CALLAGENT_API_URL') . '/campaigns/01JQ0000000000000000000000/contacts/preview');

curl_setopt_array($handle, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('CALLAGENT_API_KEY'),
        'Accept: application/json',
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
    'file' => 'string',
]),
]);

$body = json_decode(curl_exec($handle), true);
$status = curl_getinfo($handle, CURLINFO_HTTP_CODE);
curl_close($handle);

if ($status >= 400) {
    throw new RuntimeException("{$body['error']['type']}: {$body['error']['message']}");
}

$data = $body['data'];
python
import os, requests

response = requests.post(
    f"{os.environ['CALLAGENT_API_URL']}/campaigns/01JQ0000000000000000000000/contacts/preview",
    headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
    json={
      "file": "string"
    },
    timeout=30,
)
response.raise_for_status()
data = response.json()["data"]

Responses

StatusBody
200data-wrapped object

Errors

StatusWhen it happens
403The API key does not carry the campaign.read scope
404No campaign with that id in this organization
422No file was uploaded or it exceeds 200 MB

Example response, generated from the schema:

json
{
  "data": "string"
}

Import a CSV of contacts into the campaign and return the batch tally with per-row errors ​

POST/campaigns/{campaignId}/contacts/upload
  • Requires campaign.write.
  • Multipart upload, max 200 MB. skip_suppressed defaults to true here even though it is optional; row errors are capped at 500.

Path parameters

NameTypeRequiredNotes
campaignIdstringyes

Request body

FieldTypeRequiredNotes
filestringyesMaximum file size: 204800 kilobytes.. binary
column_mapstring[] | null—
default_countrystring | null—max 8 chars
default_timezonestring | null—
skip_suppressedboolean | null—
delimiterstring | null—max 1 chars. min 1 chars
bash
curl -X POST "$CALLAGENT_API_URL/campaigns/01JQ0000000000000000000000/contacts/upload" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
       "file": "string",
       "skip_suppressed": true
     }'
js
const response = await fetch(`${process.env.CALLAGENT_API_URL}/campaigns/01JQ0000000000000000000000/contacts/upload`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
    Accept: 'application/json',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "file": "string",
    "skip_suppressed": true
  }),
});

if (!response.ok) {
  const { error } = await response.json();
  throw new Error(`${error.type}: ${error.message}`);
}

const { data } = await response.json();
php
<?php

$handle = curl_init(getenv('CALLAGENT_API_URL') . '/campaigns/01JQ0000000000000000000000/contacts/upload');

curl_setopt_array($handle, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('CALLAGENT_API_KEY'),
        'Accept: application/json',
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
    'file' => 'string',
    'skip_suppressed' => true,
]),
]);

$body = json_decode(curl_exec($handle), true);
$status = curl_getinfo($handle, CURLINFO_HTTP_CODE);
curl_close($handle);

if ($status >= 400) {
    throw new RuntimeException("{$body['error']['type']}: {$body['error']['message']}");
}

$data = $body['data'];
python
import os, requests

response = requests.post(
    f"{os.environ['CALLAGENT_API_URL']}/campaigns/01JQ0000000000000000000000/contacts/upload",
    headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
    json={
      "file": "string",
      "skip_suppressed": True
    },
    timeout=30,
)
response.raise_for_status()
data = response.json()["data"]

Responses

StatusBody
201data-wrapped object

Errors

StatusWhen it happens
403The API key does not carry the campaign.write scope
404No campaign with that id in this organization
422Bad upload, an empty CSV, or no column mappable to a phone number

Example response, generated from the schema:

json
{
  "data": {
    "batch_id": "01JQ0000000000000000000000",
    "read": 1,
    "imported": 1,
    "duplicates": 1,
    "invalid": 1,
    "suppressed": 1,
    "errors": [
      "string"
    ],
    "errors_truncated": true
  }
}

Pause a campaign, halting new dials and returning claimed-but-undialled contacts to the queue ​

POST/campaigns/{campaignId}/pause
  • Requires campaign.write.
  • Calls already connected are left alone unless the body sets endLiveCalls — camelCase on a snake_case API — optionally with a farewell line

Path parameters

NameTypeRequiredNotes
campaignIdstringyes

Request body

FieldTypeRequiredNotes
reasonstring—
endLiveCallsboolean—default false
bash
curl -X POST "$CALLAGENT_API_URL/campaigns/01JQ0000000000000000000000/pause" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
       "endLiveCalls": false
     }'
js
const response = await fetch(`${process.env.CALLAGENT_API_URL}/campaigns/01JQ0000000000000000000000/pause`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
    Accept: 'application/json',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "endLiveCalls": false
  }),
});

if (!response.ok) {
  const { error } = await response.json();
  throw new Error(`${error.type}: ${error.message}`);
}

const { data } = await response.json();
php
<?php

$handle = curl_init(getenv('CALLAGENT_API_URL') . '/campaigns/01JQ0000000000000000000000/pause');

curl_setopt_array($handle, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('CALLAGENT_API_KEY'),
        'Accept: application/json',
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
    'endLiveCalls' => false,
]),
]);

$body = json_decode(curl_exec($handle), true);
$status = curl_getinfo($handle, CURLINFO_HTTP_CODE);
curl_close($handle);

if ($status >= 400) {
    throw new RuntimeException("{$body['error']['type']}: {$body['error']['message']}");
}

$data = $body['data'];
python
import os, requests

response = requests.post(
    f"{os.environ['CALLAGENT_API_URL']}/campaigns/01JQ0000000000000000000000/pause",
    headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
    json={
      "endLiveCalls": False
    },
    timeout=30,
)
response.raise_for_status()
data = response.json()["data"]

Responses

StatusBody
200data-wrapped object

Errors

StatusWhen it happens
403The API key does not carry the campaign.write scope
404No campaign with that id in this organization
422The campaign cannot move from its current status to paused

Example response, generated from the schema:

json
{
  "data": {
    "id": "01JQ0000000000000000000000",
    "name": "Support Line",
    "description": "string",
    "status": "string",
    "target_type": "string",
    "assistant_id": "01JQ0000000000000000000000",
    "squad_id": "01JQ0000000000000000000000",
    "origination_type": "string",
    "phone_number_id": "01JQ0000000000000000000000",
    "sip_trunk_id": "01JQ0000000000000000000000",
    "caller_id_number": "+40712345678",
    "timezone": "string",
    "window": {
      "start": "string",
      "end": "string",
      "days": {}
    },
    "starts_at": "2026-01-01T12:00:00+00:00",
    "ends_at": "2026-01-01T12:00:00+00:00",
    "max_concurrency": 1,
    "retry_policy": {},
    "voicemail_behavior": "ops@example.com",
    "detect_voicemail": true,
    "screening_mode": "string",
    "controls": {},
    "suppression_list_ids": {},
    "variables": {},
    "progress": {
      "total": 1,
      "pending": 1,
      "completed": 1,
      "failed": 1,
      "skipped": 1,
      "attempts": 1,
      "percent": 1
    },
    "started_at": "2026-01-01T12:00:00+00:00",
    "completed_at": "2026-01-01T12:00:00+00:00",
    "created_at": "2026-01-01T12:00:00+00:00"
  }
}

Return a campaign's live progress: status, totals, attempts, percent complete and counts by contact state ​

GET/campaigns/{campaignId}/progress
  • Requires campaign.read.

Path parameters

NameTypeRequiredNotes
campaignIdstringyes
bash
curl -X GET "$CALLAGENT_API_URL/campaigns/01JQ0000000000000000000000/progress" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Accept: application/json"
js
const response = await fetch(`${process.env.CALLAGENT_API_URL}/campaigns/01JQ0000000000000000000000/progress`, {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
    Accept: 'application/json',
  },
});

if (!response.ok) {
  const { error } = await response.json();
  throw new Error(`${error.type}: ${error.message}`);
}

const { data } = await response.json();
php
<?php

$handle = curl_init(getenv('CALLAGENT_API_URL') . '/campaigns/01JQ0000000000000000000000/progress');

curl_setopt_array($handle, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('CALLAGENT_API_KEY'),
        'Accept: application/json',
    ],
]);

$body = json_decode(curl_exec($handle), true);
$status = curl_getinfo($handle, CURLINFO_HTTP_CODE);
curl_close($handle);

if ($status >= 400) {
    throw new RuntimeException("{$body['error']['type']}: {$body['error']['message']}");
}

$data = $body['data'];
python
import os, requests

response = requests.get(
    f"{os.environ['CALLAGENT_API_URL']}/campaigns/01JQ0000000000000000000000/progress",
    headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
    timeout=30,
)
response.raise_for_status()
data = response.json()["data"]

Responses

StatusBody
200data-wrapped object

Errors

StatusWhen it happens
403The API key does not carry the campaign.read scope
404No campaign with that id in this organization

Example response, generated from the schema:

json
{
  "data": {
    "status": "string",
    "total": 1,
    "attempts": 1,
    "percent": 1,
    "states": {},
    "started_at": "2026-01-01T12:00:00+00:00",
    "completed_at": "2026-01-01T12:00:00+00:00"
  }
}

Stream every contact's outcome as a CSV download so a finished 500k-contact campaign never buffers ​

GET/campaigns/{campaignId}/results
  • Requires campaign.read.
  • This response is not data-wrapped.
  • Responds with text/csv as a file attachment, not JSON. Columns are fixed and include last_ended_reason, which the contacts list endpoint does not return.

Path parameters

NameTypeRequiredNotes
campaignIdstringyes
bash
curl -X GET "$CALLAGENT_API_URL/campaigns/01JQ0000000000000000000000/results" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Accept: application/json"
js
const response = await fetch(`${process.env.CALLAGENT_API_URL}/campaigns/01JQ0000000000000000000000/results`, {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
    Accept: 'application/json',
  },
});

if (!response.ok) {
  const { error } = await response.json();
  throw new Error(`${error.type}: ${error.message}`);
}

const { data } = await response.json();
php
<?php

$handle = curl_init(getenv('CALLAGENT_API_URL') . '/campaigns/01JQ0000000000000000000000/results');

curl_setopt_array($handle, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('CALLAGENT_API_KEY'),
        'Accept: application/json',
    ],
]);

$body = json_decode(curl_exec($handle), true);
$status = curl_getinfo($handle, CURLINFO_HTTP_CODE);
curl_close($handle);

if ($status >= 400) {
    throw new RuntimeException("{$body['error']['type']}: {$body['error']['message']}");
}

$data = $body['data'];
python
import os, requests

response = requests.get(
    f"{os.environ['CALLAGENT_API_URL']}/campaigns/01JQ0000000000000000000000/results",
    headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
    timeout=30,
)
response.raise_for_status()
data = response.json()["data"]

Responses

StatusBody
200Not JSON — text/csv

Errors

StatusWhen it happens
403The API key does not carry the campaign.read scope
404No campaign with that id in this organization

Resume a paused campaign, clear its stop signal and re-dispatch the dialler ​

POST/campaigns/{campaignId}/resume
  • Requires campaign.write.

Path parameters

NameTypeRequiredNotes
campaignIdstringyes
bash
curl -X POST "$CALLAGENT_API_URL/campaigns/01JQ0000000000000000000000/resume" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Accept: application/json"
js
const response = await fetch(`${process.env.CALLAGENT_API_URL}/campaigns/01JQ0000000000000000000000/resume`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
    Accept: 'application/json',
  },
});

if (!response.ok) {
  const { error } = await response.json();
  throw new Error(`${error.type}: ${error.message}`);
}

const { data } = await response.json();
php
<?php

$handle = curl_init(getenv('CALLAGENT_API_URL') . '/campaigns/01JQ0000000000000000000000/resume');

curl_setopt_array($handle, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('CALLAGENT_API_KEY'),
        'Accept: application/json',
    ],
]);

$body = json_decode(curl_exec($handle), true);
$status = curl_getinfo($handle, CURLINFO_HTTP_CODE);
curl_close($handle);

if ($status >= 400) {
    throw new RuntimeException("{$body['error']['type']}: {$body['error']['message']}");
}

$data = $body['data'];
python
import os, requests

response = requests.post(
    f"{os.environ['CALLAGENT_API_URL']}/campaigns/01JQ0000000000000000000000/resume",
    headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
    timeout=30,
)
response.raise_for_status()
data = response.json()["data"]

Responses

StatusBody
200data-wrapped object

Errors

StatusWhen it happens
403The API key does not carry the campaign.write scope
404No campaign with that id in this organization
422The campaign cannot move from its current status to running

Example response, generated from the schema:

json
{
  "data": {
    "id": "01JQ0000000000000000000000",
    "name": "Support Line",
    "description": "string",
    "status": "string",
    "target_type": "string",
    "assistant_id": "01JQ0000000000000000000000",
    "squad_id": "01JQ0000000000000000000000",
    "origination_type": "string",
    "phone_number_id": "01JQ0000000000000000000000",
    "sip_trunk_id": "01JQ0000000000000000000000",
    "caller_id_number": "+40712345678",
    "timezone": "string",
    "window": {
      "start": "string",
      "end": "string",
      "days": {}
    },
    "starts_at": "2026-01-01T12:00:00+00:00",
    "ends_at": "2026-01-01T12:00:00+00:00",
    "max_concurrency": 1,
    "retry_policy": {},
    "voicemail_behavior": "ops@example.com",
    "detect_voicemail": true,
    "screening_mode": "string",
    "controls": {},
    "suppression_list_ids": {},
    "variables": {},
    "progress": {
      "total": 1,
      "pending": 1,
      "completed": 1,
      "failed": 1,
      "skipped": 1,
      "attempts": 1,
      "percent": 1
    },
    "started_at": "2026-01-01T12:00:00+00:00",
    "completed_at": "2026-01-01T12:00:00+00:00",
    "created_at": "2026-01-01T12:00:00+00:00"
  }
}

Start a campaign, refusing one with no contacts, no target, or a recording policy it cannot announce ​

POST/campaigns/{campaignId}/start
  • Requires campaign.write.
  • Returns status scheduled rather than running when starts_at is in the future; the scheduler picks it up later

Path parameters

NameTypeRequiredNotes
campaignIdstringyes
bash
curl -X POST "$CALLAGENT_API_URL/campaigns/01JQ0000000000000000000000/start" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Accept: application/json"
js
const response = await fetch(`${process.env.CALLAGENT_API_URL}/campaigns/01JQ0000000000000000000000/start`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
    Accept: 'application/json',
  },
});

if (!response.ok) {
  const { error } = await response.json();
  throw new Error(`${error.type}: ${error.message}`);
}

const { data } = await response.json();
php
<?php

$handle = curl_init(getenv('CALLAGENT_API_URL') . '/campaigns/01JQ0000000000000000000000/start');

curl_setopt_array($handle, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('CALLAGENT_API_KEY'),
        'Accept: application/json',
    ],
]);

$body = json_decode(curl_exec($handle), true);
$status = curl_getinfo($handle, CURLINFO_HTTP_CODE);
curl_close($handle);

if ($status >= 400) {
    throw new RuntimeException("{$body['error']['type']}: {$body['error']['message']}");
}

$data = $body['data'];
python
import os, requests

response = requests.post(
    f"{os.environ['CALLAGENT_API_URL']}/campaigns/01JQ0000000000000000000000/start",
    headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
    timeout=30,
)
response.raise_for_status()
data = response.json()["data"]

Responses

StatusBody
200data-wrapped object

Errors

StatusWhen it happens
403The API key does not carry the campaign.write scope
404No campaign with that id in this organization
422No contacts, no assistant or squad, unannounceable consent, or illegal transition

Example response, generated from the schema:

json
{
  "data": {
    "id": "01JQ0000000000000000000000",
    "name": "Support Line",
    "description": "string",
    "status": "string",
    "target_type": "string",
    "assistant_id": "01JQ0000000000000000000000",
    "squad_id": "01JQ0000000000000000000000",
    "origination_type": "string",
    "phone_number_id": "01JQ0000000000000000000000",
    "sip_trunk_id": "01JQ0000000000000000000000",
    "caller_id_number": "+40712345678",
    "timezone": "string",
    "window": {
      "start": "string",
      "end": "string",
      "days": {}
    },
    "starts_at": "2026-01-01T12:00:00+00:00",
    "ends_at": "2026-01-01T12:00:00+00:00",
    "max_concurrency": 1,
    "retry_policy": {},
    "voicemail_behavior": "ops@example.com",
    "detect_voicemail": true,
    "screening_mode": "string",
    "controls": {},
    "suppression_list_ids": {},
    "variables": {},
    "progress": {
      "total": 1,
      "pending": 1,
      "completed": 1,
      "failed": 1,
      "skipped": 1,
      "attempts": 1,
      "percent": 1
    },
    "started_at": "2026-01-01T12:00:00+00:00",
    "completed_at": "2026-01-01T12:00:00+00:00",
    "created_at": "2026-01-01T12:00:00+00:00"
  }
}

Intended CSV preview, but the collection route is unreachable because the handler demands a campaign id ​

POST/campaigns/preview-contacts
  • Requires campaign.read.
  • Broken route: CampaignController::previewContacts requires a $campaignId, and this path supplies none, so every call raises ArgumentCountError (500). Use /campaigns/{campaignId}/contacts/preview.

Request body

FieldTypeRequiredNotes
filestringyesMaximum file size: 204800 kilobytes.. binary
default_countrystring—
bash
curl -X POST "$CALLAGENT_API_URL/campaigns/preview-contacts" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
       "file": "string"
     }'
js
const response = await fetch(`${process.env.CALLAGENT_API_URL}/campaigns/preview-contacts`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
    Accept: 'application/json',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "file": "string"
  }),
});

if (!response.ok) {
  const { error } = await response.json();
  throw new Error(`${error.type}: ${error.message}`);
}

const { data } = await response.json();
php
<?php

$handle = curl_init(getenv('CALLAGENT_API_URL') . '/campaigns/preview-contacts');

curl_setopt_array($handle, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('CALLAGENT_API_KEY'),
        'Accept: application/json',
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
    'file' => 'string',
]),
]);

$body = json_decode(curl_exec($handle), true);
$status = curl_getinfo($handle, CURLINFO_HTTP_CODE);
curl_close($handle);

if ($status >= 400) {
    throw new RuntimeException("{$body['error']['type']}: {$body['error']['message']}");
}

$data = $body['data'];
python
import os, requests

response = requests.post(
    f"{os.environ['CALLAGENT_API_URL']}/campaigns/preview-contacts",
    headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
    json={
      "file": "string"
    },
    timeout=30,
)
response.raise_for_status()
data = response.json()["data"]

Responses

StatusBody
200data-wrapped object

Errors

StatusWhen it happens
422Validation error

Example response, generated from the schema:

json
{
  "data": "string"
}

Documented against the code. Anything not yet implemented is labelled, never described as working.