Triggering Instant Campaigns with the API

Prev Next

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 needWhere it comes from
An Instant campaign that is ActiveCreated in Campaigns. A Planned campaign cannot take requests, and a paused or completed Instant campaign refuses them
The campaign's Campaign IDThe detail page of the campaign, in the header
A client ID and client secretGiven to you by SESTEK for your tenant
The address of the API and of the token serviceGiven 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 detail page of an Instant campaign

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.

The Copy as cURL window

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.

FieldRequiredRule
itemsYes1 to 100 items. For more people, send several requests
items[].clientReferenceIdYesYour own key, up to 128 characters, unique inside the campaign. See the section on reference IDs below
items[].contact.phoneNumberYesUp to 30 characters. It is converted to the international E.164 format. If it cannot be, only that item is rejected
items[].contact.nameYesUp to 200 characters. Used when a new contact has to be created
items[].contact.emailNoA valid address, up to 256 characters. Written only to a newly created contact
items[].localeNoUp to 16 characters, for example en-GB. Passed to the AI agent
items[].parametersNoUp 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
    }
  ]
}
statusacceptedMeaningWhat to do
QueuedtrueThe person joined the campaign and the call is nextNothing. The call usually starts within about 15 seconds
DeferredtrueAccepted, but the call window is closed. The request is not lostnextEligibleAtUtc tells you when the call can happen
MergedtrueThat person already had a call coming, so the request joined itmergedIntoId is the request it joined. No second call is placed
RejectedfalseNot acceptedRead rejectionReason
rejectionReasonMeaningCan you send it again?
ContactPhoneInvalidThe number cannot be turned into a number that can be dialedNo. The same number gives the same answer. Send the corrected number with a new clientReferenceId
AdmissionContendedA parallel operation on the same person was runningYes, with the same body and the same key
AdmissionFailedThe request could not be saved and nothing was createdYes, 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-1 and order-1 are 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.codeMeaningWhat to do
AgenticCX:ExternalApi:CampaignNotFoundNo campaign with this ID in your tenantCheck the Campaign ID
AgenticCX:ExternalApi:CampaignNotInstantTriggerThe campaign is not an Instant campaignCheck the trigger mode of the campaign
AgenticCX:ExternalApi:CampaignNotActiveThe campaign is paused or completedStart the campaign
AgenticCX:ExternalApi:CampaignOutsideDateWindowThe campaign is active but outside its own start and end datesCheck 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 statusMeaning
200The request was processed. Item level rejections are inside the body
400The 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
401The token is missing or expired, or the audience is wrong. Request a token with scope=AgenticCXExternal
403The token is valid but the client may not call this API. Check the client's role and tenant with SESTEK
422The 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.

BlockWhat it tells you
submittedWhat you sent, including the normalized phone number
acceptanceWhat the API answered when it accepted the request: status, nextEligibleAtUtc, mergedIntoId, rejectionReason
callWhat the campaign did for this request: state, attemptCount, answered, the time of each attempt and its outcome
contactStateWhere the person is in the campaign right now. If several requests exist for the same person they all see the same contactState
call.stateMeaning
NotStartedAccepted, no attempt yet. The normal state between acceptance and the first call. Not an error
DialingThe call is being placed now
CompletedThe call reached a final result. answered tells you whether anyone picked up
FailedThe call could not be set up
CancelledThe 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.statusMeaning
PendingIn line, a call is expected. nextEligibleAtUtc is the earliest time of the next call
WaitingNextWindowThe call window is closed. The person returns to the line when it opens
Reserved or CallingBeing called now
CompletedDone
MaxAttemptsReachedNo attempts left
ExcludedRemoved 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.

EndpointWhat it returns
GET /ext-api/v1/campaign/{campaignId}/call-requestsA page of the requests, newest first
GET /ext-api/v1/campaign/{campaignId}/call-requests-summaryThe requests counted by outcome and call state

Both accept these filters.

ParameterRule
groupIdLimits the answer to what one POST touched. The groupId comes back in the response of that POST
startDate, endDateBoth 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

  1. Get a token and reuse it until it expires.
  2. Copy the Campaign ID from the campaign's detail page into your configuration.
  3. When the event happens, send a POST with up to 100 people.
  4. Store each result: its id, status and groupId.
  5. After a lost connection, send the same body again.
  6. To follow one person, read the request by its clientReferenceId. To follow a batch, use call-requests-summary with its groupId.

Good to know

  • All times in the API are UTC (fields ending in Utc). Nothing is converted to the tenant time zone.
  • A Merged request keeps its parameters and locale on 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 clientReferenceId each 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.