API reference
Campaigns
Outbound runs: one agent, one number and a queue of contacts. Create a campaign, add contacts, start and stop it, remove queued calls and retry.
/v1/campaignsPOST/v1/campaignsGET/v1/campaigns/{campaignId}PATCH/v1/campaigns/{campaignId}DELETE/v1/campaigns/{campaignId}POST/v1/campaigns/{campaignId}/contactsDELETE/v1/campaigns/{campaignId}/callsPOST/v1/campaigns/{campaignId}/retryList campaigns
/v1/campaignsList all campaigns for the authenticated tenant with pagination and optional filtering.
Authorization
AuthorizationstringrequiredBearer <API key>, a key created at app.callshift.ai/api-keys.
Query parameters
pageintegerlimitintegernamestringagentIdstring<uuid>Responses
Campaigns listed successfully
dataobject[]requiredShow child attributes
idstring<uuid>requirednamestringrequiredstatusenum<string>requiredactiveinactivephoneIdstring<uuid>requiredagentobjectrequiredShow child attributes
idstring<uuid>requirednamestringrequiredgenderenum<string>requiredFEMALEMALEpageCountnumberrequiredUnauthorized
errorstringrequiredInternal server error
errorstringrequiredcurl "https://api.callshift.ai/v1/campaigns?agentId=550e8400-e29b-41d4-a716-446655440000" \
-H "Authorization: Bearer $CALLSHIFT_API_KEY"const res = await fetch("https://api.callshift.ai/v1/campaigns?agentId=550e8400-e29b-41d4-a716-446655440000", {
headers: {
Authorization: `Bearer ${process.env.CALLSHIFT_API_KEY}`,
},
});
const data = await res.json();import os
import requests
res = requests.get(
"https://api.callshift.ai/v1/campaigns?agentId=550e8400-e29b-41d4-a716-446655440000",
headers={"Authorization": f"Bearer {os.environ['CALLSHIFT_API_KEY']}"},
)
print(res.json()){
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Q2 Outreach",
"status": "active",
"phoneId": "0d3a7e6b-2b8e-4c5b-9c4e-5f4b9d2a1c8e",
"agent": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Alice",
"gender": "FEMALE"
}
}
],
"pageCount": 3
}{
"error": "Authentication error"
}{
"error": "Internal server error"
}Create a campaign
/v1/campaignsCreate a new outbound campaign for the authenticated tenant. The campaign is inactive unless isActive is set to true.
Authorization
AuthorizationstringrequiredBearer <API key>, a key created at app.callshift.ai/api-keys.
Body application/json
namestringrequiredagentIdstring<uuid>requiredphoneIdstring<uuid>requiredThe phone the campaign dials from. It must be allowed to place outbound calls (isOutboundAllowed in GET /phones).
isActivebooleanWhether the campaign starts active. Defaults to false: the campaign is created inactive and dials nothing until it is activated, either here or with PATCH /campaigns/{campaignId}. Set to true to have contacts added afterwards dispatched as soon as they arrive.
declinedStopRationumberThe campaign is stopped automatically once this share of its recent calls were declined as soon as they were placed, which usually means the carrier is blocking the number. Recent calls are its last 40 ended in the last 10 minutes, counted once at least 30 have ended. Between 0.35 (14 of 40 calls) and 0.7 (28 of 40 calls); defaults to 0.5 (20 of 40 calls). An advanced setting: an account it is not enabled on gets a 403 and keeps the default. Contact your sales representative to enable it.
Responses
Campaign created successfully
idstring<uuid>requiredInvalid agent or phone, or a phone not allowed to place outbound calls
errorstringrequiredUnauthorized
errorstringrequireddeclinedStopRatio is an advanced setting that is not enabled on the account
errorstringrequiredThe change would link items of two different entities. The error names them.
errorstringrequiredInternal server error
errorstringrequiredcurl -X POST "https://api.callshift.ai/v1/campaigns" \
-H "Authorization: Bearer $CALLSHIFT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Q2 Outreach",
"agentId": "550e8400-e29b-41d4-a716-446655440000",
"phoneId": "0d3a7e6b-2b8e-4c5b-9c4e-5f4b9d2a1c8e",
"isActive": false
}'const res = await fetch("https://api.callshift.ai/v1/campaigns", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CALLSHIFT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"name": "Q2 Outreach",
"agentId": "550e8400-e29b-41d4-a716-446655440000",
"phoneId": "0d3a7e6b-2b8e-4c5b-9c4e-5f4b9d2a1c8e",
"isActive": false
}),
});
const data = await res.json();import os
import requests
res = requests.post(
"https://api.callshift.ai/v1/campaigns",
headers={"Authorization": f"Bearer {os.environ['CALLSHIFT_API_KEY']}"},
json={
"name": "Q2 Outreach",
"agentId": "550e8400-e29b-41d4-a716-446655440000",
"phoneId": "0d3a7e6b-2b8e-4c5b-9c4e-5f4b9d2a1c8e",
"isActive": False,
},
)
print(res.json()){
"id": "550e8400-e29b-41d4-a716-446655440000"
}{
"error": "Invalid agent or phone, or a phone not allowed to place outbound calls"
}{
"error": "Authentication error"
}{
"error": "declinedStopRatio is an advanced setting that is not enabled on the account"
}{
"error": "The change would link items of two different entities. The error names them"
}{
"error": "Internal server error"
}Get campaign details
/v1/campaigns/{campaignId}Retrieve a campaign by ID along with its call metrics (total calls, reached, and completed).
Authorization
AuthorizationstringrequiredBearer <API key>, a key created at app.callshift.ai/api-keys.
Path parameters
campaignIdstring<uuid>requiredResponses
Campaign details retrieved successfully
idstring<uuid>requirednamestringrequiredstatusenum<string>requiredactiveinactivedeclinedStopRationumberrequiredThe campaign is stopped automatically once this share of its recent calls were declined as soon as they were placed, which usually means the carrier is blocking the number. Recent calls are its last 40 ended in the last 10 minutes, counted once at least 30 have ended. Between 0.35 (14 of 40 calls) and 0.7 (28 of 40 calls); defaults to 0.5 (20 of 40 calls). An advanced setting: an account it is not enabled on gets a 403 and keeps the default. Contact your sales representative to enable it.
metricsobjectrequiredShow child attributes
totalCallsintegerrequiredreachedintegerrequiredcallsCompletedintegerrequiredUnauthorized
errorstringrequiredCampaign not found
errorstringrequiredInternal server error
errorstringrequiredcurl "https://api.callshift.ai/v1/campaigns/550e8400-e29b-41d4-a716-446655440000" \
-H "Authorization: Bearer $CALLSHIFT_API_KEY"const res = await fetch("https://api.callshift.ai/v1/campaigns/550e8400-e29b-41d4-a716-446655440000", {
headers: {
Authorization: `Bearer ${process.env.CALLSHIFT_API_KEY}`,
},
});
const data = await res.json();import os
import requests
res = requests.get(
"https://api.callshift.ai/v1/campaigns/550e8400-e29b-41d4-a716-446655440000",
headers={"Authorization": f"Bearer {os.environ['CALLSHIFT_API_KEY']}"},
)
print(res.json()){
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Q2 Outreach",
"status": "active",
"declinedStopRatio": 0.5,
"metrics": {
"totalCalls": 1200,
"reached": 840,
"callsCompleted": 760
}
}{
"error": "Authentication error"
}{
"error": "Campaign not found"
}{
"error": "Internal server error"
}Update a campaign
/v1/campaigns/{campaignId}Partially update a campaign: rename it, change its agent or phone, start/stop it by setting the active state, or change the declined-call ratio it stops at.
Authorization
AuthorizationstringrequiredBearer <API key>, a key created at app.callshift.ai/api-keys.
Path parameters
campaignIdstring<uuid>requiredBody application/json
namestringagentIdstring<uuid>phoneIdstring<uuid>The phone the campaign dials from. It must be allowed to place outbound calls (isOutboundAllowed in GET /phones).
isActivebooleandeclinedStopRationumber | nullThe campaign is stopped automatically once this share of its recent calls were declined as soon as they were placed, which usually means the carrier is blocking the number. Recent calls are its last 40 ended in the last 10 minutes, counted once at least 30 have ended. Between 0.35 (14 of 40 calls) and 0.7 (28 of 40 calls); defaults to 0.5 (20 of 40 calls). An advanced setting: an account it is not enabled on gets a 403 and keeps the default. Contact your sales representative to enable it. Set to null to restore the default.
Responses
Campaign updated successfully
successbooleanrequiredInvalid agent or phone, or a phone not allowed to place outbound calls
errorstringrequiredUnauthorized
errorstringrequireddeclinedStopRatio is an advanced setting that is not enabled on the account
errorstringrequiredCampaign not found
errorstringrequiredThe change would link items of two different entities. The error names them.
errorstringrequiredInternal server error
errorstringrequiredcurl -X PATCH "https://api.callshift.ai/v1/campaigns/550e8400-e29b-41d4-a716-446655440000" \
-H "Authorization: Bearer $CALLSHIFT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"isActive": true
}'const res = await fetch("https://api.callshift.ai/v1/campaigns/550e8400-e29b-41d4-a716-446655440000", {
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.CALLSHIFT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"isActive": true
}),
});
const data = await res.json();import os
import requests
res = requests.patch(
"https://api.callshift.ai/v1/campaigns/550e8400-e29b-41d4-a716-446655440000",
headers={"Authorization": f"Bearer {os.environ['CALLSHIFT_API_KEY']}"},
json={
"isActive": True,
},
)
print(res.json()){
"success": true
}{
"error": "Invalid agent or phone, or a phone not allowed to place outbound calls"
}{
"error": "Authentication error"
}{
"error": "declinedStopRatio is an advanced setting that is not enabled on the account"
}{
"error": "Campaign not found"
}{
"error": "The change would link items of two different entities. The error names them"
}{
"error": "Internal server error"
}Delete a campaign
/v1/campaigns/{campaignId}Soft-delete a campaign by ID.
Authorization
AuthorizationstringrequiredBearer <API key>, a key created at app.callshift.ai/api-keys.
Path parameters
campaignIdstring<uuid>requiredResponses
Campaign deleted successfully
No content.
Unauthorized
errorstringrequiredCampaign not found
errorstringrequiredInternal server error
errorstringrequiredcurl -X DELETE "https://api.callshift.ai/v1/campaigns/550e8400-e29b-41d4-a716-446655440000" \
-H "Authorization: Bearer $CALLSHIFT_API_KEY"const res = await fetch("https://api.callshift.ai/v1/campaigns/550e8400-e29b-41d4-a716-446655440000", {
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.CALLSHIFT_API_KEY}`,
},
});
console.log(res.status); // 204import os
import requests
res = requests.delete(
"https://api.callshift.ai/v1/campaigns/550e8400-e29b-41d4-a716-446655440000",
headers={"Authorization": f"Bearer {os.environ['CALLSHIFT_API_KEY']}"},
)
print(res.status_code) # 204No content{
"error": "Authentication error"
}{
"error": "Campaign not found"
}{
"error": "Internal server error"
}Add contacts to a campaign
/v1/campaigns/{campaignId}/contactsAdd contacts to a campaign. Contacts are created or updated in the CRM (matched by phone number) and a call is queued for each one. If the campaign is active, the new calls are dispatched immediately.
Authorization
AuthorizationstringrequiredBearer <API key>, a key created at app.callshift.ai/api-keys.
Path parameters
campaignIdstring<uuid>requiredBody application/json
contactsobject[]requiredShow child attributes
salutationenum<string>mrmrsmsmissfirstNamestringlastNamestringemailstring<email>phoneNumberstringrequiredStored in E.164, so the queued call can later be matched by number.
informationstringmetadataobjectFree-form JSON metadata (advanced mode). When set, replaces the entire metadata blob — takes precedence over information. Injected into the agent prompt via the contact.metadata variable and included in the call-ended webhook payload.
optedOutbooleanResponses
Contacts added to the campaign successfully
successbooleanrequiredInvalid payload. error.issues[].path locates the offending contact, e.g. ["contacts", 0, "email"].
successenum<boolean>requiredfalseerrorobjectrequiredShow child attributes
nameenum<string>requiredZodErrorissuesobject[]requiredShow child attributes
codestringrequiredpathstring | number[]requiredmessagestringrequiredUnauthorized
errorstringrequiredCampaign not found
errorstringrequiredInternal server error
errorstringrequiredcurl -X POST "https://api.callshift.ai/v1/campaigns/550e8400-e29b-41d4-a716-446655440000/contacts" \
-H "Authorization: Bearer $CALLSHIFT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"contacts": [
{
"firstName": "Ada",
"lastName": "Lovelace",
"phoneNumber": "+33612345678",
"metadata": {
"plan": "pro"
}
}
]
}'const res = await fetch("https://api.callshift.ai/v1/campaigns/550e8400-e29b-41d4-a716-446655440000/contacts", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CALLSHIFT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"contacts": [
{
"firstName": "Ada",
"lastName": "Lovelace",
"phoneNumber": "+33612345678",
"metadata": {
"plan": "pro"
}
}
]
}),
});
const data = await res.json();import os
import requests
res = requests.post(
"https://api.callshift.ai/v1/campaigns/550e8400-e29b-41d4-a716-446655440000/contacts",
headers={"Authorization": f"Bearer {os.environ['CALLSHIFT_API_KEY']}"},
json={
"contacts": [
{
"firstName": "Ada",
"lastName": "Lovelace",
"phoneNumber": "+33612345678",
"metadata": {
"plan": "pro",
},
},
],
},
)
print(res.json()){
"success": true
}{
"success": false,
"error": {
"name": "ZodError",
"issues": [
{
"code": "invalid_enum_value",
"path": [
"contacts",
0,
"email"
],
"message": "Invalid email"
}
]
}
}{
"error": "Authentication error"
}{
"error": "Campaign not found"
}{
"error": "Internal server error"
}Delete a campaign's queued calls
/v1/campaigns/{campaignId}/callsDelete the calls queued for this campaign — those waiting to be dialed. status is required and must be READY; calls already dispatched, in progress, or completed are never affected, including on a campaign that is currently running. Send a body with phoneNumbers to delete only the calls queued for those numbers, or send no body at all to delete every queued call of the campaign. Nothing queued for the requested numbers returns { "deleted": 0 }; a 404 means the campaign itself was not found. Campaign statistics are computed from the remaining calls, so deleting queued calls lowers the campaign's total and moves its completion ratio toward 100%. This is irreversible: re-add the contacts through POST /v1/campaigns/{campaignId}/contacts to dial them again.
Authorization
AuthorizationstringrequiredBearer <API key>, a key created at app.callshift.ai/api-keys.
Path parameters
campaignIdstring<uuid>requiredQuery parameters
statusenum<string>requiredMust be READY. Required so that a request can never mean 'delete every call of this campaign'.
READYBody application/json
phoneNumbersstring[]Restrict the deletion to the queued calls placed to these numbers. Omit the body entirely to delete every queued call of the campaign. Numbers are normalised to E.164, so the formatting sent here does not matter, and a number with nothing queued is simply skipped.
Responses
Queued calls deleted successfully
deletedintegerrequiredstatus is missing or is not READY, or phoneNumbers holds an invalid number
successenum<boolean>requiredfalseerrorobjectrequiredShow child attributes
nameenum<string>requiredZodErrorissuesobject[]requiredShow child attributes
codestringrequiredpathstring | number[]requiredmessagestringrequiredUnauthorized
errorstringrequiredCampaign not found
errorstringrequiredInternal server error
errorstringrequiredcurl -X DELETE "https://api.callshift.ai/v1/campaigns/550e8400-e29b-41d4-a716-446655440000/calls?status=READY" \
-H "Authorization: Bearer $CALLSHIFT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"phoneNumbers": [
"+33612345678",
"+33612345679"
]
}'const res = await fetch("https://api.callshift.ai/v1/campaigns/550e8400-e29b-41d4-a716-446655440000/calls?status=READY", {
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.CALLSHIFT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"phoneNumbers": [
"+33612345678",
"+33612345679"
]
}),
});
const data = await res.json();import os
import requests
res = requests.delete(
"https://api.callshift.ai/v1/campaigns/550e8400-e29b-41d4-a716-446655440000/calls?status=READY",
headers={"Authorization": f"Bearer {os.environ['CALLSHIFT_API_KEY']}"},
json={
"phoneNumbers": [
"+33612345678",
"+33612345679",
],
},
)
print(res.json()){
"deleted": 8213
}{
"success": false,
"error": {
"name": "ZodError",
"issues": [
{
"code": "invalid_enum_value",
"path": [
"contacts",
0,
"email"
],
"message": "Invalid email"
}
]
}
}{
"error": "Authentication error"
}{
"error": "Campaign not found"
}{
"error": "Internal server error"
}Create a retry campaign
/v1/campaigns/{campaignId}/retryCreate a new campaign that re-dials the contacts the source campaign did not reach and who never called back. The new campaign reuses the source campaign's agent, phone and declinedStopRatio, and is named after it with a | retry suffix unless a name is given. It is created inactive: start it with PATCH /campaigns/{campaignId} once you are ready to dial.
Authorization
AuthorizationstringrequiredBearer <API key>, a key created at app.callshift.ai/api-keys.
Path parameters
campaignIdstring<uuid>requiredBody application/json
namestringResponses
Retry campaign created successfully
idstring<uuid>requirednamestringrequiredcallsQueuedintegerrequiredThe campaign has no calls to retry, its agent or phone no longer exists, or its phone is not allowed to place outbound calls
errorstringrequiredUnauthorized
errorstringrequiredCampaign not found
errorstringrequiredThe change would link items of two different entities. The error names them.
errorstringrequiredInternal server error
errorstringrequiredcurl -X POST "https://api.callshift.ai/v1/campaigns/550e8400-e29b-41d4-a716-446655440000/retry" \
-H "Authorization: Bearer $CALLSHIFT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Q2 Outreach | retry"
}'const res = await fetch("https://api.callshift.ai/v1/campaigns/550e8400-e29b-41d4-a716-446655440000/retry", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CALLSHIFT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"name": "Q2 Outreach | retry"
}),
});
const data = await res.json();import os
import requests
res = requests.post(
"https://api.callshift.ai/v1/campaigns/550e8400-e29b-41d4-a716-446655440000/retry",
headers={"Authorization": f"Bearer {os.environ['CALLSHIFT_API_KEY']}"},
json={
"name": "Q2 Outreach | retry",
},
)
print(res.json()){
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Q2 Outreach | retry",
"callsQueued": 142
}{
"error": "The campaign has no calls to retry, its agent or phone no longer exists, or its phone is not allowed to place outbound calls"
}{
"error": "Authentication error"
}{
"error": "Campaign not found"
}{
"error": "The change would link items of two different entities. The error names them"
}{
"error": "Internal server error"
}