Browse the docs

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.

List campaigns

GET/v1/campaigns
Bearer API key

List all campaigns for the authenticated tenant with pagination and optional filtering.

Authorization

Authorizationstringrequired

Bearer <API key>, a key created at app.callshift.ai/api-keys.

Query parameters

pageinteger
minimum 1default 1example 1
limitinteger
from 1 to 100default 50example 50
namestring
example Q2 Outreach
agentIdstring<uuid>
example 550e8400-e29b-41d4-a716-446655440000

Responses

Campaigns listed successfully

dataobject[]required
Show child attributes
idstring<uuid>required
example 550e8400-e29b-41d4-a716-446655440000
namestringrequired
example Q2 Outreach
statusenum<string>required
example active
Allowedactiveinactive
phoneIdstring<uuid>required
example 0d3a7e6b-2b8e-4c5b-9c4e-5f4b9d2a1c8e
agentobjectrequired
Show child attributes
idstring<uuid>required
example 550e8400-e29b-41d4-a716-446655440000
namestringrequired
example Alice
genderenum<string>required
example FEMALE
AllowedFEMALEMALE
pageCountnumberrequired
example 3

Unauthorized

errorstringrequired

Internal server error

errorstringrequired
GET
curl "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())
Response
{
  "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

POST/v1/campaigns
Bearer API key

Create a new outbound campaign for the authenticated tenant. The campaign is inactive unless isActive is set to true.

Authorization

Authorizationstringrequired

Bearer <API key>, a key created at app.callshift.ai/api-keys.

Body application/json

namestringrequired
1 to 255 charactersexample Q2 Outreach
agentIdstring<uuid>required
example 550e8400-e29b-41d4-a716-446655440000
phoneIdstring<uuid>required

The phone the campaign dials from. It must be allowed to place outbound calls (isOutboundAllowed in GET /phones).

example 0d3a7e6b-2b8e-4c5b-9c4e-5f4b9d2a1c8e
isActiveboolean

Whether 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.

default falseexample false
declinedStopRationumber

The 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.

from 0.35 to 0.7example 0.6

Responses

Campaign created successfully

idstring<uuid>required

Invalid agent or phone, or a phone not allowed to place outbound calls

errorstringrequired

Unauthorized

errorstringrequired

declinedStopRatio is an advanced setting that is not enabled on the account

errorstringrequired

The change would link items of two different entities. The error names them.

errorstringrequired

Internal server error

errorstringrequired
POST
curl -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())
Response
{
  "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

GET/v1/campaigns/{campaignId}
Bearer API key

Retrieve a campaign by ID along with its call metrics (total calls, reached, and completed).

Authorization

Authorizationstringrequired

Bearer <API key>, a key created at app.callshift.ai/api-keys.

Path parameters

campaignIdstring<uuid>required
example 550e8400-e29b-41d4-a716-446655440000

Responses

Campaign details retrieved successfully

idstring<uuid>required
example 550e8400-e29b-41d4-a716-446655440000
namestringrequired
example Q2 Outreach
statusenum<string>required
example active
Allowedactiveinactive
declinedStopRationumberrequired

The 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.

example 0.5
metricsobjectrequired
Show child attributes
totalCallsintegerrequired
example 1200
reachedintegerrequired
example 840
callsCompletedintegerrequired
example 760

Unauthorized

errorstringrequired

Campaign not found

errorstringrequired

Internal server error

errorstringrequired
GET
curl "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())
Response
{
  "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

PATCH/v1/campaigns/{campaignId}
Bearer API key

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

Authorizationstringrequired

Bearer <API key>, a key created at app.callshift.ai/api-keys.

Path parameters

campaignIdstring<uuid>required
example 550e8400-e29b-41d4-a716-446655440000

Body application/json

namestring
1 to 255 charactersexample Q2 Outreach
agentIdstring<uuid>
example 550e8400-e29b-41d4-a716-446655440000
phoneIdstring<uuid>

The phone the campaign dials from. It must be allowed to place outbound calls (isOutboundAllowed in GET /phones).

example 0d3a7e6b-2b8e-4c5b-9c4e-5f4b9d2a1c8e
isActiveboolean
example true
declinedStopRationumber | null

The 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.

from 0.35 to 0.7example 0.6

Responses

Campaign updated successfully

successbooleanrequired

Invalid agent or phone, or a phone not allowed to place outbound calls

errorstringrequired

Unauthorized

errorstringrequired

declinedStopRatio is an advanced setting that is not enabled on the account

errorstringrequired

Campaign not found

errorstringrequired

The change would link items of two different entities. The error names them.

errorstringrequired

Internal server error

errorstringrequired
PATCH
curl -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())
Response
{
  "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

DELETE/v1/campaigns/{campaignId}
Bearer API key

Soft-delete a campaign by ID.

Authorization

Authorizationstringrequired

Bearer <API key>, a key created at app.callshift.ai/api-keys.

Path parameters

campaignIdstring<uuid>required
example 550e8400-e29b-41d4-a716-446655440000

Responses

Campaign deleted successfully

No content.

Unauthorized

errorstringrequired

Campaign not found

errorstringrequired

Internal server error

errorstringrequired
DELETE
curl -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); // 204
import 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)  # 204
Response
No content
{
  "error": "Authentication error"
}
{
  "error": "Campaign not found"
}
{
  "error": "Internal server error"
}

