DEV Community

Jason Shouldice
Jason Shouldice

Posted on • Originally published at vicistack.com

VICIdial API Integration Examples

Originally published at vicistack.com. Follow us for more call center engineering content.

Last updated: March 2026 | Reading time: ~28 minutes

VICIdial has two APIs that most people know exist but few people use well. The Non-Agent API handles backend automation — pushing leads, pulling reports, scheduling callbacks. The Agent API controls what happens on the agent screen — pausing, dialing, transferring, dispositioning.

Together, they let you build practically anything on top of VICIdial: CRM integrations, real-time dashboards, automated lead routing, web-to-lead forms, callback scheduling systems, and custom reporting pipelines.

The official documentation (NON-AGENT_API.txt and AGENT_API.txt) is thorough but written in the style of a 2008-era Unix man page — technically complete, practically incomprehensible if you're just trying to add a lead from a web form. So here's the practical version with real code examples in curl, Python, PHP, and Node.js.


API Basics

Endpoint URLs

Both APIs are accessed via HTTP GET or POST to PHP scripts on your VICIdial web server:

  • Non-Agent API: https://YOUR-SERVER/vicidial/non_agent_api.php
  • Agent API: https://YOUR-SERVER/agc/api.php

Always use HTTPS in production. If your VICIdial server doesn't have an SSL certificate, set one up before exposing the API.

Authentication

Every API call requires two parameters:

  • user — A valid VICIdial user account
  • pass — The user's password

The user must have API access enabled in their account settings. In the VICIdial Admin GUI:

  1. Go to Admin > Users
  2. Edit the API user
  3. Set user_level to 8 or higher (7 for limited functions)
  4. Enable modify_leads (for lead-related functions)
  5. Under Allowed API Functions, check the functions you want to allow
  6. Set API IP Restrictions to limit which IPs can make API calls (do this)

Response Format

The API returns plain text responses. Successful calls return data starting with SUCCESS: and failures start with ERROR: or NOTICE:. Some functions support JSON output by adding &header=YES to the request.


Non-Agent API: Lead Management

add_lead — Insert a New Lead

This is the most-used API function. Your web forms, CRM imports, and lead vendors push leads into VICIdial through this endpoint.

Minimum required parameters:

  • source — Identifier for the API source (for logging)
  • function=add_lead
  • phone_number — 10-digit phone number
  • list_id — The VICIdial list to add the lead to

curl example:

curl -s "https://YOUR-SERVER/vicidial/non_agent_api.php?\
source=webform&\
user=apiuser&\
pass=API_PASSWORD&\
function=add_lead&\
phone_number=3125551234&\
list_id=10001&\
first_name=John&\
last_name=Smith&\
address1=123+Main+St&\
city=Chicago&\
state=IL&\
postal_code=60601&\
email=john@example.com&\
vendor_lead_code=CRM-98765"
Enter fullscreen mode Exit fullscreen mode

Response on success:

SUCCESS: add_lead LEAD HAS BEEN ADDED - 12345678|10001|3125551234|CRM-98765
Enter fullscreen mode Exit fullscreen mode

The number 12345678 is the lead_id — save this for future API calls.

Python example:

import requests

api_url = "https://YOUR-SERVER/vicidial/non_agent_api.php"
params = {
 "source": "crm_sync",
 "user": "apiuser",
 "pass": "API_PASSWORD",
 "function": "add_lead",
 "phone_number": "3125551234",
 "list_id": "10001",
 "first_name": "John",
 "last_name": "Smith",
 "city": "Chicago",
 "state": "IL",
 "postal_code": "60601",
 "vendor_lead_code": "CRM-98765",
 "custom_fields": "Y",
 "cf_company_size": "50-100",
 "cf_industry": "manufacturing",
}

response = requests.get(api_url, params=params, verify=True, timeout=10)
print(response.text)

if "SUCCESS" in response.text:
 lead_id = response.text.split("|")[0].split(" - ")[1]
 print(f"Lead created: {lead_id}")
