curl --request POST \
--url https://{host}/api/v1/campaigns/{id}/contacts \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"contact_ids": [
"3c90c3cc-0d44-4b50-8888-8dd25736052a"
],
"items": [
{
"contact_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"e164": "<string>",
"name": "<string>",
"reference": "<string>",
"variables": {}
}
],
"segment_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
'const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
contact_ids: ['3c90c3cc-0d44-4b50-8888-8dd25736052a'],
items: [
{
contact_id: '3c90c3cc-0d44-4b50-8888-8dd25736052a',
e164: '<string>',
name: '<string>',
reference: '<string>',
variables: {}
}
],
segment_id: '3c90c3cc-0d44-4b50-8888-8dd25736052a'
})
};
fetch('https://{host}/api/v1/campaigns/{id}/contacts', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://{host}/api/v1/campaigns/{id}/contacts"
payload = {
"contact_ids": ["3c90c3cc-0d44-4b50-8888-8dd25736052a"],
"items": [
{
"contact_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"e164": "<string>",
"name": "<string>",
"reference": "<string>",
"variables": {}
}
],
"segment_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"added": 123,
"offered": 123,
"suppressed": 123
}{
"error": {
"code": "invalid_request",
"message": "<string>"
}
}{
"error": {
"code": "invalid_request",
"message": "<string>"
}
}{
"error": {
"code": "invalid_request",
"message": "<string>"
}
}{
"error": {
"code": "invalid_request",
"message": "<string>"
}
}Load contacts into a campaign
Takes contact ids, not numbers: a campaign must not conjure customer records as a side effect of being filled in. Idempotent — a contact already on the list is skipped. At most 10,000 per request.
curl --request POST \
--url https://{host}/api/v1/campaigns/{id}/contacts \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"contact_ids": [
"3c90c3cc-0d44-4b50-8888-8dd25736052a"
],
"items": [
{
"contact_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"e164": "<string>",
"name": "<string>",
"reference": "<string>",
"variables": {}
}
],
"segment_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
'const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
contact_ids: ['3c90c3cc-0d44-4b50-8888-8dd25736052a'],
items: [
{
contact_id: '3c90c3cc-0d44-4b50-8888-8dd25736052a',
e164: '<string>',
name: '<string>',
reference: '<string>',
variables: {}
}
],
segment_id: '3c90c3cc-0d44-4b50-8888-8dd25736052a'
})
};
fetch('https://{host}/api/v1/campaigns/{id}/contacts', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://{host}/api/v1/campaigns/{id}/contacts"
payload = {
"contact_ids": ["3c90c3cc-0d44-4b50-8888-8dd25736052a"],
"items": [
{
"contact_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"e164": "<string>",
"name": "<string>",
"reference": "<string>",
"variables": {}
}
],
"segment_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"added": 123,
"offered": 123,
"suppressed": 123
}{
"error": {
"code": "invalid_request",
"message": "<string>"
}
}{
"error": {
"code": "invalid_request",
"message": "<string>"
}
}{
"error": {
"code": "invalid_request",
"message": "<string>"
}
}{
"error": {
"code": "invalid_request",
"message": "<string>"
}
}Authorizations
Every request sends Authorization: Bearer <token>. The token is either a panel session (a JWT from /auth/login, 12 hours) or an API key ft_<id>_<secret>. An API key is accepted only from an address on its IP allowlist (403 ip_not_allowed otherwise; 403 ip_allowlist_required for an old key that has none), is limited to its rate per minute (429 rate_limited with Retry-After; X-RateLimit-Limit/Remaining/Reset on every response), and at most 60 call placements a minute.
Headers
Any string up to 255 characters. A retry with the same key and the same body gets the first answer again (with Idempotent-Replayed: true) instead of doing it twice; the same key with a different body is 409 idempotency_mismatch; while the first is still running, 409 idempotency_in_progress. Kept 24 hours.
255Path Parameters
Body
Send one of items, contact_ids or segment_id.
10000People one by one, each with your reference and variables. A number with no contact creates one (contact.created). variables are the call's own ({{name}} in the message or workflow, and an AI agent's brief) and come back with the result.
10000Show child attributes
Show child attributes
Load everyone in this segment. A COPY, not a reference: the campaign's list is a state machine recording whether each person has been dialled, so a list that read a segment live would change under a running campaign and could re-offer somebody already called. There is no size limit on this form, because the work happens in the database.
Response
OK
How many ids were sent. Zero when loading from a segment.
How many on the list will never be dialled -- do-not-call or blacklisted. Reported here rather than left to be found on the call sheet: it is the difference between the list somebody chose and the calls that will happen.