API Reference
Campaigns

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

StatusDescription
DRAFTCreated but not started
SCHEDULEDWaiting to start at scheduledAt time
IN_PROGRESSActively dialing contacts
PAUSEDTemporarily halted, can be resumed
COMPLETEDAll contacts called
CANCELLEDPermanently stopped

List Campaigns

GET /v1/campaigns

Query Parameters:

ParameterTypeDescription
pageintegerPage number (default: 1)
limitintegerItems per page (default: 20, max: 100)
statusstringFilter by status (e.g., IN_PROGRESS)
searchstringSearch by campaign name

Get a Campaign

GET /v1/campaigns/:id

Create a Campaign

POST /v1/campaigns

Request Body:

FieldTypeRequiredDescription
namestringYesCampaign name
useCaseTypestringYesOne of COLLECTIONS, LEAD_QUALIFICATION, MEDICAL_BOOKING, CUSTOMER_SUPPORT, EDUTECH, CUSTOM. Seeds the campaign's intent labels.
flowIdstringYesID of the voice flow to use
audienceIdstringYesID of the audience to call
callsPerMinuteintegerNoDefault: 10
maxConcurrentCallsintegerNoDefault: 5
maxRetriesintegerNoRetries per contact, 0–10. Default: 3
retryDelayMinutesintegerNoFlat delay, 30–4320 min. Default: 60
retryDelaysMinutesinteger[]NoPer-attempt delay schedule (minutes). Overrides retryDelayMinutes when non-empty. See Retry Policy.
retryWithinWindowbooleanNoDefer retries into the calling window. Default: true
bestTimeOptimizationbooleanNoSchedule retries within each contact's preferred time band. Default: false
endAtstring (ISO 8601)NoCampaign deadline; retries past it are skipped
callTimeoutMinutesintegerNoDefault: 5
scheduledAtstring (ISO 8601)NoFuture start time
timezonestringNoRequired 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/start

Transitions 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/pause

Stops new calls from being initiated. Calls in progress continue to completion.

Response: 200 OK with { "status": "PAUSED" }.


Resume a Campaign

POST /v1/campaigns/:id/resume

Continues a paused campaign from where it left off.

Response: 200 OK with { "status": "IN_PROGRESS" }.


Cancel a Campaign

POST /v1/campaigns/:id/cancel

Permanently stops the campaign. This cannot be undone.

Response: 200 OK with { "status": "CANCELLED" }.


Get Campaign Progress

GET /v1/campaigns/:id/progress

Response:

{
  "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/dashboard

Query parameters (all optional):

ParameterDescription
useCaseTypeFilter campaigns by use case type
statusFilter by campaign status
startDateFromOnly campaigns created on/after this ISO date
startDateToOnly 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), or null until any call resolves.
  • positiveRate = positive-intent connected ÷ connected, or null when there are no connected calls.
  • health.level is green / amber / red; reason is 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/calls

Returns 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:

FieldDescription
intentLabelThe assigned label (null when undetermined or not yet analysed)
intentSourceauto (AI-classified), system (e.g. unanswered → Not Reachable), or null
intentUndeterminedtrue when the call could not be classified (no/short transcript, or AI failure)
journeyActionThe journey-rule action applied at the last tagging (null if not yet routed)
flaggedForReviewtrue when a FLAG_REVIEW rule fired for the call
suppressedFromCampaigntrue when suppressed from further retries in this campaign
suppressionReasonWhy 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
}
FieldDescription
colorHex swatch (#rrggbb) for the label badge
isPositiveCounts as a positive outcome
retryBehaviorRETRYABLE, SUPPRESS_CAMPAIGN, or SUPPRESS_ORG_WIDE — see Suppression
suppressRetriesDerived (true when retryBehavior is not RETRYABLE). Read-only.
isSystemRequired label (e.g. Not Reachable) — cannot be deleted, only renamed
isCustomAdmin-added label (max 10 per campaign)
sortOrderPriority order; lower = higher priority when resolving ambiguous calls

List Intent Labels

GET /v1/campaigns/:id/intent-labels

Returns the campaign's labels ordered by sortOrder.

Add a Custom Label

POST /v1/campaigns/:id/intent-labels
FieldTypeRequiredDescription
namestringYesLabel name
colorstringNoHex colour, e.g. #16a34a
isPositivebooleanNoDefault false
retryBehaviorstringNoRETRYABLE (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/:labelId

Any subset of name, color, isPositive, retryBehavior. System labels can be renamed/recoloured but not deleted.

Reorder Labels

PATCH /v1/campaigns/:id/intent-labels/reorder
FieldTypeRequiredDescription
orderedIdsstring[]YesAll label IDs in the desired priority order

Delete a Label

DELETE /v1/campaigns/:id/intent-labels/:labelId

Returns 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

ActionEffect
RETRYRe-dial the contact after retryDelayMinutes (or the campaign default), respecting the calling window and max-retries cap
SUPPRESS_CAMPAIGNNo further retries for this contact in this campaign
SUPPRESS_ORG_WIDEAdd the contact to the org-wide Do Not Contact list (skipped by all campaigns)
FLAG_REVIEWFlag the call for human review in the dashboard
COMMITMENT_REMINDERTrigger a commitment reminder (no-op until Commitment Capture ships)
MARK_COMPLETEDNo 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-rules

Returns the configured rules, each with its intent label.

Set a Rule

PUT /v1/campaigns/:id/journey-rules/:labelId

Creates or replaces the rule for the given intent label.

FieldTypeRequiredDescription
actionstringYesOne of the actions above
retryDelayMinutesintegerNoRETRY only — overrides the campaign default (1–10080)

Delete a Rule

DELETE /v1/campaigns/:id/journey-rules/:labelId

Removes the rule; the label reverts to the MARK_COMPLETED default.


Update a Campaign

PATCH /v1/campaigns/:id
⚠️

Campaigns 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-policy

Updates 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 —

FieldTypeDescription
maxRetriesintegerRetries per contact, 0–10
retryDelayMinutesintegerFlat delay, 30–4320 min (used when retryDelaysMinutes is empty)
retryDelaysMinutesinteger[]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.
retryWithinWindowbooleanWhen false, retries bypass the calling-window deferral
bestTimeOptimizationbooleanSchedule retries within the contact's preferred time band (always inside the calling window)
endAtstring | nullCampaign 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/fulfill

Response: 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/restore

Lifts 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/dnc

Query 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/:id

Removes 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
FieldTypeDescription
maxCallsPerDayintegerPer contact, per calendar day (0–100; 0 disables)
maxCallsPerWeekintegerPer 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.