Enter fullscreen mode Exit fullscreen mode

PHP example:

<?php
$api_url = "https://YOUR-SERVER/vicidial/non_agent_api.php";
$params = http_build_query([
 'source' => 'crm_sync',
 'user' => 'apiuser',
 'pass' => 'API_PASSWORD',
 'function' => 'add_lead',
 'phone_number' => '3125551234',
 'list_id' => '10001',
 'first_name' => 'John',
 'last_name' => 'Smith',
 'vendor_lead_code' => 'CRM-98765',
]);

$response = file_get_contents("$api_url?$params");

if (strpos($response, 'SUCCESS') !== false) {
 preg_match('/- (\d+)\|/', $response, $matches);
 $lead_id = $matches[1];
 echo "Lead created: $lead_id\n";
}
?>
Enter fullscreen mode Exit fullscreen mode

Node.js example:

const https = require('https');
const querystring = require('querystring');

const params = querystring.stringify({
 source: 'crm_sync',
 user: 'apiuser',
 pass: 'API_PASSWORD',
 function: 'add_lead',
 phone_number: '3125551234',
 list_id: '10001',
 first_name: 'John',
 last_name: 'Smith',
 vendor_lead_code: 'CRM-98765',
});

const url = `https://YOUR-SERVER/vicidial/non_agent_api.php?${params}`;

https.get(url, (res) => {
 let data = '';
 res.on('data', chunk => data += chunk);
 res.on('end', () => {
 console.log(data);
 if (data.includes('SUCCESS')) {
 const leadId = data.split(' - ')[1].split('|')[0];
 console.log(`Lead created: ${leadId}`);
 }
 });
}).on('error', console.error);
Enter fullscreen mode Exit fullscreen mode

add_lead with Callback Scheduling

One of the most powerful features: inject a lead AND schedule a callback in a single API call. Perfect for web-to-lead forms where the customer requests a specific call-back time.

curl -s "https://YOUR-SERVER/vicidial/non_agent_api.php?\
source=webform&\
user=apiuser&\
pass=API_PASSWORD&\
function=add_lead&\
phone_number=3125551234&\
list_id=10001&\
first_name=Jane&\
last_name=Doe&\
callback=Y&\
campaign_id=SALES01&\
callback_status=CALLBK&\
callback_datetime=2026-03-27+14:30:00&\
callback_type=ANYONE&\
callback_comments=Requested+callback+after+2pm+CT"
Enter fullscreen mode Exit fullscreen mode

Key callback parameters:

  • callback=Y — Enables callback scheduling
  • campaign_id — Which campaign the callback appears in (required when callback=Y)
  • callback_datetime — Use YYYY-MM-DD+HH:MM:SS format (use NOW for immediate)
  • callback_typeANYONE (any agent) or USERONLY (specific agent)
  • callback_user — Required if callback_type is USERONLY
  • callback_status — Default is CALLBK, the lead's status gets set to CBHOLD

update_lead — Modify Existing Leads

Update lead data, change statuses, or add callback information to existing leads. You need either the lead_id or a combination of vendor_lead_code + phone_number to identify the lead.

curl -s "https://YOUR-SERVER/vicidial/non_agent_api.php?\
source=crm_sync&\
user=apiuser&\
pass=API_PASSWORD&\
function=update_lead&\
lead_id=12345678&\
address1=456+Oak+Ave&\
city=Chicago&\
email=jane.updated@example.com&\
custom_fields=Y&\
cf_company_size=100-250"
Enter fullscreen mode Exit fullscreen mode

