An Instant campaign calls the people you tell it to call, at the moment you tell it. A form, a CRM or any other system sends a call request to the SESTEK AgenticCX external API, the person joins the campaign, and the campaign's AI agent calls them. Typical cases are "the customer just filled in the form, call them now" or "the order was cancelled, place a notification call".
This article shows how to trigger an Instant campaign through the API from start to finish: what you need, how to get a token, how to send requests, how to read the answers, and how to follow each call afterwards. To create the campaign itself, see Creating and Editing a Campaign and AI Agent Campaigns and Instant Trigger.
Before you start
| You need | Where it comes from |
|---|---|
| An Instant campaign that is Active | Created in Campaigns. A Planned campaign cannot take requests, and a paused or completed Instant campaign refuses them |
| The campaign's Campaign ID | The detail page of the campaign, in the header |
| A client ID and client secret | Given to you by SESTEK for your tenant |
| The address of the API and of the token service | Given to you by SESTEK together with the client |
A client belongs to exactly one tenant and sees only that tenant's campaigns. You never send a tenant in a request: it is read from the token.

The Campaign ID next to the campaign name is the number your integration uses. It never changes, even when you rename the campaign, so store the ID and not the name. Do not complete an Instant campaign that an integration still uses: completing cannot be undone and the ID stops working. Use Pause for a break.
Get the cURL commands from the screen
You do not have to write the requests from scratch. Select the </> icon next to the Campaign ID to open Copy as cURL (ext-api). It lists the five campaign endpoints, and selecting one copies its cURL command to your clipboard with your Campaign ID filled in. The access token is always a placeholder (<YOUR_ACCESS_TOKEN>): no real token is ever shown or copied. Replace every placeholder in angle brackets before you run the command.