Add contacts to a campaign

POST/v1/campaigns/{campaignId}/contacts
Bearer API key

Add 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

Authorizationstringrequired

Bearer <API key>, a key created at app.callshift.ai/api-keys.

Path parameters

campaignIdstring<uuid>required
example 550e8400-e29b-41d4-a716-446655440000

Body application/json

contactsobject[]required
Show child attributes
salutationenum<string>
example mr
Allowedmrmrsmsmiss
firstNamestring
example John
lastNamestring
example Doe
emailstring<email>
example john@example.com
phoneNumberstringrequired

Stored in E.164, so the queued call can later be matched by number.

example +33612345678
informationstring
example VIP customer
metadataobject

Free-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.

optedOutboolean
example false

Responses

Contacts added to the campaign successfully

successbooleanrequired

Invalid payload. error.issues[].path locates the offending contact, e.g. ["contacts", 0, "email"].

successenum<boolean>required
Allowedfalse
errorobjectrequired
Show child attributes
nameenum<string>required
AllowedZodError
issuesobject[]required
Show child attributes
codestringrequired
example invalid_enum_value
pathstring | number[]required
messagestringrequired
example Invalid email

Unauthorized

errorstringrequired

Campaign not found

errorstringrequired

Internal server error

errorstringrequired
POST
curl -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())
Response
{
  "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

DELETE/v1/campaigns/{campaignId}/calls
Bearer API key

Delete 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

Authorizationstringrequired

Bearer <API key>, a key created at app.callshift.ai/api-keys.

Path parameters

campaignIdstring<uuid>required
example 550e8400-e29b-41d4-a716-446655440000

Query parameters

statusenum<string>required

Must be READY. Required so that a request can never mean 'delete every call of this campaign'.

example READY
AllowedREADY

Body 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

deletedintegerrequired
example 8213

status is missing or is not READY, or phoneNumbers holds an invalid number

successenum<boolean>required
Allowedfalse
errorobjectrequired
Show child attributes
nameenum<string>required
AllowedZodError
issuesobject[]required
Show child attributes
codestringrequired
example invalid_enum_value
pathstring | number[]required
messagestringrequired
example Invalid email

Unauthorized

errorstringrequired

Campaign not found

errorstringrequired

Internal server error

errorstringrequired
DELETE
curl -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())
Response
{
  "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

POST/v1/campaigns/{campaignId}/retry
Bearer API key

Create 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

Authorizationstringrequired

Bearer <API key>, a key created at app.callshift.ai/api-keys.

Path parameters

campaignIdstring<uuid>required
example 550e8400-e29b-41d4-a716-446655440000

Body application/json

namestring
1 to 255 charactersexample Q2 Outreach | retry

Responses

Retry campaign created successfully

idstring<uuid>required
example 550e8400-e29b-41d4-a716-446655440000
namestringrequired
example Q2 Outreach | retry
callsQueuedintegerrequired
example 142

The campaign has no calls to retry, its agent or phone no longer exists, or its phone is not allowed to place outbound calls

errorstringrequired

Unauthorized

errorstringrequired

Campaign not found

errorstringrequired

The change would link items of two different entities. The error names them.

errorstringrequired

Internal server error

errorstringrequired
POST
curl -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())
Response
{
  "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"
}