Update by vendor_lead_code (useful when your CRM doesn't store VICIdial lead_ids):

curl -s "https://YOUR-SERVER/vicidial/non_agent_api.php?\
source=crm_sync&\
user=apiuser&\
pass=API_PASSWORD&\
function=update_lead&\
vendor_lead_code=CRM-98765&\
search_method=VENDOR_LEAD_CODE&\
address1=456+Oak+Ave"
Enter fullscreen mode Exit fullscreen mode

lead_status_search — Find Leads by Status

Search for leads with a specific disposition status. Useful for building reports or syncing disposition data back to your CRM.

curl -s "https://YOUR-SERVER/vicidial/non_agent_api.php?\
source=reporting&\
user=apiuser&\
pass=API_PASSWORD&\
function=lead_status_search&\
status=SALE&\
list_id=10001&\
date=2026-03-26&\
header=YES"
Enter fullscreen mode Exit fullscreen mode

Non-Agent API: Campaign and System Functions

recording_lookup — Pull Call Recordings

Retrieve recording URLs for a specific lead or date range. Essential for CRM integration where reps need to listen to calls.

curl -s "https://YOUR-SERVER/vicidial/non_agent_api.php?\
source=crm&\
user=apiuser&\
pass=API_PASSWORD&\
function=recording_lookup&\
lead_id=12345678&\
header=YES"
Enter fullscreen mode Exit fullscreen mode

The response includes recording file paths that you can serve via HTTP. Make sure your web server is configured to serve files from the recording directory.

agent_stats_export — Pull Agent Performance Data

Export agent statistics for a date range. Perfect for feeding data into external BI tools.

curl -s "https://YOUR-SERVER/vicidial/non_agent_api.php?\
source=bi_tool&\
user=apiuser&\
pass=API_PASSWORD&\
function=agent_stats_export&\
datetime_start=2026-03-20+00:00:00&\
datetime_end=2026-03-26+23:59:59&\
campaign_id=SALES01&\
header=YES"
Enter fullscreen mode Exit fullscreen mode

Agent API: Controlling the Agent Screen

The Agent API is different from the Non-Agent API. It controls active agent sessions — the same actions an agent performs by clicking buttons on the VICIdial agent web interface.

Endpoint: https://YOUR-SERVER/agc/api.php

Each Agent API call requires the agent_user parameter identifying which agent session to control.

Pausing and Unpausing Agents

Programmatically pause agents from an external system (workforce management, break scheduling, etc.):

# Pause agent with pause code BREAK
curl -s "https://YOUR-SERVER/agc/api.php?\
source=wfm&\
user=apiuser&\
pass=API_PASSWORD&\
agent_user=agent001&\
function=pause_agent&\
value=PAUSE&\
pause_code=BREAK"

# Unpause agent (resume dialing)
curl -s "https://YOUR-SERVER/agc/api.php?\
source=wfm&\
user=apiuser&\
pass=API_PASSWORD&\
agent_user=agent001&\
function=pause_agent&\
value=RESUME"
Enter fullscreen mode Exit fullscreen mode

External Dial — Originate a Call

Trigger a manual dial from an external system. The agent's screen gets populated and the call connects through the normal VICIdial call flow.

curl -s "https://YOUR-SERVER/agc/api.php?\
source=crm&\
user=apiuser&\
pass=API_PASSWORD&\
agent_user=agent001&\
function=external_dial&\
value=3125551234&\
lead_id=12345678&\
phone_code=1&\
search=YES&\
preview=NO&\
focus=YES"
Enter fullscreen mode Exit fullscreen mode

Parameters:

  • value — Phone number to dial
  • lead_id — Associate with existing lead (optional)
  • search=YES — Search for existing lead by phone number
  • preview=NO — Dial immediately (YES shows preview first)
  • focus=YES — Bring the agent screen into focus

External Transfer

Transfer the active call to another number or extension:

curl -s "https://YOUR-SERVER/agc/api.php?\
source=crm&\
user=apiuser&\
pass=API_PASSWORD&\
agent_user=agent001&\
function=transfer_conference&\
value=BLIND_TRANSFER&\
phone_number=3125559876&\
consultative=NO"
Enter fullscreen mode Exit fullscreen mode

Set Disposition

Programmatically set the disposition from an external system after the call ends:

curl -s "https://YOUR-SERVER/agc/api.php?\
source=crm&\
user=apiuser&\
pass=API_PASSWORD&\
agent_user=agent001&\
function=external_status&\
value=SALE&\
callback=NO"
Enter fullscreen mode Exit fullscreen mode

Building a Web-to-Lead Integration

Here's a complete example: a web form on your marketing site pushes leads directly into VICIdial for immediate dialing. The famous "speed to lead" — the faster you call a web lead, the higher the conversion rate. Under 5 minutes is the target.

The Form Handler (PHP)

<?php
# web-to-lead.php — Receives form submissions, pushes to VICIdial

$VICIDIAL_SERVER = "https://dialer.yourcompany.com";
$API_USER = "webform_api";
$API_PASS = "SECURE_PASSWORD";
$LIST_ID = "10001";
$CAMPAIGN_ID = "WEBLEAD";

# Sanitize input
$phone = preg_replace('/[^0-9]/', '', $_POST['phone']);
$first = substr(strip_tags($_POST['first_name']), 0, 30);
$last = substr(strip_tags($_POST['last_name']), 0, 30);
$email = filter_var($_POST['email'], FILTER_SANITIZE_EMAIL);

if (strlen($phone) !== 10) {
 http_response_code(400);
 echo json_encode(['error' => 'Invalid phone number']);
 exit;
}

# Push to VICIdial
$params = http_build_query([
 'source' => 'website',
 'user' => $API_USER,
 'pass' => $API_PASS,
 'function' => 'add_lead',
 'phone_number' => $phone,
 'list_id' => $LIST_ID,
 'first_name' => $first,
 'last_name' => $last,
 'email' => $email,
 'vendor_lead_code' => 'WEB-' . time(),
 'callback' => 'Y',
 'campaign_id' => $CAMPAIGN_ID,
 'callback_status' => 'CALLBK',
 'callback_datetime' => 'NOW',
 'callback_type' => 'ANYONE',
 'callback_comments' => 'Web form submission - call immediately',
]);

$ctx = stream_context_create(['http' => ['timeout' => 10]]);
$response = file_get_contents("$VICIDIAL_SERVER/vicidial/non_agent_api.php?$params", false, $ctx);

if (strpos($response, 'SUCCESS') !== false) {
 echo json_encode(['status' => 'ok', 'message' => 'We will call you shortly']);
} else {
 error_log("VICIdial API error: $response");
 echo json_encode(['status' => 'ok', 'message' => 'We will call you shortly']);
 # Still show success to user, handle API errors internally
}
?>
Enter fullscreen mode Exit fullscreen mode

The key trick: callback=Y with callback_datetime=NOW and callback_type=ANYONE creates an immediate callback that the next available agent picks up. The lead gets called within seconds of form submission — not minutes, not hours.

CRM Webhook: Sync Dispositions Back

After a VICIdial agent disposes a call, you want that data in your CRM. VICIdial supports URL-based event triggers that fire on disposition.

In the VICIdial Admin GUI:

  1. Go to Campaigns > Campaign Settings
  2. Find Dispo Call URL
  3. Enter your webhook endpoint:
https://your-crm.com/api/vicidial-webhook?lead_id=--A--lead_id--B--&status=--A--dispo--B--&agent=--A--user--B--&phone=--A--phone_number--B--&talk_time=--A--talk_epoch--B--&vendor_code=--A--vendor_lead_code--B--
Enter fullscreen mode Exit fullscreen mode

The --A--field_name--B-- tags get replaced with actual call data when the webhook fires. Your CRM endpoint receives a GET request with all the call details and can update the lead record accordingly.

Python webhook receiver (Flask):

from flask import Flask, request
import json

app = Flask(__name__)

@app.route('/api/vicidial-webhook', methods=['GET'])
def handle_disposition():
 lead_id = request.args.get('lead_id')
 status = request.args.get('status')
 agent = request.args.get('agent')
 phone = request.args.get('phone')
 talk_time = request.args.get('talk_time')
 vendor_code = request.args.get('vendor_code')

 # Update your CRM
 crm_update = {
 'vicidial_lead_id': lead_id,
 'disposition': status,
 'called_by': agent,
 'phone': phone,
 'talk_seconds': talk_time,
 }

 # Your CRM API call here
 print(f"Updating CRM for {vendor_code}: {json.dumps(crm_update)}")

 return 'OK', 200

if __name__ == '__main__':
 app.run(host='0.0.0.0', port=8080)
Enter fullscreen mode Exit fullscreen mode

API Security Best Practices

1. Dedicated API User Accounts

Never use an admin account for API access. Create dedicated users with minimal permissions:

  • Web form import user: Only add_lead function enabled
  • CRM sync user: add_lead, update_lead, recording_lookup
  • Reporting user: Only read-only functions

2. IP Restrictions

Set API IP restrictions on every API user account. Your web server and CRM have known IPs — lock the API down to only those IPs.

3. HTTPS Only

Never send API credentials over HTTP. The user/pass parameters travel in the URL query string (or POST body), and without TLS they're visible to anyone sniffing the network.

4. Rate Limiting

VICIdial doesn't have built-in API rate limiting. If your web form gets hit by a bot, it'll flood your VICIdial with garbage leads. Implement rate limiting at the form level (CAPTCHA, throttling) or at the web server level (nginx rate limiting).

5. Input Validation

Always sanitize data before sending it to the API. The API has some built-in validation, but SQL injection and XSS payloads in lead data can cause problems downstream in reports and agent screens.


Common API Errors and Fixes

Error Cause Fix
ERROR: add_lead INVALID LIST ID List doesn't exist or is inactive Verify list_id in Admin > Lists
ERROR: NO FUNCTION SPECIFIED Missing function parameter Add function=add_lead (or whichever function)
ERROR: INVALID USER Bad credentials or user not API-enabled Check user/pass, enable API access in user settings
ERROR: FUNCTION NOT ALLOWED Function not enabled for this user Enable the specific function in user's Allowed API Functions
ERROR: INVALID SOURCE Source parameter missing or invalid Add source=your_identifier to the request
ERROR: add_lead DUPLICATE Phone number already exists in list Use duplicate_check=DUPLIST to allow or update_lead instead
ERROR: IP NOT ALLOWED Your IP isn't in the user's allowed list Add your server's IP to the API user's IP restrictions

Performance Considerations

Batch Lead Imports

If you need to import thousands of leads, don't hit the API once per lead in a tight loop. The API processes each request synchronously, and high-frequency requests can overwhelm the web server.

Instead:

  • Use VICIdial's built-in list loader for bulk imports (Admin > Lists > Load Leads)
  • If you must use the API, throttle to 5-10 requests per second
  • For large batches, generate a lead file and use the add_list function to upload the whole file

API Response Time

Expect 100-500ms per API call on a healthy system. If response times exceed 1 second consistently, check:

  • MySQL query performance (the API hits the database on every call)
  • Web server load (Apache workers competing with agent web sessions)
  • Network latency between your calling application and the VICIdial server

When the API Isn't Enough

VICIdial's API covers most common integration scenarios, but it has limitations:

  • No real-time event streaming (webhooks are disposition-only)
  • No bulk operations beyond lead file upload
  • Limited reporting functions compared to direct database access
  • Agent API requires an active agent session

For advanced integrations — real-time call event streaming, complex reporting, custom agent screen modifications — you'll need direct database access (read-only, through a replica) or custom development on the VICIdial codebase.



Related reading:

Pattern 1: Lead Score Routing

Your CRM assigns lead scores (1-100) based on demographic data and behavioral signals. High-score leads should go to your best agents. Here's how:

  1. When adding leads via API, store the score in a custom field:
curl -s "https://YOUR-SERVER/vicidial/non_agent_api.php?\
source=crm&\
user=apiuser&\
pass=API_PASSWORD&\
function=add_lead&\
phone_number=3125551234&\
list_id=10001&\
custom_fields=Y&\
cf_lead_score=85"
Enter fullscreen mode Exit fullscreen mode
  1. Create two lists: 10001 (high-score leads) and 10002 (standard leads)
  2. In your API integration script, route leads to the appropriate list based on score
  3. Assign your best agents to campaign configurations that dial the high-score list first using list mix priority in the Admin GUI

Pattern 2: Real-Time CRM Screen Pop

When an agent connects to a call, you want your CRM to automatically open the customer record. VICIdial supports this through the agent interface's "Web Form" feature.

In Campaign Settings, configure the Web Form URL:

https://your-crm.com/popup?phone=--A--phone_number--B--&name=--A--first_name--B--+--A--last_name--B--&lead_id=--A--lead_id--B--&vendor=--A--vendor_lead_code--B--
Enter fullscreen mode Exit fullscreen mode

When the agent clicks "Web Form" (or it auto-opens based on campaign settings), the URL opens in a new tab with the lead's data pre-filled. Your CRM uses the vendor_lead_code to look up the customer record and display it.

For automatic screen pop (no click required), set "Web Form Target" to "New Window" and enable "Auto-Launch Web Form" in the campaign settings. The CRM window opens the instant the call connects.

Pattern 3: Disposition-Triggered Automation

Beyond the simple webhook, you can chain multiple actions on disposition:

  • SALE disposition → Webhook to CRM → Create deal → Trigger e-signature email → Schedule follow-up callback
  • APPT disposition → Webhook to scheduling system → Block calendar slot → Send SMS confirmation
  • DNC disposition → Webhook to compliance system → Log the request → Trigger DNC registry update

Each of these chains starts with VICIdial's Dispo Call URL firing to your middleware, which then orchestrates the downstream actions.

Pattern 4: Multi-System Lead Deduplication

If you receive leads from multiple sources (web forms, bought lists, partner referrals), you need to prevent duplicate calls. Use the API's duplicate_check parameter:

curl -s "https://YOUR-SERVER/vicidial/non_agent_api.php?\
source=webform&\
user=apiuser&\
pass=API_PASSWORD&\
function=add_lead&\
phone_number=3125551234&\
list_id=10001&\
duplicate_check=DUPCAMP&\
duplicate_campaign_id=SALES01"
Enter fullscreen mode Exit fullscreen mode

duplicate_check options:

  • DUPLIST — Check within the same list only
  • DUPCAMP — Check across all lists in the campaign
  • DUPSYS — Check the entire VICIdial system
  • DUPFULL — Check system-wide plus the DNC list

The API returns ERROR: add_lead DUPLICATE if the phone number already exists, and your integration code can handle it accordingly (update existing lead, skip, or log for review).

Pattern 5: Automated Campaign Activation

For operations that run different campaigns at different times (morning shift campaigns, afternoon campaigns, after-hours), automate the start and stop:

import requests
from datetime import datetime

API_URL = "https://YOUR-SERVER/vicidial/non_agent_api.php"
CAMPAIGNS = {
 "MORNING": {"start": 8, "end": 12},
 "AFTERNOON": {"start": 12, "end": 17},
 "EVENING": {"start": 17, "end": 20},
}

current_hour = datetime.now().hour

for campaign, hours in CAMPAIGNS.items():
 if hours["start"] <= current_hour < hours["end"]:
 action = "ACTIVE"
 else:
 action = "PAUSED"

 resp = requests.get(API_URL, params={
 "source": "scheduler",
 "user": "apiuser",
 "pass": "API_PASSWORD",
 "function": "update_campaign",
 "campaign_id": campaign,
 "active": action,
 }, timeout=10)
 print(f"{campaign}: {action} - {resp.text}")
Enter fullscreen mode Exit fullscreen mode

Schedule this via cron every hour. No more manual campaign activation at shift boundaries.

That's the kind of work we do at ViciStack. We've built CRM integrations, custom real-time dashboards, ML-based lead scoring pipelines, and automated campaign management systems on top of VICIdial's API and database. If you need something the stock API can't do, the $5K engagement gets you a working integration, not a proposal.

Top comments (0)