Skip to content

Campaigns

Outbound calling campaigns, their contacts, per-contact results, follow-ups and extracted data.

POST /campaigns

Creates a DRAFT outbound calling campaign in your organisation. The body’s organisationId must be the key’s own organisation and the agent must belong to it. Upload contacts next, then start it.

Scope: campaigns:write

Body (application/json)

Field Type Required Description
name string yes
organisationId string yes Your organisation ID. Must equal the key’s own organisation.
voiceAgentId string yes Agent that makes the calls. Must be a live agent in your organisation.
callStartTime string yes Daily window start, HH:MM in the campaign timezone. Must be before callEndTime.
callEndTime string yes Daily window end, HH:MM.
description string no
targetCountryCode string no Dial code used to normalise uploaded numbers. Defaults to +1 when omitted, so send +91 for Indian numbers.
dialingNumber string no Caller number. For an agent that calls through a carrier account (Plivo, Twilio, Exotel, FreJun, VoiceLink) it must be the agent’s own calling number or an active, voice-capable number on that same carrier account in your organisation (400 otherwise); the campaign then calls from it. For an agent without a carrier it must match an active outbound SIP configuration in your organisation (404 otherwise). Omit it to call from the agent’s own number.
retryCount integer no
cooldownSeconds integer no Wait between retries.
maxCallDuration integer no Seconds.
inactivityDuration integer no Seconds of silence before the call ends.
startDate string no ISO 8601 date or YYYY-MM-DD. Optional.
endDate string no ISO 8601 date or YYYY-MM-DD. Must not be before today (in the campaign timezone) or before startDate.
timezone string no IANA timezone such as Asia/Kolkata.
dataExtractionSchema object no Post-campaign data extraction configuration.
followUpRules object no Follow-up rules. Omit to leave unchanged, send null to remove them. Missing keys take defaults (enabled true, results [no_answer, not_reached, voicemail], maxRounds 1, delayDays 1, windowDays 7, autoStart true). Booleans also accept “true”/“false”/“1”/“0”; anything else is a 400.

Example request

Terminal window
curl -X POST "https://api.foan.ai/api/v1/campaigns" \
-H "Authorization: Bearer $FOAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"Diwali offer reminders","organisationId":"cmf1a2b3c0000orgx5678ijkl","voiceAgentId":"cmf2b3c4d0002agnt9012mnop","callStartTime":"10:00","callEndTime":"19:00","timezone":"Asia/Kolkata","targetCountryCode":"+91","retryCount":2,"cooldownSeconds":1800,"startDate":"2026-10-01","endDate":"2026-10-07","dialingNumber":"+918071234567"}'

Response 201

{
"success": true,
"data": {
"id": "cmf3k2j9x0001abcd1234efgh",
"name": "Diwali offer reminders",
"description": "Remind past buyers about the festive offer",
"organisationId": "cmf1a2b3c0000orgx5678ijkl",
"createdById": "cmf0z9y8x0003user3456qrst",
"createdBy": {
"id": "cmf0z9y8x0003user3456qrst",
"name": "Priya Sharma",
"email": "priya@example.com"
},
"voiceAgentId": "cmf2b3c4d0002agnt9012mnop",
"dialingNumber": "+918071234567",
"voiceAgent": {
"id": "cmf2b3c4d0002agnt9012mnop",
"name": "Order confirmation",
"customVariables": {
"orderId": "",
"city": ""
},
"callingNumber": "+918071234567",
"telephonyProvider": "plivo"
},
"targetCountryCode": "+91",
"callStartTime": "10:00",
"callEndTime": "19:00",
"startDate": "2026-10-01T00:00:00.000Z",
"endDate": "2026-10-07T23:59:59.999Z",
"timezone": "Asia/Kolkata",
"retryCount": 2,
"cooldownSeconds": 1800,
"maxCallDuration": 300,
"inactivityDuration": 30,
"status": "DRAFT",
"totalContacts": 0,
"completedCalls": 0,
"failedCalls": 0,
"inProgressCalls": 0,
"uploadedFileName": null,
"uploadedFileUrl": null,
"dataExtractionSchema": null,
"followUpRound": 0,
"followUpRules": null,
"createdAt": "2026-09-25T09:30:00.000Z",
"updatedAt": "2026-09-25T09:30:00.000Z"
},
"message": "Campaign created successfully"
}

Errors

Code When
400 Validation failed: missing fields, bad HH:MM, start not before end, bad timezone, dates in the past, invalid followUpRules, or a dialingNumber that is not an active number on the agent’s carrier account
401 Missing, invalid or revoked API key
403 Key lacks campaigns:write, or organisationId is not the key’s organisation
404 Voice agent not in this organisation, dialingNumber has no active outbound SIP configuration (agents without a carrier), or a follow-up agent/number is not in this organisation
429 Rate limited

GET /campaigns/organisations/{organisationId}

Lists your organisation’s campaigns, newest first. For running or paused campaigns the counters are recomputed live from the contact list.

Scope: campaigns:read

Parameters

Name In Type Required Description
organisationId path string yes Your organisation ID. Must be the key’s own organisation.
page query integer no Page number, starting at 1.
limit query integer no Items per page (1 to 100).

Example request

Terminal window
curl -X GET "https://api.foan.ai/api/v1/campaigns/organisations/{organisationId}" \
-H "Authorization: Bearer $FOAN_API_KEY"

Response 200

{
"success": true,
"data": {
"campaigns": [
{
"id": "cmf3k2j9x0001abcd1234efgh",
"name": "Diwali offer reminders",
"description": "Remind past buyers about the festive offer",
"organisationId": "cmf1a2b3c0000orgx5678ijkl",
"createdById": "cmf0z9y8x0003user3456qrst",
"createdBy": {
"id": "cmf0z9y8x0003user3456qrst",
"name": "Priya Sharma",
"email": "priya@example.com"
},
"voiceAgentId": "cmf2b3c4d0002agnt9012mnop",
"dialingNumber": "+918071234567",
"voiceAgent": {
"id": "cmf2b3c4d0002agnt9012mnop",
"name": "Order confirmation",
"customVariables": {
"orderId": "",
"city": ""
},
"callingNumber": "+918071234567",
"telephonyProvider": "plivo"
},
"targetCountryCode": "+91",
"callStartTime": "10:00",
"callEndTime": "19:00",
"startDate": "2026-10-01T00:00:00.000Z",
"endDate": "2026-10-07T23:59:59.999Z",
"timezone": "Asia/Kolkata",
"retryCount": 2,
"cooldownSeconds": 1800,
"maxCallDuration": 300,
"inactivityDuration": 30,
"status": "IN_PROGRESS",
"totalContacts": 250,
"completedCalls": 120,
"failedCalls": 18,
"inProgressCalls": 3,
"uploadedFileName": "diwali_buyers.csv",
"uploadedFileUrl": null,
"dataExtractionSchema": null,
"followUpRound": 0,
"followUpRules": null,
"createdAt": "2026-09-25T09:30:00.000Z",
"updatedAt": "2026-09-25T09:30:00.000Z",
"startedAt": "2026-10-01T04:30:00.000Z"
}
],
"total": 1,
"totalPages": 1,
"currentPage": 1
},
"message": "Campaigns retrieved successfully"
}

Errors

Code When
400 Invalid page or limit
401 Missing, invalid or revoked API key
403 Key lacks campaigns:read, or organisationId is not the key’s organisation
429 Rate limited

GET /campaigns/{id}