Step 1: get an access token
The API uses the OAuth client credentials flow.
POST /connect/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
&client_id=<YOUR_CLIENT_ID>
&client_secret=<YOUR_CLIENT_SECRET>
&scope=AgenticCXExternal
Send the token that comes back on every request as Authorization: Bearer <YOUR_ACCESS_TOKEN> and reuse it until it expires.
The scope parameter is required. Without it the token is issued without an audience, the token request still looks successful, and every API call then fails with 401 and the message The audience 'empty' is invalid.
Step 2: send call requests
POST /ext-api/v1/campaign/96/call-requests
Authorization: Bearer <YOUR_ACCESS_TOKEN>
Content-Type: application/json
{
"items": [
{
"clientReferenceId": "ORDER-2026-0001",
"contact": {
"phoneNumber": "+447700900123",
"name": "Emma Clarke",
"email": "emma.clarke@example.com"
},
"locale": "en-GB",
"parameters": [
{ "key": "orderId", "value": "A-1001" },
{ "key": "amount", "value": "249.90" }
]
}
]
}
Every item is one person to call.
| Field | Required | Rule |
|---|---|---|
items | Yes | 1 to 100 items. For more people, send several requests |
items[].clientReferenceId | Yes | Your own key, up to 128 characters, unique inside the campaign. See the section on reference IDs below |
items[].contact.phoneNumber | Yes | Up to 30 characters. It is converted to the international E.164 format. If it cannot be, only that item is rejected |
items[].contact.name | Yes | Up to 200 characters. Used when a new contact has to be created |
items[].contact.email | No | A valid address, up to 256 characters. Written only to a newly created contact |
items[].locale | No | Up to 16 characters, for example en-GB. Passed to the AI agent |
items[].parameters | No | Up to 20 key and value pairs. A key is required and unique in the item, up to 64 characters. A value is up to 512 characters. The whole set must also fit in 4,000 bytes |
The parameters give the AI agent the context of the call, for example an order number or an appointment date. Both limits apply together: twenty pairs with 512 character values exceed the 4,000 bytes and the whole request is refused with 400.
Step 3: read the answer
The response is always 200 when the request itself is valid, and every item carries its own result, in the order you sent them. Match results to your records with clientReferenceId. A rejected item does not affect the others.
{
"campaignId": 96,
"groupId": "2f6a8e10-9b4c-4a3d-8e21-0a5b7c9d1e2f",
"acceptedCount": 1,
"rejectedCount": 0,
"results": [
{
"id": "8f14e45f-ea8d-4b41-9a2c-7c2b0e1d5a63",
"clientReferenceId": "ORDER-2026-0001",
"accepted": true,
"status": "Queued",
"nextEligibleAtUtc": null,
"mergedIntoId": null,
"rejectionReason": null
}
]
}
status | accepted | Meaning | What to do |
|---|---|---|---|
Queued | true | The person joined the campaign and the call is next | Nothing. The call usually starts within about 15 seconds |
Deferred | true | Accepted, but the call window is closed. The request is not lost | nextEligibleAtUtc tells you when the call can happen |
Merged | true | That person already had a call coming, so the request joined it | mergedIntoId is the request it joined. No second call is placed |
Rejected | false | Not accepted | Read rejectionReason |
rejectionReason | Meaning | Can you send it again? |
|---|---|---|
ContactPhoneInvalid | The number cannot be turned into a number that can be dialed | No. The same number gives the same answer. Send the corrected number with a new clientReferenceId |
AdmissionContended | A parallel operation on the same person was running | Yes, with the same body and the same key |
AdmissionFailed | The request could not be saved and nothing was created | Yes, with the same body and the same key |
Accepting a request does not start a call by itself. The campaign engine places it according to the campaign's call window, maximum attempt count and wait between calls.
Reference IDs and retries
clientReferenceId is your idempotency key. Sending the same key again never creates a second call: you get the first result back, with the same status. That makes it safe to send the whole body again after a lost connection, with no reconciliation.
- The key is case sensitive.
ORDER-1andorder-1are two different keys and place two calls. Generate keys in one consistent form. - It cannot contain
/,\,%,?or#, control characters, or be only.or... Everything else is allowed, including spaces and non English letters. - Percent-encode the key when you put it in a URL.
- The guarantee lasts as long as the request is kept. Finished requests are removed after a retention period, 90 days by default, after which the same key counts as new work and can place a new call. A system that reuses keys, for example order numbers that repeat every year, has to take this into account.
- Use a repeated request only to recover from an error. Never use it to check the state of a call: each repeat writes a record. Use the follow-up endpoints below instead.
When the whole request is refused
Before it reads any item, the API checks the campaign. If the campaign cannot take requests, the entire body is refused with 422.
error.code | Meaning | What to do |
|---|---|---|
AgenticCX:ExternalApi:CampaignNotFound | No campaign with this ID in your tenant | Check the Campaign ID |
AgenticCX:ExternalApi:CampaignNotInstantTrigger | The campaign is not an Instant campaign | Check the trigger mode of the campaign |
AgenticCX:ExternalApi:CampaignNotActive | The campaign is paused or completed | Start the campaign |
AgenticCX:ExternalApi:CampaignOutsideDateWindow | The campaign is active but outside its own start and end dates | Check the dates of the campaign |
After a 422, never retry with a new clientReferenceId. That is the one case that can place two calls for the same person. Send the same body with the same keys again once the campaign runs. A paused campaign still answers a body made only of keys it already accepted, so replaying after a lost connection stays safe.
| HTTP status | Meaning |
|---|---|
| 200 | The request was processed. Item level rejections are inside the body |
| 400 | The body or the query does not follow the contract, for example a missing field, a limit exceeded or an unknown filter value. Fix the request, retrying does not help |
| 401 | The token is missing or expired, or the audience is wrong. Request a token with scope=AgenticCXExternal |
| 403 | The token is valid but the client may not call this API. Check the client's role and tenant with SESTEK |
| 422 | The request is valid but cannot be done. Read error.code, not the message text |
Step 4: follow a call
To see what happened to one request, ask for it by its reference ID.
GET /ext-api/v1/campaign/96/call-requests/ORDER-2026-0001
Authorization: Bearer <YOUR_ACCESS_TOKEN>
The answer has four blocks.
| Block | What it tells you |
|---|---|
submitted | What you sent, including the normalized phone number |
acceptance | What the API answered when it accepted the request: status, nextEligibleAtUtc, mergedIntoId, rejectionReason |
call | What the campaign did for this request: state, attemptCount, answered, the time of each attempt and its outcome |
contactState | Where the person is in the campaign right now. If several requests exist for the same person they all see the same contactState |
call.state | Meaning |
|---|---|
NotStarted | Accepted, no attempt yet. The normal state between acceptance and the first call. Not an error |
Dialing | The call is being placed now |
Completed | The call reached a final result. answered tells you whether anyone picked up |
Failed | The call could not be set up |
Cancelled | The request was rejected or the call was cancelled |
The attempt outcome is one of Answered, NoAnswer, Failed, Cancelled, Dialing, Scheduled or Unknown. For a failed attempt, errorCode gives the reason: a hangup reason reported by the telephony system, for example NO_ANSWER or USER_BUSY, or a configuration reason such as NoActiveOutboundNumber when the call could not be placed at all. New reason codes can appear, so do not branch on exact values. Treat an unknown code as a failed attempt and log the text.
contactState.status | Meaning |
|---|---|
Pending | In line, a call is expected. nextEligibleAtUtc is the earliest time of the next call |
WaitingNextWindow | The call window is closed. The person returns to the line when it opens |
Reserved or Calling | Being called now |
Completed | Done |
MaxAttemptsReached | No attempts left |
Excluded | Removed from the campaign, or the request never entered the pool |
A Merged request has an empty call block, because it did not create a call of its own. Ask for the request in mergedIntoId to see the call.
List and count requests
Two more endpoints help when you track many requests.
| Endpoint | What it returns |
|---|---|
GET /ext-api/v1/campaign/{campaignId}/call-requests | A page of the requests, newest first |
GET /ext-api/v1/campaign/{campaignId}/call-requests-summary | The requests counted by outcome and call state |
Both accept these filters.
| Parameter | Rule |
|---|---|
groupId | Limits the answer to what one POST touched. The groupId comes back in the response of that POST |
startDate, endDate | Both inclusive and both optional. They filter on the time the request was sent, not the time of the call. A value without an offset is read as UTC |
The list adds callState to filter by call state, maxResultCount (1 to 500, default 50) and cursor for paging. Pass the nextCursor of one page as the cursor of the next one, and treat it as an opaque value. A nextCursor of null means the last page. The summary returns totalCount, acceptedCount, the count of each status and each call state, and two sums: pendingCount (not started, scheduled or dialing) and finishedCount (completed, failed or cancelled).
A typical integration
- Get a token and reuse it until it expires.
- Copy the Campaign ID from the campaign's detail page into your configuration.
- When the event happens, send a
POSTwith up to 100 people. - Store each result: its
id,statusandgroupId. - After a lost connection, send the same body again.
- To follow one person, read the request by its
clientReferenceId. To follow a batch, usecall-requests-summarywith itsgroupId.
Good to know
- All times in the API are UTC (fields ending in
Utc). Nothing is converted to the tenant time zone. - A
Mergedrequest keeps itsparametersandlocaleon record, but the AI agent uses the context of the request it joined. To give the same person a different context, wait until the first call is finished and then send a new key. - A person is matched by phone number. If a contact with that number already exists it is used, and the name and email you send are not written to it. They are used only when a new contact is created. To change an existing contact, use the Contacts screens.
- An accepted request is never cancelled and never expires: the call waits for its window, stops at the attempt limit, and waits while the campaign is paused.
- There is no limit per person on how often the same person can be triggered. If you send a new
clientReferenceIdeach time, the same number can be called again at short intervals, the campaign's Wait Between Calls does not apply, and the attempt counter starts again with each new request. Agree on a rule for this with the people responsible for compliance. - This article describes the campaign endpoints. The full contract, including every field, is in the External API article under Technical Documents.
