osmTalk Docs
Campaigns

Campaigns

Upload a list of leads, dial them with an AI agent on a controlled schedule, and track outcomes per row.

A Campaign runs an outbound voice agent against a list of phone numbers — typically uploaded as a CSV — with full control over scheduling, concurrency, retries, and per-lead variables. Every call's outcome (qualified, voicemail, no-answer, callback) is captured against the lead row so you get a clean post-campaign report.

What you can build

  • Lead-gen outreach — qualify cold or warm prospects from a CRM export
  • Renewal reminders — auto-call policy holders before expiry
  • Appointment confirmations — pre-call patients/clients the day before
  • Customer feedback / NPS — short post-purchase surveys
  • Membership renewals / collections — friendly first-touch before escalation

Lifecycle

draft ─→ scheduled ─→ running ←─→ paused ─→ completed

                       failed (if all leads fail) ┘
StateWhat's allowed
draftEdit config, upload leads, delete
scheduledEdit config; will auto-start at scheduledStartAt
runningPause, stop
pausedResume, stop
completedView report, export, delete
failedView report, delete

Quick start

1. Create a campaign

curl -X POST https://api.osmtalk.com/api/campaigns \
  -H "Authorization: Bearer $OSMTALK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Q2 Renewals — Mumbai",
    "agentId": "agent_xxx",
    "phoneNumberId": "pn_xxx",
    "maxConcurrent": 5,
    "schedule": {
      "timezone": "Asia/Kolkata",
      "windowStart": "10:00",
      "windowEnd": "18:00",
      "weekdays": [1, 2, 3, 4, 5]
    },
    "retryPolicy": {
      "maxAttempts": 3,
      "backoffMinutes": 60,
      "retryOn": ["no_answer", "busy"]
    },
    "webhookUrl": "https://your-crm.example.com/webhooks/osmtalk"
  }'

2. Upload leads (CSV)

curl -X POST https://api.osmtalk.com/api/campaigns/{campaignId}/leads \
  -H "Authorization: Bearer $OSMTALK_API_KEY" \
  -H "Content-Type: text/csv" \
  --data-binary @leads.csv

The CSV format is documented in CSV format — short version: one phone column required, every other column becomes a {{variable}} your agent prompt can reference.

3. Start

curl -X POST https://api.osmtalk.com/api/campaigns/{campaignId}/start

The campaign immediately transitions to running and the in-process dialer begins placing calls at the next 15-second tick.

4. Watch outcomes

curl https://api.osmtalk.com/api/campaigns/{campaignId}/report

Returns per-status and per-disposition counts. The dashboard's Campaigns → Live Monitor page shows the same data live.

How dialing works

Every 15 seconds the dialer:

  1. Iterates every running campaign
  2. Skips those outside their schedule.windowStartwindowEnd in their timezone
  3. Calculates available concurrency: min(campaign.maxConcurrent − active_calls_for_this_campaign, org_remaining_capacity)
  4. Pulls up to that many leads where status='pending' OR (status in ('no_answer','busy','failed') AND nextAttemptAt <= now)
  5. Scrubs each against the DNC list — DNC matches go straight to dnc_blocked
  6. Spawns a call per remaining lead with the lead's row data injected as dynamicVariables

When the call ends:

  • The post-call analyzer (if enabled on the agent) extracts structured fields
  • The disposition is set based on analysis.lead_qualified / analysis.next_action
  • The webhook URL receives a campaign.lead_completed event
  • If the outcome is retryable and attempts < maxAttempts, nextAttemptAt is set to now + backoffMinutes

The campaign auto-transitions to completed once every lead has reached a terminal status.

Personalization with {{variables}}

Each CSV row's columns become per-call variables. Reference them in the agent's system prompt or welcome message:

System prompt:

You're calling {{first_name}} from {{company}} about their {{policy_type}} policy
renewal due {{renewal_date}}. The new premium is ₹{{premium}}.

CSV row:

phone,first_name,company,policy_type,renewal_date,premium
+919876543210,Arjun,Acme,Health,2026-05-28,12450

When this lead is dialed, the bot speaks: "Hi Arjun, I'm calling from Acme about your Health policy renewal due 2026-05-28. The new premium is ₹12,450 …"

See Dynamic Variables for the full template syntax.

Outcomes (dispositions)

After each call:

DispositionMeaning
qualifiedLead expressed interest or matched success criteria
not_interestedCaller declined
callbackCaller asked to be called later
voicemailHit voicemail; bot may have left a message
no_answerNo one picked up — will retry if policy allows
busyLine busy — will retry
failedBot crashed / SIP error — will retry if policy allows
dncPhone matched the DNC list — never dialed

Dispositions come from analysis.next_action (when post-call analysis is enabled) or call status (otherwise).

What's next