Returns one campaign. For running or paused campaigns the contact counters are recomputed live.

Scope: campaigns:read

Parameters

Name In Type Required Description
id path string yes Campaign ID. The campaign must belong to the key’s organisation.

Example request

Terminal window
curl -X GET "https://api.foan.ai/api/v1/campaigns/{id}" \
-H "Authorization: Bearer $FOAN_API_KEY"

Response 200

{
"success": true,
"data": {
"id": "cmf3k2j9x0001abcd1234efgh",
"name": "Diwali offer reminders",
"description": "Remind past buyers about the festive offer",
"organisationId": "cmf1a2b3c0000orgx5678ijkl",
"createdById": "cmf0z9y8x0003user3456qrst",
"createdBy": {
"id": "cmf0z9y8x0003user3456qrst",
"name": "Priya Sharma",
"email": "priya@example.com"
},
"voiceAgentId": "cmf2b3c4d0002agnt9012mnop",
"dialingNumber": "+918071234567",
"voiceAgent": {
"id": "cmf2b3c4d0002agnt9012mnop",
"name": "Order confirmation",
"customVariables": {
"orderId": "",
"city": ""
},
"callingNumber": "+918071234567",
"telephonyProvider": "plivo"
},
"targetCountryCode": "+91",
"callStartTime": "10:00",
"callEndTime": "19:00",
"startDate": "2026-10-01T00:00:00.000Z",
"endDate": "2026-10-07T23:59:59.999Z",
"timezone": "Asia/Kolkata",
"retryCount": 2,
"cooldownSeconds": 1800,
"maxCallDuration": 300,
"inactivityDuration": 30,
"status": "PAUSED",
"totalContacts": 250,
"completedCalls": 140,
"failedCalls": 20,
"inProgressCalls": 0,
"uploadedFileName": "diwali_buyers.csv",
"uploadedFileUrl": null,
"dataExtractionSchema": null,
"followUpRound": 0,
"followUpRules": null,
"createdAt": "2026-09-25T09:30:00.000Z",
"updatedAt": "2026-09-25T09:30:00.000Z",
"uploadedAt": "2026-09-25T09:40:00.000Z",
"startedAt": "2026-10-01T04:30:00.000Z",
"pausedAt": "2026-10-02T08:00:00.000Z"
},
"message": "Campaign retrieved successfully"
}

Errors

Code When
401 Missing, invalid or revoked API key
403 Key lacks the campaigns:read scope, or the campaign belongs to another organisation
404 Campaign not found
429 Rate limited

PUT /campaigns/{id}

Partially updates a campaign’s settings; send only the fields to change. Status cannot be changed here. The country code is locked once contacts exist.

Scope: campaigns:write

Parameters

Name In Type Required Description
id path string yes Campaign ID. The campaign must belong to the key’s organisation.

Body (application/json)

Field Type Required Description
name string no
voiceAgentId string no Agent that makes the calls. Must be a live agent in your organisation. Changing it re-checks the campaign’s saved caller number against the new agent (400 or 404 when it no longer fits). When the campaign has no saved number and the new agent calls through a carrier account, the new agent’s own calling number is saved.
callStartTime string no Daily window start, HH:MM in the campaign timezone. Must be before callEndTime.
callEndTime string no Daily window end, HH:MM.
description string no
targetCountryCode string no Can only change while the campaign has no contacts (409 otherwise). “+91”, “91” and “ +91 “ count as the same country.
dialingNumber string no Caller number. For an agent that calls through a carrier account it must be the agent’s own calling number or an active, voice-capable number on that carrier account in your organisation (400 otherwise). For an agent without a carrier it must match an active outbound SIP configuration in your organisation (404 otherwise). Empty string or null clears the saved number.
retryCount integer no
cooldownSeconds integer no Wait between retries.
maxCallDuration integer no Seconds.
inactivityDuration integer no Seconds of silence before the call ends.
startDate string no ISO 8601 or YYYY-MM-DD; null clears it.
endDate string no ISO 8601 or YYYY-MM-DD; null clears it. Must not be in the past.
timezone string no IANA timezone.
dataExtractionSchema object no Post-campaign data extraction configuration.
followUpRules object no Follow-up rules. Omit to leave unchanged, send null to remove them. Keys you leave out keep the campaign’s current rules (or the defaults when it has none: enabled true, results [no_answer, not_reached, voicemail], maxRounds 1, delayDays 1, windowDays 7, autoStart true). Booleans also accept “true”/“false”/“1”/“0”; anything else is a 400.
status string: DRAFT, SCHEDULED, IN_PROGRESS, PAUSED, COMPLETED, CANCELLED no Not settable here: any value is refused with 400. Use start, pause or complete.

Example request

Terminal window
curl -X PUT "https://api.foan.ai/api/v1/campaigns/{id}" \
-H "Authorization: Bearer $FOAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"callStartTime":"11:00","callEndTime":"18:30","retryCount":1,"followUpRules":{"enabled":true,"results":["no_answer","voicemail"],"maxRounds":2}}'

Response 200

{
"success": true,
"data": {
"id": "cmf3k2j9x0001abcd1234efgh",
"name": "Diwali offer reminders",
"description": "Remind past buyers about the festive offer",
"organisationId": "cmf1a2b3c0000orgx5678ijkl",
"createdById": "cmf0z9y8x0003user3456qrst",
"createdBy": {
"id": "cmf0z9y8x0003user3456qrst",
"name": "Priya Sharma",
"email": "priya@example.com"
},
"voiceAgentId": "cmf2b3c4d0002agnt9012mnop",
"dialingNumber": "+918071234567",
"voiceAgent": {
"id": "cmf2b3c4d0002agnt9012mnop",
"name": "Order confirmation",
"callingNumber": "+918071234567",
"telephonyProvider": "plivo"
},
"targetCountryCode": "+91",
"callStartTime": "11:00",
"callEndTime": "18:30",
"startDate": "2026-10-01T00:00:00.000Z",
"endDate": "2026-10-07T23:59:59.999Z",
"timezone": "Asia/Kolkata",
"retryCount": 1,
"cooldownSeconds": 1800,
"maxCallDuration": 300,
"inactivityDuration": 30,
"status": "DRAFT",
"totalContacts": 0,
"completedCalls": 0,
"failedCalls": 0,
"inProgressCalls": 0,
"uploadedFileName": null,
"uploadedFileUrl": null,
"dataExtractionSchema": null,
"followUpRound": 0,
"followUpRules": {
"enabled": true,
"results": [
"no_answer",
"voicemail"
],
"maxRounds": 2,
"delayDays": 1,
"windowDays": 7,
"voiceAgentId": null,
"sipConfigurationId": null,
"autoStart": true
},
"createdAt": "2026-09-25T09:30:00.000Z",
"updatedAt": "2026-09-25T11:00:00.000Z"
},
"message": "Campaign updated successfully"
}

Errors

Code When
400 Validation failed, status sent, invalid dates or followUpRules, or a dialingNumber (sent, or kept when the agent changes) that is not an active number on the agent’s carrier account
401 Missing, invalid or revoked API key
403 Key lacks the campaigns:write scope, or the campaign belongs to another organisation
404 Campaign not found, agent not in this organisation, dialingNumber not an active outbound number, or follow-up agent/number not in this organisation
409 Country change on a campaign that already has contacts, or the campaign is busy with a contact upload
429 Rate limited

DELETE /campaigns/{id}

Permanently deletes a campaign and all its contacts and extracted data. A campaign that is IN_PROGRESS cannot be deleted; pause or complete it first. Call records of calls already made stay in call history.

