vicigeeksimple guides
Browse
All guides

Automate tasks · Integration patterns

VICIdial API automation that does not hide failures

Neither API gives you an idempotency key or a request ID — build your own operation key, classify SUCCESS, ERROR and NOTICE separately, and verify writes outside the writer account.

Reader setup

Before you evaluate

Use this to set expectations, limits and implementation boundaries before changing anything.

  1. Use a dedicated least-privilege API account and a non-production or isolated synthetic target.
  2. Store credentials in a protected secret/config mechanism; never paste them into the command line, URL or source file.
  3. Choose one read-only version request first and define success, rejection, retryable and unknown outcomes.
What you will prove
You will send one bounded read-only request, classify its transport and application result including a NOTICE outcome, and verify that automation never treats HTTP 200 alone as success.
Safety boundary
No write function is provided. Add/update/delete functions require separate revision-specific review, idempotency design and independent verification.

Reader path

How to use this article

  • Use it when: You are designing a change and want reliable limits before implementation.
  • Expected result: Separate what is known, unknown, and unsafe before you execute.
  • Start here: Use it as an evidence review before changing architecture, security, or reporting behavior.

Design your own operation key — neither API gives you one

Fast answer: neither the Agent API nor the Non-Agent API accepts an idempotency key from you — there is no field to send one in, so idempotent automation is a property your own client has to build, not a contract either endpoint offers. Give every intended mutation a stable key derived from the source event and the target identity, kept in your own ledger, and use it to decide whether an operation already happened before you send it again.

VICIdial already ships two purpose-built dry-run mechanisms instead of a generic idempotency key: the Non-Agent API's add_lead's duplicate_check field (DUPLIST, DUPCAMP, DUPSYS and several other scoped variants — see vicidial-non-agent-api-guide for the complete list) refuses to create a second lead for a number already present in the scope you choose, and its update_lead's no_update=Y flag reports whether a matching lead exists without writing anything — see safe-callback-migration for no_update used exactly this way on a real migration. Prefer one of these shipped checks over a bespoke scheme wherever the function you are calling actually offers one.

Both APIs enforce the same allow-list before either question above even matters: vicidial_users.api_allowed_functions is a space-delimited list of the exact function names one account may call, or the literal ALL_FUNCTIONS; the Agent API additionally requires vdc_agent_api_access set on that same row. A function absent from that list is rejected before your idempotency design is ever exercised, so confirm it once, for the account your automation will actually use, before writing a retry loop that will only ever receive the same rejection.

Confirm the account's own allow-list before automating anything
SELECT user, user_level, api_allowed_functions, vdc_agent_api_accessFROM vicidial_usersWHERE user = '<AGENT_USER>';
Evidence · ViciBox 12 demo capture · demo values substituted

Captured demo response · 2026-09-24 22:25 UTC. The displayed command is the command that ran; a safe subset label means it was filtered, redacted, or fixture-scoped. Replays only after you select Replay transcript.

Command output line: SELECT user, user_level, api_allowed_functions, vdc_agent_api_access FROM vicidial_users WHERE user = 'kpisynag';
+----------+------------+-----------------------+----------------------+
| user | user_level | api_allowed_functions | vdc_agent_api_access |
+----------+------------+-----------------------+----------------------+
| kpisynag | 1 | ALL_FUNCTIONS | 0 |
+----------+------------+-----------------------+----------------------+
Before you run it
Run through the read-only database account for the exact account your automation authenticates as; this never selects pass.
Success looks like
api_allowed_functions either reads ALL_FUNCTIONS or lists the exact function name you intend to call, space-delimited among others, and vdc_agent_api_access reads 1 for an Agent API account.
Stop if
A function missing from api_allowed_functions, or vdc_agent_api_access reading 0 for an Agent API account, explains a rejection no retry or backoff logic will ever fix — that is a permissions change, not a bug in your client.
Trace path · read left to right
01Validated request02Classified result03Independent verifier

Visual walkthrough

Follow three real demo screens

Captured on an isolated VICIdial demo: Administration screens on September 24, 2026, and the idle Agent screen on August 11, 2026. Each caption states its own capture time, and every sanitized image helps you recognize a related screen; none proves that this article's call, command, or result occurred.
Step 1 · Find system administration

Use the Administration map

Sanitized VICIdial Administration menu showing phones, carriers, servers, system settings, and system statuses
Captured September 24, 2026 at 21:34:11 UTC on the authorized isolated demo. This menu is a navigation map only; it does not show that any system-wide setting was changed or verified.
Step 2 · Check permission scope

Review user-group boundaries

Sanitized VICIdial User Groups Listings page showing the fixture user group
Captured September 24, 2026 at 21:53:04 UTC on the authorized isolated demo. This page shows group structure only; it does not prove that an account has a particular permission or that access was changed.
Step 3 · Read global settings

Inspect system-wide security and API context

Sanitized VICIdial Modify System Settings page showing revision, schema, interface, SIP-stack, and API-related controls
Captured August 11, 2026 at 16:22:08 UTC on the authorized isolated demo. This is a read-only view of system-wide settings with no credentials or addresses; it does not prove that a setting was changed or that an API request succeeded.

Classify transport, VICIdial output and NOTICE separately

An HTTP response says whether the web request completed; the VICIdial response body says whether the requested function accepted its parameters. A 200 response containing ERROR is an application rejection, not success.

