Campaigns
Campaigns dial every contact in an audience using a selected voice flow.
The Campaign Object
{
"id": "camp_def456",
"name": "Mumbai Outreach - March 2026",
"status": "IN_PROGRESS",
"useCaseType": "COLLECTIONS",
"useCaseTypeConfirmed": true,
"flowId": "flow_xyz789",
"audienceId": "aud_abc123",
"callsPerMinute": 10,
"maxConcurrentCalls": 5,
"maxRetries": 3,
"retryDelayMinutes": 60,
"retryDelaysMinutes": [],
"retryWithinWindow": true,
"endAt": null,
"callTimeoutMinutes": 5,
"scheduledAt": null,
"timezone": null,
"organizationId": "org_1",
"createdAt": "2026-03-10T09:00:00.000Z",
"startedAt": "2026-03-10T09:05:00.000Z",
"completedAt": null
}Campaign Statuses
| Status | Description |
|---|---|
DRAFT | Created but not started |
SCHEDULED | Waiting to start at scheduledAt time |
IN_PROGRESS | Actively dialing contacts |
PAUSED | Temporarily halted, can be resumed |
COMPLETED | All contacts called |
CANCELLED | Permanently stopped |
List Campaigns
GET /v1/campaignsQuery Parameters:
| Parameter | Type | Description |
|---|---|---|
page | integer | Page number (default: 1) |
limit | integer | Items per page (default: 20, max: 100) |
status | string | Filter by status (e.g., IN_PROGRESS) |
search | string | Search by campaign name |
Get a Campaign
GET /v1/campaigns/:idCreate a Campaign
POST /v1/campaignsRequest Body:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Campaign name |
useCaseType | string | Yes | One of COLLECTIONS, LEAD_QUALIFICATION, MEDICAL_BOOKING, CUSTOMER_SUPPORT, EDUTECH, CUSTOM. Seeds the campaign's intent labels. |
flowId | string | Yes | ID of the voice flow to use |
audienceId | string | Yes | ID of the audience to call |
callsPerMinute | integer | No | Default: 10 |
maxConcurrentCalls | integer | No | Default: 5 |
maxRetries | integer | No | Retries per contact, 0–10. Default: 3 |
retryDelayMinutes | integer | No | Flat delay, 30–4320 min. Default: 60 |
retryDelaysMinutes | integer[] | No | Per-attempt delay schedule (minutes). Overrides retryDelayMinutes when non-empty. See Retry Policy. |
retryWithinWindow | boolean | No | Defer retries into the calling window. Default: true |
bestTimeOptimization | boolean | No | Schedule retries within each contact's preferred time band. Default: false |
endAt | string (ISO 8601) | No | Campaign deadline; retries past it are skipped |
callTimeoutMinutes | integer | No | Default: 5 |
scheduledAt | string (ISO 8601) | No | Future start time |
timezone | string | No | Required if scheduledAt is set (e.g., Asia/Kolkata) |
Example:
{
"name": "Mumbai Outreach - March 2026",
"useCaseType": "COLLECTIONS",
"flowId": "flow_xyz789",
"audienceId": "aud_abc123",
"callsPerMinute": 10,
"maxConcurrentCalls": 5,
"maxRetries": 2,
"scheduledAt": "2026-03-15T09:00:00Z",
"timezone": "Asia/Kolkata"
}Response: 201 Created with the Campaign object in DRAFT status.
Start a Campaign
POST /v1/campaigns/:id/startTransitions the campaign from DRAFT or SCHEDULED to IN_PROGRESS.
OliAI checks your organization's quota before starting. If insufficient minutes remain, the request returns 402 Payment Required.
Response: 200 OK with updated Campaign object.
Pause a Campaign
POST /v1/campaigns/:id/pauseStops new calls from being initiated. Calls in progress continue to completion.
Response: 200 OK with { "status": "PAUSED" }.
Resume a Campaign
POST /v1/campaigns/:id/resumeContinues a paused campaign from where it left off.
Response: 200 OK with { "status": "IN_PROGRESS" }.
Cancel a Campaign
POST /v1/campaigns/:id/cancelPermanently stops the campaign. This cannot be undone.
Response: 200 OK with { "status": "CANCELLED" }.
Get Campaign Progress
GET /v1/campaigns/:id/progressResponse:
{
"status": "IN_PROGRESS",
"total": 250,
"pending": 180,
"queued": 5,
"inProgress": 5,
"completed": 55,
"failed": 5,
"skipped": 0,
"suppressed": 2,
"frequencyDeferred": 8,
"percentComplete": 24
}frequencyDeferred is the running count of calls pushed to a later date by the org's frequency cap.
Real-time Dashboard
Aggregated, org-scoped metrics for the campaigns list view — per-campaign cards plus an organization summary. Powers the live dashboard in the UI.
GET /v1/campaigns/dashboardQuery parameters (all optional):
| Parameter | Description |
|---|---|
useCaseType | Filter campaigns by use case type |
status | Filter by campaign status |
startDateFrom | Only campaigns created on/after this ISO date |
startDateTo | Only campaigns created on/before this ISO date |
Response:
{
"summary": {
"activeCampaigns": 3,
"callsToday": 412,
"contactsToday": 287,
"generatedAt": "2026-06-01T09:30:00.000Z"
},
"campaigns": [
{
"id": "clx...",
"name": "Q2 Collections",
"useCaseType": "COLLECTIONS",
"status": "IN_PROGRESS",
"totalContacts": 250,
"callsAttempted": 318,
"connected": 142,
"notConnected": 38,
"connectionRate": 0.79,
"retryQueueDepth": 12,
"nextRetryAt": "2026-06-01T11:00:00.000Z",
"positiveConnected": 61,
"positiveRate": 0.43,
"intentSplit": [
{ "id": "...", "name": "Interested in Paying", "color": "#16a34a", "isPositive": true, "count": 61 }
],
"completionPct": 72,
"health": { "level": "green", "reason": "Healthy connection rate (79%)" }
}
]
}connectionRate= connected ÷ (connected + failed), ornulluntil any call resolves.positiveRate= positive-intent connected ÷ connected, ornullwhen there are no connected calls.health.levelisgreen/amber/red;reasonis a one-line explanation.
The summary counts are org-wide (unaffected by the filters); the filters only narrow the campaigns array.
Get Campaign Calls
GET /v1/campaigns/:id/callsReturns the individual call records for every contact in the campaign.
Query Parameters: page, limit
Response:
{
"data": [
{
"callSid": "CA...",
"contactId": "clxxx123",
"contactName": "Priya Sharma",
"phoneNumber": "+919876543210",
"status": "COMPLETED",
"duration": 87,
"attempts": 1,
"startedAt": "2026-03-10T09:07:00.000Z",
"completedAt": "2026-03-10T09:08:27.000Z",
"intentSource": "auto",
"intentUndetermined": false,
"intentLabel": {
"id": "lbl_abc123",
"name": "Interested in Paying",
"color": "#16a34a",
"isPositive": true
},
"journeyAction": "RETRY",
"flaggedForReview": false,
"suppressedFromCampaign": false
}
],
"total": 250,
"page": 1,
"limit": 20
}The intent and routing fields are populated by automatic post-call analysis (see Intent Labels) and Journey Rules:
| Field | Description |
|---|---|
intentLabel | The assigned label (null when undetermined or not yet analysed) |
intentSource | auto (AI-classified), system (e.g. unanswered → Not Reachable), or null |
intentUndetermined | true when the call could not be classified (no/short transcript, or AI failure) |
journeyAction | The journey-rule action applied at the last tagging (null if not yet routed) |
flaggedForReview | true when a FLAG_REVIEW rule fired for the call |
suppressedFromCampaign | true when suppressed from further retries in this campaign |
suppressionReason | Why the call was suppressed (intent label, or Do Not Contact (DNC)). Status is SUPPRESSED. See Suppression. |
Intent Labels
Each campaign has its own set of intent labels used to classify call outcomes. They are seeded from the campaign's useCaseType at creation and can be edited only while the campaign is in DRAFT. After every call ends, OliAI auto-assigns one label (or flags it Undetermined).
The Intent Label Object
{
"id": "lbl_abc123",
"name": "Interested in Paying",
"color": "#16a34a",
"isPositive": true,
"retryBehavior": "RETRYABLE",
"suppressRetries": false,
"isSystem": false,
"isCustom": false,
"sortOrder": 0
}| Field | Description |
|---|---|
color | Hex swatch (#rrggbb) for the label badge |
isPositive | Counts as a positive outcome |
retryBehavior | RETRYABLE, SUPPRESS_CAMPAIGN, or SUPPRESS_ORG_WIDE — see Suppression |
suppressRetries | Derived (true when retryBehavior is not RETRYABLE). Read-only. |
isSystem | Required label (e.g. Not Reachable) — cannot be deleted, only renamed |
isCustom | Admin-added label (max 10 per campaign) |
sortOrder | Priority order; lower = higher priority when resolving ambiguous calls |
List Intent Labels
GET /v1/campaigns/:id/intent-labelsReturns the campaign's labels ordered by sortOrder.
Add a Custom Label
POST /v1/campaigns/:id/intent-labels| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Label name |
color | string | No | Hex colour, e.g. #16a34a |
isPositive | boolean | No | Default false |
retryBehavior | string | No | RETRYABLE (default), SUPPRESS_CAMPAIGN, SUPPRESS_ORG_WIDE |
A campaign may have at most 10 custom labels on top of the preset.
Update a Label
PATCH /v1/campaigns/:id/intent-labels/:labelIdAny subset of name, color, isPositive, retryBehavior. System labels can be renamed/recoloured but not deleted.
Reorder Labels
PATCH /v1/campaigns/:id/intent-labels/reorder| Field | Type | Required | Description |
|---|---|---|---|
orderedIds | string[] | Yes | All label IDs in the desired priority order |
Delete a Label
DELETE /v1/campaigns/:id/intent-labels/:labelIdReturns 400 if the label is a system label, or if any call is already tagged with it (the message includes the affected call count).
All intent-label endpoints require the campaign to be in DRAFT. Once the campaign launches, the label set is locked.
Journey Rules
Journey rules map a campaign's intent labels to a next action that runs automatically the moment a call is tagged. There is at most one rule per intent label; labels without a rule default to MARK_COMPLETED.
Unlike intent labels, journey rules can be changed while the campaign is running. Updates apply to new intent-tagging events only — contacts already routed are not re-evaluated.
Actions
| Action | Effect |
|---|---|
RETRY | Re-dial the contact after retryDelayMinutes (or the campaign default), respecting the calling window and max-retries cap |
SUPPRESS_CAMPAIGN | No further retries for this contact in this campaign |
SUPPRESS_ORG_WIDE | Add the contact to the org-wide Do Not Contact list (skipped by all campaigns) |
FLAG_REVIEW | Flag the call for human review in the dashboard |
COMMITMENT_REMINDER | Trigger a commitment reminder (no-op until Commitment Capture ships) |
MARK_COMPLETED | No further action (default) |
The Journey Rule Object
{
"id": "jr_abc123",
"campaignId": "camp_def456",
"intentLabelId": "lbl_abc123",
"action": "RETRY",
"retryDelayMinutes": 240
}retryDelayMinutes is only used by the RETRY action (1–10080 minutes); it is ignored/cleared for every other action.
List Journey Rules
GET /v1/campaigns/:id/journey-rulesReturns the configured rules, each with its intent label.
Set a Rule
PUT /v1/campaigns/:id/journey-rules/:labelIdCreates or replaces the rule for the given intent label.
| Field | Type | Required | Description |
|---|---|---|---|
action | string | Yes | One of the actions above |
retryDelayMinutes | integer | No | RETRY only — overrides the campaign default (1–10080) |
Delete a Rule
DELETE /v1/campaigns/:id/journey-rules/:labelIdRemoves the rule; the label reverts to the MARK_COMPLETED default.
Update a Campaign
PATCH /v1/campaigns/:idCampaigns can only be updated while in DRAFT status (except the retry policy — see below).
Request Body: Any subset of campaign creation fields.
Update Retry Policy
PATCH /v1/campaigns/:id/retry-policyUpdates the retry configuration independently of the DRAFT-only general update — allowed while the campaign is DRAFT, SCHEDULED, IN_PROGRESS, or PAUSED. Changes apply from the next retry cycle; contacts already queued are unaffected.
Request Body: any subset of —
| Field | Type | Description |
|---|---|---|
maxRetries | integer | Retries per contact, 0–10 |
retryDelayMinutes | integer | Flat delay, 30–4320 min (used when retryDelaysMinutes is empty) |
retryDelaysMinutes | integer[] | Per-attempt delays in minutes; index 0 = wait after attempt 1. Empty array clears it (back to the flat delay). Attempts beyond its length reuse the last entry. |
retryWithinWindow | boolean | When false, retries bypass the calling-window deferral |
bestTimeOptimization | boolean | Schedule retries within the contact's preferred time band (always inside the calling window) |
endAt | string | null | Campaign deadline; null clears it |
0 retries = one attempt per contact. A retry that would land past endAt is skipped and the contact is marked Failed with reason Retry Expired.
Callbacks
When a flow has a variable flagged as a callback request, OliAI extracts the requested time from the call transcript and schedules the next attempt then. The scheduled time appears on the call as callbackScheduledAt (with callbackAdjusted set if it was moved into the calling window) and is included in the write-back payload as callback_time.
Mark a Callback Fulfilled
Closes out a scheduled callback manually and cancels the pending outbound call (status → SKIPPED).
POST /v1/campaigns/:id/callbacks/:campaignCallId/fulfillResponse: the updated campaign call.
Suppression & Do Not Contact
When a call is tagged with a label whose retryBehavior is SUPPRESS_CAMPAIGN or SUPPRESS_ORG_WIDE, the CampaignCall status becomes SUPPRESSED (with suppressionReason). Org-wide suppression also adds the phone number to the Do Not Contact registry, which the dialer checks in real time before every call.
Restore a Suppressed Contact
POST /v1/campaigns/:id/suppressed/:campaignCallId/restoreLifts a campaign-level suppression and re-queues the contact (if the campaign is running). The action is recorded in the security log.
Request Body (optional): { "reason": "..." }
List the Do Not Contact Registry
GET /v1/dncQuery Parameters: page, limit
{
"data": [
{
"id": "dnc_abc123",
"phoneNumber": "+919876543210",
"sourceCampaignName": "Mumbai Outreach - March",
"intentLabel": "Opted Out",
"createdAt": "2026-05-31T09:09:00.000Z"
}
],
"pagination": { "page": 1, "limit": 50, "total": 1, "totalPages": 1 }
}Remove from Do Not Contact
DELETE /v1/dnc/:idRemoves the phone from the registry and clears the contact's Do Not Contact flag, allowing future contact. Recorded in the security log.
Frequency Cap
The frequency cap is an org-level setting (not per campaign) that limits how often a contact is called across all campaigns. Before every call — first attempt or retry — OliAI checks the contact's call count for the current day and the rolling 7-day window, and defers the call if a cap is reached.
Get the Frequency Cap
GET /v1/organizations/:id/frequency-cap{ "id": 1, "maxCallsPerDay": 3, "maxCallsPerWeek": 7 }Update the Frequency Cap
PATCH /v1/organizations/:id/frequency-cap| Field | Type | Description |
|---|---|---|
maxCallsPerDay | integer | Per contact, per calendar day (0–100; 0 disables) |
maxCallsPerWeek | integer | Per contact, rolling 7 days (0–100; 0 disables) |
Requires the ADMIN or SUPER_ADMIN role. Changes take effect immediately for future call scheduling; contacts already deferred keep their scheduled date. Deferred calls are recorded on the CampaignCall with deferralReason Frequency Cap - Daily or Frequency Cap - Weekly.