Scope: campaigns:write

Parameters

Name In Type Required Description
id path string yes Campaign ID. The campaign must belong to the key’s organisation.

Example request

Terminal window
curl -X DELETE "https://api.foan.ai/api/v1/campaigns/{id}" \
-H "Authorization: Bearer $FOAN_API_KEY"

Response 200

{
"success": true,
"data": null,
"message": "Campaign deleted successfully"
}

Errors

Code When
400 Campaign is in progress
401 Missing, invalid or revoked API key
403 Key lacks the campaigns:write scope, or the campaign belongs to another organisation
404 Campaign not found
409 The campaign is busy with a contact upload or copy; retry shortly
429 Rate limited

POST /campaigns/{id}/duplicate

Creates a new DRAFT campaign with the same agent, caller number, schedule, retry settings and extraction schema. Contacts are copied only when includeContacts is true, as a fresh PENDING list with no call history (duplicate numbers collapsed). Follow-up rules are not copied.

Scope: campaigns:write

Parameters

Name In Type Required Description
id path string yes Campaign ID. The campaign must belong to the key’s organisation.

Body (application/json)

Field Type Required Description
name string no Name of the copy. Defaults to “<source name> (copy)”.
includeContacts boolean no Copy the contact list. Also accepts “true”/“false”/“1”/“0”.

Example request

Terminal window
curl -X POST "https://api.foan.ai/api/v1/campaigns/{id}/duplicate" \
-H "Authorization: Bearer $FOAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"Diwali offer reminders (copy)","includeContacts":true}'

Response 201

{
"success": true,
"data": {
"id": "cmf3k2j9x0009abcd1234wxyz",
"name": "Diwali offer reminders (copy)",
"description": "Remind past buyers about the festive offer",
"organisationId": "cmf1a2b3c0000orgx5678ijkl",
"createdById": "cmf0z9y8x0003user3456qrst",
"createdBy": {
"id": "cmf0z9y8x0003user3456qrst",
"name": "Priya Sharma",
"email": "priya@example.com"
},
"voiceAgentId": "cmf2b3c4d0002agnt9012mnop",
"dialingNumber": "+918071234567",
"voiceAgent": {
"id": "cmf2b3c4d0002agnt9012mnop",
"name": "Order confirmation",
"customVariables": {
"orderId": "",
"city": ""
},
"callingNumber": "+918071234567",
"telephonyProvider": "plivo"
},
"targetCountryCode": "+91",
"callStartTime": "10:00",
"callEndTime": "19:00",
"startDate": null,
"endDate": null,
"timezone": "Asia/Kolkata",
"retryCount": 2,
"cooldownSeconds": 1800,
"maxCallDuration": 300,
"inactivityDuration": 30,
"status": "DRAFT",
"totalContacts": 250,
"completedCalls": 0,
"failedCalls": 0,
"inProgressCalls": 0,
"uploadedFileName": "diwali_buyers.csv",
"uploadedFileUrl": null,
"dataExtractionSchema": null,
"followUpRound": 0,
"followUpRules": null,
"createdAt": "2026-09-25T09:30:00.000Z",
"updatedAt": "2026-09-25T09:30:00.000Z",
"sipConfigurationId": null,
"sipConfiguration": null,
"parentCampaignId": null,
"followUpSettledAt": null,
"followUpDecidedAt": null,
"uploadedAt": "2026-09-25T10:00:00.000Z",
"startedAt": null,
"pausedAt": null,
"completedAt": null,
"copiedContacts": 250,
"scheduleCleared": true
},
"message": "Campaign duplicated with 250 contacts. Its dates had already passed, so the copy has no date range yet - set new dates before starting it."
}

Errors

Code When
400 Validation failed (the error message lists the problems)
401 Missing, invalid or revoked API key
403 Key lacks the campaigns:write scope, or the campaign belongs to another organisation
404 Campaign not found
409 The source campaign is busy with another contact upload; retry shortly
429 Rate limited

GET /campaigns/{id}/follow-up

Shows what a follow-up round of this campaign would call: a count per call-result bucket, how many contacts the stored (or default) selection would dial, and the rounds already created. Read only, nothing is created.

Scope: campaigns:read

Parameters

Name In Type Required Description
id path string yes Campaign ID. The campaign must belong to the key’s organisation.

Example request

Terminal window
curl -X GET "https://api.foan.ai/api/v1/campaigns/{id}/follow-up" \
-H "Authorization: Bearer $FOAN_API_KEY"

Response 200

{
"success": true,
"data": {
"campaignId": "cmf3k2j9x0001abcd1234efgh",
"name": "Diwali offer reminders",
"status": "COMPLETED",
"timezone": "Asia/Kolkata",
"followUpRound": 0,
"rules": null,
"roundsLeft": 0,
"buckets": [
{
"id": "no_answer",
"label": "Did not answer",
"count": 42,
"canFollowUp": true,
"selected": true
},
{
"id": "not_reached",
"label": "Never reached",
"count": 8,
"canFollowUp": true,
"selected": true
},
{
"id": "voicemail",
"label": "Went to voicemail",
"count": 11,
"canFollowUp": true,
"selected": true
},
{
"id": "hung_up",
"label": "Hung up",
"count": 6,
"canFollowUp": true,
"selected": false
},
{
"id": "callback",
"label": "Asked us to call back",
"count": 9,
"canFollowUp": true,
"selected": false
},
{
"id": "unclear",
"label": "Unclear",
"count": 5,
"canFollowUp": true,
"selected": false
},
{
"id": "interested",
"label": "Interested",
"count": 64,
"canFollowUp": true,
"selected": false
},
{
"id": "not_interested",
"label": "Not interested",
"count": 90,
"canFollowUp": false,
"selected": false
},
{
"id": "do_not_call",
"label": "Asked not to be called",
"count": 3,
"canFollowUp": false,
"selected": false
},
{
"id": "wrong_person",
"label": "Wrong person",
"count": 12,
"canFollowUp": false,
"selected": false
}
],
"eligibleContacts": 61,
"excludedFamilyOptOuts": 0,
"excludedUnknownOutcomes": 2,
"rounds": [],
"rootCampaignId": "cmf3k2j9x0001abcd1234efgh",
"rootCampaignName": "Diwali offer reminders",
"family": [
{
"id": "cmf3k2j9x0001abcd1234efgh",
"name": "Diwali offer reminders",
"organisationId": "cmf1a2b3c0000orgx5678ijkl",
"parentCampaignId": null,
"followUpRound": 0,
"status": "COMPLETED",
"totalContacts": 250,
"completedCalls": 180,
"failedCalls": 62,
"startDate": "2026-10-01T00:00:00.000Z",
"endDate": "2026-10-07T23:59:59.999Z",
"createdAt": "2026-09-25T09:30:00.000Z",
"isOriginal": true
}
]
},
"message": "Follow-up preview retrieved"
}

Errors

Code When
401 Missing, invalid or revoked API key
403 Key lacks the campaigns:read scope, or the campaign belongs to another organisation
404 Campaign not found
409 The follow-up chain is inconsistent (cycle, missing member, more than 5 rounds or more than 200 campaigns)
429 Rate limited

POST /campaigns/{id}/follow-up