The local example below is intentionally small. It never sends a request; it turns a sanitized status/body into a category so dependent work can stop on unknown outcomes.

A plain-text response is not always exactly SUCCESS or ERROR, either. AGENT_API.txt documents a third class, prefixed NOTICE, for a condition that is neither: external_dial's own worked example shows NOTICE: defined dial_ingroup not found when an optional field names something that does not exist, and the call still proceeds. Classify that separately from an outright rejection instead of letting it fall into the same bucket as a genuinely unrecognized response.

Beginner response classifier with a NOTICE branch
function classify(httpStatus, body) {  const text = String(body).trim();  if (httpStatus >= 500) return 'retryable-server';  if (httpStatus === 401 || httpStatus === 403) return 'rejected-auth';  if (httpStatus < 200 || httpStatus >= 300) return 'rejected-http';  if (/^ERROR/i.test(text)) return 'rejected-application';  if (/^NOTICE/i.test(text)) return 'accepted-with-notice';  if (!text) return 'unknown-empty';  return 'verify-required';} console.log(classify(200, 'NOTICE: defined dial_ingroup not found - FAKE_INGROUP'));
Not executed · worksheet or reference text

This sample is a template or reading aid, not a terminal command. There is no output to show.

Before you run it
Run locally with synthetic status/body strings. The example output is a category only; adapt the exact prefixes to your installed API's documented formats before integrating.
Success looks like
The example prints accepted-with-notice — the call was accepted and, on functions like external_dial, still took its main effect, but with one optional field ignored. Route that outcome to a warning log, not silently to the same bucket as a plain SUCCESS.
Stop if
Stop if you cannot enumerate which of your integration's functions can even return NOTICE, or if a write job would treat accepted-with-notice as fully successful without checking which field was ignored.

Bound and classify retries

Retry transport failures and clearly temporary server responses with backoff. Do not retry validation, authentication or ambiguous partial-success responses without a read check.

Separate secrets from operator logins and redact them from command history, logs and support bundles.

A retryable-server classification and a rejected-application classification call for opposite behavior, so keep them on separate code paths rather than funneling every non-success result through one generic retry wrapper. Retrying an ERROR body a second time costs latency and produces the identical rejection every time, since the request was understood and refused, not lost.

A bounded, read-only health request
curl --fail-with-body --silent --show-error --connect-timeout 5 --max-time 20 --config /etc/vicidial-api/agent.cfg --data-urlencode "function=version"
Evidence · ViciBox 12 demo capture

Captured demo response · 2026-09-24 22:25 UTC. The displayed command is the command that ran; a safe subset label means it was filtered, redacted, or fixture-scoped. Replays only after you select Replay transcript.

Command output line: curl --fail-with-body --silent --show-error --connect-timeout 5 --max-time 20 --config /etc/vicidial-api/agent.cfg --data-urlencode "function=version"
VERSION: 2.14-83|BUILD: 250830-2102|DATE: 2026-09-24 18:25:16|EPOCH: [number]
Before you run it
The config file already supplies the URL, user, pass and source — add only --connect-timeout and --max-time here, so a hung network path fails fast instead of blocking your retry logic indefinitely.
Success looks like
curl exits zero within the bounded time and the body does not start with ERROR — safe to treat as a live, reachable endpoint before layering any retry policy on top.
Stop if
A nonzero exit from --fail-with-body, or an exit only at the 20-second ceiling, are both retryable-transport outcomes; an ERROR-prefixed body on exit zero is not — classify it as rejected-application instead of retrying it. A TLS handshake failure is a certificate problem to fix, never a reason to add -k or --insecure to make the retry logic quiet.

Verify outside the writer

Read the resulting lead, callback or user state back through a read-only path — /etc/vicidial-api/readonly.cfg's own Non-Agent API account, or a direct read-only database query — never by trusting the writer call's own response a second time. Keeping /etc/vicidial-api/writer.cfg's write-capable account separate from readonly.cfg's read-only one is not just tidiness: it means a verification step can never accidentally repeat the write it is supposed to be checking.

Report completed, already-complete, rejected, retryable and unknown outcomes separately. Unknown is a real state and should stop dependent work.

This is the same discipline vicidial-agent-api-guide and vicidial-agent-api-vs-non-agent-api teach for a single call; automation just needs it enforced every time, in code, rather than remembered by whoever happens to be watching a terminal that day.

Stop on unknown and reconcile before retry

When a write-capable job is later developed, an unknown timeout or malformed response must pause dependent work. First perform an independent read using the stable operation key and target identity; retry only when that read proves the operation was not committed.

Rollback must be a documented supported inverse operation or restoration workflow scoped to the exact synthetic or authorized record. Never guess by issuing the same mutation repeatedly or deleting rows directly.

  • Completed, already-complete, rejected, retryable and unknown totals reconcile to input.
  • Every retry has a bounded attempt count and backoff schedule.
  • Secrets and response payloads remain out of URLs, process lists, source and logs.

Primary references

Sources

  1. VICIdial Agent API reference (AGENT_API.txt)VICIdial Group · accessed September 23, 2026
  2. VICIdial Non-Agent API reference (NON-AGENT_API.txt)VICIdial Group · accessed September 23, 2026

Follow without guesswork

Get the next article

RSS is live now. Email delivery below is an explicit local preview and sends nothing.Open the RSS feed
Email preview only. The address stays in this browser and is never transmitted.