Creates the next follow-up round of a COMPLETED campaign: a new campaign with the same settings (and the same caller number, unless the rules pick a different agent) holding only contacts whose last call result is in the selected buckets. People who said not interested, do not call or wrong person in any round are never included, and neither is anyone who was dialled but has no recorded result. A campaign family (the original and all its rounds) can have at most 5 follow-up rounds in total, whichever member you start from. With autoStart on (the default) the round is started straight away, which schedules real calls from the round’s start date.

Scope: campaigns:write

Parameters

Name In Type Required Description
id path string yes Campaign ID. The campaign must belong to the key’s organisation.

Body (application/json)

Field Type Required Description
name string no Name for the round. Defaults to “<source name> (follow-up N)”.
results array of string no Buckets to call again. not_interested, do_not_call and wrong_person are refused with 400. not_reached selects contacts nobody ever dialled.
autoStart boolean no Start the round immediately (true by default when neither this request nor the campaign’s stored rules say otherwise).
maxRounds integer no
delayDays integer no
windowDays integer no
voiceAgentId string no Agent for the round; must be in your organisation.
sipConfigurationId string no Outbound number for the round; must be an active outbound SIP configuration in your organisation.
rules object no Alternatively send the same rule keys nested here instead of flat.

Example request

Terminal window
curl -X POST "https://api.foan.ai/api/v1/campaigns/{id}/follow-up" \
-H "Authorization: Bearer $FOAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"results":["no_answer","not_reached","callback"],"delayDays":1,"windowDays":3,"autoStart":false}'

Response 200

{
"success": true,
"data": {
"campaign": null,
"contacts": 0,
"round": 1
},
"message": "Nobody from this campaign needs calling again, so no follow-up was created"
}

Errors

Code When
400 Invalid rules (unknown or never-follow-up result, out-of-range numbers, bad boolean, empty selection), or the campaign family already has 5 follow-up rounds
401 Missing, invalid or revoked API key
403 Key lacks the campaigns:write scope, or the campaign belongs to another organisation
404 Campaign not found, or the follow-up agent or number is not in this organisation
409 Campaign is not COMPLETED, another round of it is still draft/scheduled/running/paused, the campaign is busy, or the follow-up chain is inconsistent
429 Rate limited

GET /campaigns/{campaignId}/follow-ups/history

Lists the calls made by this campaign and every round in its follow-up chain (original plus rounds), newest first. Pass roundCampaignId to see one round only.

Scope: campaigns:read

Parameters

Name In Type Required Description
campaignId path string yes Campaign ID. The campaign must belong to the key’s organisation.
roundCampaignId query string no Limit to one campaign in the chain. Must be a member of this campaign’s follow-up family (400 otherwise).
page query integer no Page number, starting at 1.
limit query integer no Items per page (1 to 100).

Example request

Terminal window
curl -X GET "https://api.foan.ai/api/v1/campaigns/{campaignId}/follow-ups/history" \
-H "Authorization: Bearer $FOAN_API_KEY"

Response 200

{
"success": true,
"data": [
{
"id": "66f1c2e8a4b5c6d7e8f90123",
"roomName": "call-7f3a9c2e",
"agentId": "cmf2b3c4d0002agnt9012mnop",
"agentName": "Order confirmation",
"orgId": "cmf1a2b3c0000orgx5678ijkl",
"participantName": "Rahul Verma",
"phoneNumber": "+919876543210",
"callStatus": "Completed",
"startTime": "2026-10-09T05:12:00.000Z",
"createdAt": "2026-10-09T05:12:00.000Z",
"endedAt": "2026-10-09T05:13:34.000Z",
"callDuration": 94000,
"callOutcome": "completed",
"callOutcomeDetail": null,
"recordingUrl": "/api/v1/voice-sessions/66f1c2e8a4b5c6d7e8f90123/recording",
"customVariables": {
"participantName": "Rahul Verma"
},
"callDirection": "OUTBOUND",
"callType": "websocket",
"scheduledTime": null,
"scheduledStatus": null,
"campaignId": "cmf3k2j9x0009abcd1234wxyz",
"campaign": {
"id": "cmf3k2j9x0009abcd1234wxyz",
"name": "Diwali offer reminders (follow-up 1)",
"status": "IN_PROGRESS",
"followUpRound": 1
},
"campaignName": "Diwali offer reminders (follow-up 1)",
"followUpRound": 1,
"overview": {
"score": 78,
"classification": "Relevant",
"sentiment": "positive",
"summary": [
"Customer asked for the offer link on WhatsApp."
]
},
"actions": {
"tracking": "not_recorded",
"items": []
},
"totalMessages": 14,
"userMessages": 7,
"assistantMessages": 7,
"totalTokens": 0,
"firstMessageAt": "2026-10-09T05:12:03.000Z",
"lastMessageAt": "2026-10-09T05:13:31.000Z"
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 1,
"totalPages": 1,
"hasMore": false
}
}

Errors

Code When
400 Invalid page/limit, empty roundCampaignId, or roundCampaignId not in this follow-up family
401 Missing, invalid or revoked API key
403 Key lacks the campaigns:read scope, or the campaign belongs to another organisation
404 Campaign not found
409 The follow-up chain is inconsistent
429 Rate limited

GET /campaigns/template/download/{campaignId}

Returns a CSV template for this campaign’s contact upload: phoneNumber, participantName, then one column per custom variable of the campaign’s agent, with three sample rows.

Scope: campaigns:read

Parameters

Name In Type Required Description
campaignId path string yes Campaign ID. The campaign must belong to the key’s organisation.

Example request

Terminal window
curl -X GET "https://api.foan.ai/api/v1/campaigns/template/download/{campaignId}" \
-H "Authorization: Bearer $FOAN_API_KEY"

Response 200

Errors

Code When
401 Missing, invalid or revoked API key
403 Key lacks the campaigns:read scope, or the campaign belongs to another organisation
404 Campaign not found
429 Rate limited
500 Template could not be built

POST /campaigns/{id}/start

Starts a DRAFT or PAUSED campaign that has contacts. Inside today’s call window it becomes IN_PROGRESS and dialling begins within about a minute; otherwise it becomes SCHEDULED and starts when the window opens. This places real calls.

Scope: campaigns:control

Parameters

Name In Type Required Description
id path string yes Campaign ID. The campaign must belong to the key’s organisation.

Example request

Terminal window
curl -X POST "https://api.foan.ai/api/v1/campaigns/{id}/start" \
-H "Authorization: Bearer $FOAN_API_KEY"

Response 200

{
"success": true,
"data": {
"id": "cmf3k2j9x0001abcd1234efgh",
"name": "Diwali offer reminders",
"description": "Remind past buyers about the festive offer",
"organisationId": "cmf1a2b3c0000orgx5678ijkl",
"createdById": "cmf0z9y8x0003user3456qrst",
"createdBy": {
"id": "cmf0z9y8x0003user3456qrst",
"name": "Priya Sharma",
"email": "priya@example.com"
},
"voiceAgentId": "cmf2b3c4d0002agnt9012mnop",
"dialingNumber": "+918071234567",
"voiceAgent": {
"id": "cmf2b3c4d0002agnt9012mnop",
"name": "Order confirmation",
"callingNumber": "+918071234567",
"telephonyProvider": "plivo"
},
"targetCountryCode": "+91",
"callStartTime": "10:00",
"callEndTime": "19:00",
"startDate": "2026-10-01T00:00:00.000Z",
"endDate": "2026-10-07T23:59:59.999Z",
"timezone": "Asia/Kolkata",
"retryCount": 2,
"cooldownSeconds": 1800,
"maxCallDuration": 300,
"inactivityDuration": 30,
"status": "IN_PROGRESS",
"totalContacts": 250,
"completedCalls": 0,
"failedCalls": 0,
"inProgressCalls": 0,
"uploadedFileName": null,
"uploadedFileUrl": null,
"dataExtractionSchema": null,
"followUpRound": 0,
"followUpRules": null,
"createdAt": "2026-09-25T09:30:00.000Z",
"updatedAt": "2026-09-25T09:30:00.000Z",
"startedAt": "2026-10-01T04:30:00.000Z"
},
"message": "Campaign started successfully"
}

Errors

Code When
400 Status is not DRAFT or PAUSED, the campaign has no contacts, or its date range has ended
401 Missing, invalid or revoked API key
403 Key lacks campaigns:control, or the campaign belongs to another organisation
404 Campaign not found
429 Rate limited: 20 control actions per minute per API key (default)

POST /campaigns/{id}/pause

Pauses an IN_PROGRESS campaign so no new calls are placed. Calls already in progress finish normally. Resume with start.

Scope: campaigns:control

Parameters

Name In Type Required Description
id path string yes Campaign ID. The campaign must belong to the key’s organisation.

Example request

Terminal window
curl -X POST "https://api.foan.ai/api/v1/campaigns/{id}/pause" \
-H "Authorization: Bearer $FOAN_API_KEY"

Response 200

{
"success": true,
"data": {
"id": "cmf3k2j9x0001abcd1234efgh",
"name": "Diwali offer reminders",
"description": "Remind past buyers about the festive offer",
"organisationId": "cmf1a2b3c0000orgx5678ijkl",
"createdById": "cmf0z9y8x0003user3456qrst",
"createdBy": {
"id": "cmf0z9y8x0003user3456qrst",
"name": "Priya Sharma",
"email": "priya@example.com"
},
"voiceAgentId": "cmf2b3c4d0002agnt9012mnop",
"dialingNumber": "+918071234567",
"voiceAgent": {
"id": "cmf2b3c4d0002agnt9012mnop",
"name": "Order confirmation",
"callingNumber": "+918071234567",
"telephonyProvider": "plivo"
},
"targetCountryCode": "+91",
"callStartTime": "10:00",
"callEndTime": "19:00",
"startDate": "2026-10-01T00:00:00.000Z",
"endDate": "2026-10-07T23:59:59.999Z",
"timezone": "Asia/Kolkata",
"retryCount": 2,
"cooldownSeconds": 1800,
"maxCallDuration": 300,
"inactivityDuration": 30,
"status": "PAUSED",
"totalContacts": 250,
"completedCalls": 0,
"failedCalls": 0,
"inProgressCalls": 0,
"uploadedFileName": null,
"uploadedFileUrl": null,
"dataExtractionSchema": null,
"followUpRound": 0,
"followUpRules": null,
"createdAt": "2026-09-25T09:30:00.000Z",
"updatedAt": "2026-09-25T09:30:00.000Z",
"startedAt": "2026-10-01T04:30:00.000Z",
"pausedAt": "2026-10-01T06:00:00.000Z"
},
"message": "Campaign paused successfully"
}

Errors

Code When
400 Only in-progress campaigns can be paused (a SCHEDULED campaign cannot)
401 Missing, invalid or revoked API key
403 Key lacks campaigns:control, or the campaign belongs to another organisation
404 Campaign not found
429 Rate limited: 20 control actions per minute per API key (default)

POST /campaigns/{id}/complete

Ends a campaign now. Contacts still waiting are marked SKIPPED, contacts mid-call are marked FAILED (not settled), counters are finalised and the status becomes COMPLETED. It is never dialled again. Works from any status.

Scope: campaigns:control

Parameters

Name In Type Required Description
id path string yes Campaign ID. The campaign must belong to the key’s organisation.

Example request

Terminal window
curl -X POST "https://api.foan.ai/api/v1/campaigns/{id}/complete" \
-H "Authorization: Bearer $FOAN_API_KEY"

Response 200

{
"success": true,
"data": {
"id": "cmf3k2j9x0001abcd1234efgh",
"name": "Diwali offer reminders",
"description": "Remind past buyers about the festive offer",
"organisationId": "cmf1a2b3c0000orgx5678ijkl",
"createdById": "cmf0z9y8x0003user3456qrst",
"createdBy": {
"id": "cmf0z9y8x0003user3456qrst",
"name": "Priya Sharma",
"email": "priya@example.com"
},
"voiceAgentId": "cmf2b3c4d0002agnt9012mnop",
"dialingNumber": "+918071234567",
"voiceAgent": {
"id": "cmf2b3c4d0002agnt9012mnop",
"name": "Order confirmation",
"callingNumber": "+918071234567",
"telephonyProvider": "plivo"
},
"targetCountryCode": "+91",
"callStartTime": "10:00",
"callEndTime": "19:00",
"startDate": "2026-10-01T00:00:00.000Z",
"endDate": "2026-10-07T23:59:59.999Z",
"timezone": "Asia/Kolkata",
"retryCount": 2,
"cooldownSeconds": 1800,
"maxCallDuration": 300,
"inactivityDuration": 30,
"status": "COMPLETED",
"totalContacts": 250,
"completedCalls": 180,
"failedCalls": 62,
"inProgressCalls": 0,
"uploadedFileName": null,
"uploadedFileUrl": null,
"dataExtractionSchema": null,
"followUpRound": 0,
"followUpRules": null,
"createdAt": "2026-09-25T09:30:00.000Z",
"updatedAt": "2026-09-25T09:30:00.000Z",
"startedAt": "2026-10-01T04:30:00.000Z",
"completedAt": "2026-10-03T13:30:00.000Z"
},
"message": "Campaign completed successfully"
}

Errors

Code When
401 Missing, invalid or revoked API key
403 Key lacks campaigns:control, or the campaign belongs to another organisation
404 Campaign not found
409 The campaign is busy with a contact upload; retry shortly
429 Rate limited: 20 control actions per minute per API key (default)
500 Unexpected failure

POST /campaigns/{campaignId}/contacts/upload

Uploads a contact file. By default it appends: numbers already on the campaign are skipped and existing call history is kept. mode=replace deletes every existing contact and their results first, and is only allowed on a DRAFT campaign. Numbers are normalised with the campaign’s country code.

Scope: campaigns:write

Parameters

Name In Type Required Description
campaignId path string yes Campaign ID. The campaign must belong to the key’s organisation.
mode query string: append, replace no append (default) keeps existing contacts; replace wipes them first (DRAFT only).

Body (multipart/form-data)

Field Type Required Description
file file yes One .csv or .xlsx file, at most 8 MB, sent in the form field “file”. Only the first sheet of a workbook is read and its first row must be the headers. Old Excel 97-2003 .xls files are refused (re-save as .xlsx or .csv). Needs a phone column named phone, phoneNumber, phone_number, number or mobile (case-insensitive); rows with an invalid number are skipped. Optional name column (name, first_name, full_name or participantName) and email column (email or email_address). Every column except the email column is also stored in the contact’s customData under its original header, for use as an agent variable.
mode string: append, replace no Alternative to the mode query parameter (the query wins).

Example request

Terminal window
curl -X POST "https://api.foan.ai/api/v1/campaigns/{campaignId}/contacts/upload" \
-H "Authorization: Bearer $FOAN_API_KEY"

Response 201

{
"success": true,
"data": {
"inserted": 180,
"total": 200,
"skippedExisting": 15,
"skippedInFile": 5,
"skippedDuplicates": 20,
"removed": 0,
"mode": "append",
"totalContacts": 430
},
"message": "180 contacts added, 15 already in this campaign and 5 repeated in your file. This campaign now has 430"
}

Errors

Code When
400 No file, wrong field name, file over 8 MB, empty file, unsupported type or an old .xls file, no phone column or no valid contacts, unreadable file, bad mode, or upload cut off mid-way
401 Missing, invalid or revoked API key
403 Key lacks the campaigns:write scope, or the campaign belongs to another organisation
404 Campaign not found
409 Campaign finished or cancelled, its scheduled dates have passed, replace on a scheduled/running/paused campaign, country changed during upload, or campaign busy with another upload
429 Rate limited

POST /campaigns/{campaignId}/contacts/append

Adds the numbers in a contact file to the campaign, always in append mode whatever the query or body says. Numbers already on the campaign are skipped and existing call history is kept. Works on draft, scheduled, running and paused campaigns.

Scope: campaigns:write

Parameters

Name In Type Required Description
campaignId path string yes Campaign ID. The campaign must belong to the key’s organisation.

Body (multipart/form-data)

Field Type Required Description
file file yes One .csv or .xlsx file, at most 8 MB, sent in the form field “file”. Only the first sheet of a workbook is read and its first row must be the headers. Old Excel 97-2003 .xls files are refused (re-save as .xlsx or .csv). Needs a phone column named phone, phoneNumber, phone_number, number or mobile (case-insensitive); rows with an invalid number are skipped. Optional name column (name, first_name, full_name or participantName) and email column (email or email_address). Every column except the email column is also stored in the contact’s customData under its original header, for use as an agent variable.

Example request

Terminal window
curl -X POST "https://api.foan.ai/api/v1/campaigns/{campaignId}/contacts/append" \
-H "Authorization: Bearer $FOAN_API_KEY"

Response 201

{
"success": true,
"data": {
"inserted": 48,
"total": 50,
"skippedExisting": 2,
"skippedInFile": 0,
"skippedDuplicates": 2,
"removed": 0,
"mode": "append",
"totalContacts": 298
},
"message": "48 contacts added, 2 already in this campaign. This campaign now has 298"
}

Errors

Code When
400 No file, wrong field name, file over 8 MB, empty file, unsupported type or an old .xls file, no phone column or no valid contacts, unreadable file, bad mode, or upload cut off mid-way
401 Missing, invalid or revoked API key
403 Key lacks the campaigns:write scope, or the campaign belongs to another organisation
404 Campaign not found
409 Campaign finished or cancelled, its scheduled dates have passed, replace on a scheduled/running/paused campaign, country changed during upload, or campaign busy with another upload
429 Rate limited

GET /campaigns/{campaignId}/contacts

Lists a campaign’s contacts with each contact’s last call: connectivity outcome, business result (interested, callback, not interested and so on, with the contact’s own words as evidence), recording link, AI overview and the answers to the campaign’s extraction questions. It works in three modes. Default: newest contacts first, filtered by status, result, outcome and search. lightweight=true: slim rows, most recently updated first, for frequent polling. bucket=: the results table, where the tab, search and sort apply to the whole campaign; this mode reads a server-side snapshot of the contact list that is at most about 15 seconds old, and its pages can be pinned to one snapshot with the snapshot parameter so paging through a large list never skips or repeats a contact. Custom businessResult and capturedDetails use the agent who made each call. resultFields lists captured-detail columns across current and previous campaign agents; keys include the agent ID to keep different meanings separate. Agent-bound API keys receive and can search/sort custom business details only for their bound agent.

Scope: campaigns:read

Parameters

Name In Type Required Description
campaignId path string yes Campaign ID. The campaign must belong to the key’s organisation.
page query integer no Page number, starting at 1.
limit query integer no Items per page (1 to 500).
status query string: PENDING, IN_PROGRESS, COMPLETED, FAILED, SKIPPED, PENDING_OR_SKIPPED no Only contacts in this dialling status. PENDING_OR_SKIPPED matches both PENDING and SKIPPED. Not allowed together with bucket.
result query string: interested, callback, not_interested, do_not_call, wrong_person, voicemail, hung_up, no_answer, unclear no Only contacts whose last call result is this code: interested, callback, not_interested, do_not_call, wrong_person (backed by a quote), voicemail, hung_up, no_answer (from connectivity), or unclear (a conversation with no evidenced result). Unknown codes are a 400. Not allowed together with bucket.
outcome query string no Only contacts whose saved connectivity outcome is exactly this code (completed, voicemail, hung_up, agent_no_audio, rejected, busy, no_answer, canceled, in_progress, queued, provider_throttled, provider_failed, failed, not_settled, unknown). Contacts settled before outcomes were saved never match. Not allowed together with bucket.
search query string no Free-text filter. Without bucket it matches the contact name (case-insensitive) or part of the phone number. With bucket it also matches the result code, the quote and its English rendering, the names and statuses of actions the agent ran, and the answers to the campaign’s extraction questions.
lightweight query boolean no Send true for slim rows (see the second row shape below), ordered by most recently updated first, with resultCounts returned empty. Only the literal value true switches it on. Not allowed together with bucket.
bucket query string: all, opportunities, completed, no_answer, rejected, hung_up, busy, voicemail, carrier_rejected, carrier_failed, pending, in_progress, other no Results tab to list across the whole campaign: all, opportunities (result interested or callback; overlaps the other tabs), or one connectivity tab. Every contact sits in exactly one connectivity tab. Switches on table mode, which adds bucketCounts and snapshot to the response and enables sort, direction and snapshot. Cannot be combined with status, result, outcome or lightweight (400). Default order without sort: connectivity tab order (completed first, other last), then newest contact first.
sort query string no Table mode only (ignored without bucket). One of name, phone, date (last call time), duration, status, actions, result (interested first, then callback, not_interested, do_not_call, wrong_person, unclear, hung_up, voicemail, no_answer), quote, score, classification, sentiment, or q:<field name> to sort by the answer to one of the campaign’s extraction questions. Empty values always sort last.
direction query string: asc, desc no Sort direction for sort (table mode only).
snapshot query integer no Table mode only. The snapshot value returned with an earlier page; send it with the same bucket, search, sort and direction to read the next pages from that exact list. If the server no longer holds it (it is kept briefly, in the memory of the server that built it) the request fails with 409; start again from page 1 without snapshot.

Example request

Terminal window
curl -X GET "https://api.foan.ai/api/v1/campaigns/{campaignId}/contacts" \
-H "Authorization: Bearer $FOAN_API_KEY"

Response 200

{
"success": true,
"data": {
"contacts": [
{
"id": "cmf4l5m6n0004cont7890uvwx",
"name": "Rahul Verma",
"phoneNumber": "+919876543210",
"email": null,
"status": "COMPLETED",
"attemptCount": 1,
"lastAttemptAt": "2026-10-01T05:12:00.000Z",
"nextRetryAt": null,
"customData": {
"phone": "9876543210",
"name": "Rahul Verma",
"city": "Pune"
},
"currentSessionId": null,
"lastSessionId": "66f1c2e8a4b5c6d7e8f90123",
"callStatus": "Completed",
"outcome": {
"code": "completed",
"detail": null
},
"result": {
"code": "callback",
"quote": "Abhi busy hoon, kal shaam ko call karna",
"quoteEn": "I'm busy right now, call tomorrow evening",
"settledAt": "2026-10-01T05:15:00.000Z",
"callbackAt": "2026-10-02T12:30:00.000Z"
},
"callDuration": 94000,
"callDurationMs": 94000,
"lastCallStartedAt": "2026-10-01T05:12:00.000Z",
"lastCallEndedAt": "2026-10-01T05:13:34.000Z",
"recordingUrl": "/api/v1/voice-sessions/66f1c2e8a4b5c6d7e8f90123/recording",
"egressId": "EG_a1b2c3d4e5",
"agentName": "Order confirmation",
"participantName": "Rahul Verma",
"sessionRoomName": "call-7f3a9c2e",
"overview": {
"score": 72,
"classification": "Relevant",
"sentiment": "neutral",
"summary": [
"Asked to be called back tomorrow evening."
]
},
"actions": {
"tracking": "not_recorded",
"items": []
},
"bucket": "completed",
"extractedCallData": {
"roomName": "call-7f3a9c2e",
"name": "Rahul Verma",
"phone": "+919876543210",
"preferred_time": "tomorrow evening",
"interested_in_offer": true
},
"createdAt": "2026-09-25T09:40:00.000Z",
"updatedAt": "2026-10-01T05:15:00.000Z"
}
],
"total": 1,
"totalPages": 1,
"currentPage": 1,
"resultCounts": {
"interested": 64,
"callback": 9,
"not_interested": 90,
"no_answer": 42,
"voicemail": 11,
"unclear": 5
},
"bucketCounts": {
"all": 231,
"opportunities": 73,
"completed": 168,
"no_answer": 42,
"voicemail": 11,
"busy": 4,
"pending": 6
},
"snapshot": 1759654800123
},
"message": "Contacts retrieved successfully"
}

Errors

Code When
400 Invalid page or limit (1 to 500), unknown result code, invalid status value, unknown bucket or sort column, bad direction, search over 100 or outcome over 50 characters, or bucket combined with status, result, outcome or lightweight
401 Missing, invalid or revoked API key
403 Key lacks the campaigns:read scope, or the campaign belongs to another organisation
404 Campaign not found
409 Table mode: the pinned snapshot is no longer held by the server (the contact list changed while being read). Start again from page 1 without snapshot.
429 Rate limited

GET /campaigns/{campaignId}/call-logs

One row per contact with its latest call attempt (status, outcome, duration, recording link). A lighter view of the contacts list without custom data, business result or AI overview.

Scope: campaigns:read

Parameters

Name In Type Required Description
campaignId path string yes Campaign ID. The campaign must belong to the key’s organisation.
page query integer no Page number, starting at 1.
limit query integer no Items per page (1 to 100).

Example request

Terminal window
curl -X GET "https://api.foan.ai/api/v1/campaigns/{campaignId}/call-logs" \
-H "Authorization: Bearer $FOAN_API_KEY"

Response 200

{
"success": true,
"data": {
"calls": [
{
"id": "cmf4l5m6n0004cont7890uvwx",
"contactId": "cmf4l5m6n0004cont7890uvwx",
"contactName": "Rahul Verma",
"phoneNumber": "+919876543210",
"status": "COMPLETED",
"attemptCount": 1,
"lastAttemptAt": "2026-10-01T05:12:00.000Z",
"nextRetryAt": null,
"callStatus": "Completed",
"outcome": {
"code": "completed",
"detail": null
},
"callDuration": 94000,
"lastCallStartedAt": "2026-10-01T05:12:00.000Z",
"lastCallEndedAt": "2026-10-01T05:13:34.000Z",
"currentSessionId": null,
"lastSessionId": "66f1c2e8a4b5c6d7e8f90123",
"sessionRoomName": "call-7f3a9c2e",
"recordingUrl": "/api/v1/voice-sessions/66f1c2e8a4b5c6d7e8f90123/recording",
"egressId": "EG_a1b2c3d4e5",
"agentName": "Order confirmation",
"participantName": "Rahul Verma",
"actions": {
"tracking": "not_recorded",
"items": []
},
"createdAt": "2026-09-25T09:40:00.000Z",
"updatedAt": "2026-10-01T05:15:00.000Z"
}
],
"total": 1,
"totalPages": 1,
"currentPage": 1
},
"message": "Call logs retrieved successfully"
}

Errors

Code When
400 Invalid page or limit
401 Missing, invalid or revoked API key
403 Key lacks the campaigns:read scope, or the campaign belongs to another organisation
404 Campaign not found
429 Rate limited

GET /campaigns/{id}/business-results

Counts finished call attempts, including retries and calls by previously assigned agents. Each agent’s outcomes, including the built-in ones every agent has, remain separate. Pending and live calls are excluded. An agent-bound key sees only its own agent’s attempts.

Scope: campaigns:read

Parameters

Name In Type Required Description
id path string yes Campaign ID in the key’s organisation.

Example request

Terminal window
curl -X GET "https://api.foan.ai/api/v1/campaigns/{id}/business-results" \
-H "Authorization: Bearer $FOAN_API_KEY"

Response 200

{
"success": true,
"data": {
"calls": 3,
"outcomes": [
{
"key": "hotel_agent:reservation_confirmed",
"label": "Reservation confirmed",
"count": 2,
"agentId": "hotel_agent",
"agentName": "Hotel concierge"
},
{
"key": "hotel_agent:no_result",
"label": "No result",
"count": 0,
"agentId": "hotel_agent",
"agentName": "Hotel concierge"
},
{
"key": "qualifier_agent:meeting_booked",
"label": "Meeting booked",
"count": 1,
"agentId": "qualifier_agent",
"agentName": "Lead qualifier"
},
{
"key": "qualifier_agent:no_result",
"label": "No result",
"count": 0,
"agentId": "qualifier_agent",
"agentName": "Lead qualifier"
}
]
}
}

Errors

Code When
401 Authentication required.
403 Organisation or scope access denied.
404 Campaign not found.

GET /campaigns/{id}/stats

Live counters for a campaign computed from its contacts, plus a count per connectivity outcome of each contact’s last attempt.

Scope: campaigns:read

Parameters

Name In Type Required Description
id path string yes Campaign ID. The campaign must belong to the key’s organisation.

Example request

Terminal window
curl -X GET "https://api.foan.ai/api/v1/campaigns/{id}/stats" \
-H "Authorization: Bearer $FOAN_API_KEY"

Response 200

{
"success": true,
"data": {
"totalContacts": 250,
"completedCalls": 168,
"failedCalls": 20,
"inProgressCalls": 0,
"notAnswered": 42,
"hungup": 12,
"outcomes": {
"completed": 168,
"no_answer": 42,
"hung_up": 10,
"agent_no_audio": 2,
"busy": 9,
"voicemail": 11,
"provider_failed": 8
}
},
"message": "Campaign stats retrieved successfully"
}

Errors

Code When
401 Missing, invalid or revoked API key
403 Key lacks the campaigns:read scope, or the campaign belongs to another organisation
404 Campaign not found
429 Rate limited

GET /campaigns/{id}/eligibility

Says whether the campaign can place calls right now: it must be IN_PROGRESS and inside its daily call window and date range. When outside the window it returns the next call time.

Scope: campaigns:read

Parameters

Name In Type Required Description
id path string yes Campaign ID. The campaign must belong to the key’s organisation.

Example request

Terminal window
curl -X GET "https://api.foan.ai/api/v1/campaigns/{id}/eligibility" \
-H "Authorization: Bearer $FOAN_API_KEY"

Response 200

{
"success": true,
"data": {
"eligible": false,
"reason": "Outside call window",
"nextCallTime": "2026-10-02T04:30:00.000Z",
"minutesUntilNext": 845
},
"message": "Campaign eligibility checked successfully"
}

Errors

Code When
401 Missing, invalid or revoked API key
403 Key lacks the campaigns:read scope, or the campaign belongs to another organisation
404 Campaign not found
429 Rate limited

GET /campaigns/{campaignId}/calls-data

Every call placed by the campaign (including retries), newest first, with the full transcript, the agent prompt snapshot used on the call, and the recording link.

Scope: campaigns:read

Parameters

Name In Type Required Description
campaignId path string yes Campaign ID. The campaign must belong to the key’s organisation.
page query integer no Page number, starting at 1.
limit query integer no Items per page (1 to 100).

Example request

Terminal window
curl -X GET "https://api.foan.ai/api/v1/campaigns/{campaignId}/calls-data" \
-H "Authorization: Bearer $FOAN_API_KEY"

Response 200

{
"success": true,
"data": {
"campaign": {
"id": "cmf3k2j9x0001abcd1234efgh",
"name": "Diwali offer reminders",
"description": null,
"status": "COMPLETED",
"totalContacts": 250,
"completedCalls": 180,
"failedCalls": 62,
"inProgressCalls": 0
},
"calls": [
{
"sessionId": "66f1c2e8a4b5c6d7e8f90123",
"roomName": "call-7f3a9c2e",
"participantName": "Rahul Verma",
"phoneNumber": "+919876543210",
"callStatus": "Completed",
"callDuration": 94000,
"createdAt": "2026-10-01T05:12:00.000Z",
"endedAt": "2026-10-01T05:13:34.000Z",
"recordingUrl": "/api/v1/voice-sessions/66f1c2e8a4b5c6d7e8f90123/recording",
"egressId": "EG_a1b2c3d4e5",
"customVariables": {
"participantName": "Rahul Verma",
"city": "Pune"
},
"actions": {
"tracking": "not_recorded",
"items": []
},
"agentName": "Order confirmation",
"agentIdentity": "You are Nina from Acme Stores.",
"agentTask": "Tell the customer about the Diwali offer.",
"pronunciation": null,
"greetingMessage": "Hi, this is Nina from Acme Stores.",
"closingMessage": "Thank you, have a good day.",
"escalationMessage": null,
"transcripts": [
{
"id": "66f1c2e8a4b5c6d7e8f90124",
"role": "assistant",
"text": "Hi, this is Nina from Acme Stores.",
"speaker": {
"id": "agent",
"name": "Nina"
},
"timestamps": {
"createdAt": "2026-10-01T05:12:03.000Z",
"receivedAt": "2026-10-01T05:12:03.000Z"
},
"meta": null
}
],
"conversations": [],
"analytics": null
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 1,
"totalPages": 1
}
},
"message": "Campaign calls data retrieved successfully"
}

Errors

Code When
400 Invalid page or limit
401 Missing, invalid or revoked API key
403 Key lacks the campaigns:read scope, or the campaign belongs to another organisation
404 Campaign not found
429 Rate limited

POST /campaigns/{id}/extract-data

Starts a background job that runs AI analysis over every completed call of a COMPLETED campaign and aggregates the fields in its dataExtractionSchema. Returns immediately with PROCESSING; poll extraction-status or extracted-data. If an extraction is already running or finished, that one is returned instead of starting a new one.

Scope: campaigns:write

Parameters

Name In Type Required Description
id path string yes Campaign ID. The campaign must belong to the key’s organisation.

Example request

Terminal window
curl -X POST "https://api.foan.ai/api/v1/campaigns/{id}/extract-data" \
-H "Authorization: Bearer $FOAN_API_KEY"

Response 200

{
"success": true,
"data": {
"id": "cmf6s7t8u0006extr5678efgh",
"campaignId": "cmf3k2j9x0001abcd1234efgh",
"extractedData": {},
"processingStatus": "PROCESSING",
"processedAt": "2026-10-08T09:00:00.000Z"
},
"message": "Campaign data extraction started. Use GET to poll for status and results."
}

Errors

Code When
400 Campaign not COMPLETED, no extraction schema configured, or extraction disabled in the schema
401 Missing, invalid or revoked API key
403 Key lacks the campaigns:write scope, or the campaign belongs to another organisation
404 Campaign not found
429 Rate limited

GET /campaigns/{id}/extracted-data

Returns the latest extraction for the campaign in any status, so it can be polled. When COMPLETED, extractedData holds the aggregated field values.

Scope: campaigns:read

Parameters

Name In Type Required Description
id path string yes Campaign ID. The campaign must belong to the key’s organisation.

Example request

Terminal window
curl -X GET "https://api.foan.ai/api/v1/campaigns/{id}/extracted-data" \
-H "Authorization: Bearer $FOAN_API_KEY"

Response 200

{
"success": true,
"data": {
"id": "cmf6s7t8u0006extr5678efgh",
"campaignId": "cmf3k2j9x0001abcd1234efgh",
"extractedData": {
"interested_in_offer": "62% (112/180)",
"preferred_store": "Pune: 40, Mumbai: 31",
"successful_calls": 112
},
"processedAt": "2026-10-08T09:03:12.000Z",
"processingStatus": "COMPLETED",
"confidenceScore": 0.86,
"createdAt": "2026-10-08T09:00:00.000Z",
"updatedAt": "2026-10-08T09:03:12.000Z"
},
"message": "Extracted data retrieved successfully"
}

Errors

Code When
401 Missing, invalid or revoked API key
403 Key lacks the campaigns:read scope, or the campaign belongs to another organisation
404 Campaign not found, or no extraction has been started for it
429 Rate limited

GET /campaigns/{id}/extraction-status

Lightweight polling endpoint: the status of the campaign’s latest extraction and its error, if any.

Scope: campaigns:read

Parameters

Name In Type Required Description
id path string yes Campaign ID. The campaign must belong to the key’s organisation.

Example request

Terminal window
curl -X GET "https://api.foan.ai/api/v1/campaigns/{id}/extraction-status" \
-H "Authorization: Bearer $FOAN_API_KEY"

Response 200

{
"success": true,
"data": {
"status": "PROCESSING"
},
"message": "Extraction status retrieved successfully"
}

Errors

Code When
401 Missing, invalid or revoked API key
403 Key lacks the campaigns:read scope, or the campaign belongs to another organisation
404 Campaign not found, or no extraction has been started
429 Rate limited

GET /campaigns/extracted-call-data/{campaignId}

Returns the per-call data extraction results for a campaign: one flat object per analysed call with the caller’s name and phone plus each extracted field.

Scope: campaigns:read

Parameters

Name In Type Required Description
campaignId path string yes Campaign ID. The campaign must belong to the key’s organisation.

Example request

Terminal window
curl -X GET "https://api.foan.ai/api/v1/campaigns/extracted-call-data/{campaignId}" \
-H "Authorization: Bearer $FOAN_API_KEY"

Response 200

{
"success": true,
"data": [
{
"roomName": "call-7f3a9c2e",
"name": "Rahul Verma",
"phone": "+919876543210",
"interested_in_offer": true,
"preferred_store": "Pune"
}
],
"message": "Extracted call data retrieved successfully"
}

Errors

Code When
401 Missing, invalid or revoked API key
403 Key lacks the campaigns:read scope, or the campaign belongs to another organisation
404 Campaign not found
429 Rate limited
500 Failed to retrieve extracted call data