CALL-E tools for Dify workflows and agents.
Maintainer: J.Jason (@JJasonSun).
Source repository: https://github.com/CALLE-AI/call-e-dify-plugin
This plugin exposes safe outbound phone-call tools for CALL-E:
create_callpreviews or creates one outbound CALL-E call.get_callfetches one CALL-E call result by call ID.create_and_waitpreviews or creates one outbound CALL-E call, then waits for a final result or timeout.list_goalsandget_goalread published CALL-E Goals without starting a call.prepare_goal_runselects a published Goal, describes its fields, and validates a reviewed business mapping without starting a call.create_goal_run,get_goal_run, andcreate_goal_run_and_waitexecute and monitor one phone-specific published Goal.
Phone calls are real-world side effects. Live calls require both dry_run=false and confirm_live_call=true.
- Python 3.12
- Dify Plugin CLI
- CALL-E API key (create one in the CALL-E dashboard)
Install the Dify Plugin CLI by following the official Dify plugin documentation. For request and response details, see the CALL-E API reference.
Configure these provider credentials in Dify:
| Credential | Required | Description |
|---|---|---|
api_key |
Yes | CALL-E API key. Create a key; it is stored as a secret input in Dify. |
base_url |
Yes | CALL-E API base URL. Defaults to https://api.heycall-e.com. Do not include /v1 or /v2. |
When credentials are saved, the provider performs a non-mutating authentication probe:
GET /v2/calls/call_dify_credential_probe_00000000000000000000000000000000
The provider accepts 200 or the documented 404 not_found response as an authenticated probe. It rejects 401 unauthorized, 403 forbidden, redirects, and other errors. The probe does not create a call. The client does not follow redirects with credentials.
Creates one CALL-E call only when both live-call controls are set:
dry_run=falseconfirm_live_call=true
Otherwise it returns a preview with the phone number masked.
Both Call creation tools require a business-stable idempotency_key of 1-255 printable ASCII characters, such as order-8472-confirmation-attempt-1. Save it with the original inputs before invoking the tool. Retrying the same key and unchanged inputs recovers the same Call; a new key means a new call. The plugin does not generate keys or retry creation automatically.
The plugin sends one top-level phone to POST /v2/calls with a closed, flat scalar result_schema. Leave Result Schema JSON blank to keep the built-in fields: call_completed, outcome, summary, and next_action, with optional observation fields. Both create_call and create_and_wait accept a custom schema instead. Include missing-answer rules in the task consistent with the requested fields. Leave region and locale blank for API inference, and keep them blank on replay even when the response includes inferred values.
live_call_created=true reports an accepted resource, including an exact replay. It does not prove a new dial or a successful connection. HTTP creation errors return error.code, error.message, error.details, and the supplied key for recovery.
Fetches one result from GET /v2/calls/{id}. The parsed response includes result, error, call_outcome, result_status, and the top-level transcript. summary comes from result.summary; structured_result remains an alias for result, including null and {}.
Call tools return the JSON response and emit named workflow variables for their declared outputs, so later Dify nodes can select call_id and result without parsing text. The default json output remains available for the complete response and recovery details.
For existing Dify variable mappings, this plugin keeps call_id as the API resource ID used by get_call, also available as id. billing_call_id contains the API response's telephone call_id, which can be null. Use that field for Billing correlation, not for API reads. The redacted raw_call_result preserves the API field names.
Creates one call, then polls while result_status=pending. The waiter stops for available, unavailable, and not_applicable. A completed telephone execution can still have a pending result. A final unavailable result can have both result=null and error=null; it must not trigger an automatic redial.
A wait timeout returns timed_out=true, the same call_id, and the supplied key. A polling error returns accepted_resource with the last saved ID; use get_call to resume. A network failure during creation can leave acceptance uncertain: retry only the original key and unchanged inputs. An expired Dify host execution also does not cancel the call. Set the host's plugin execution timeout to cover the requested wait, or use create_call followed by get_call in later workflow steps.
Enter a JSON Schema object in Result Schema JSON. It replaces the built-in schema; the plugin does not add default business fields. Existing workflows that omit this input keep their current request and output paths.
The schema must have type: "object", additionalProperties: false, and at most 32 flat fields. Each field supports string, boolean, integer, or number, an optional description, and an optional enum. Field names must match [a-z][a-z0-9_]{0,63}. required may list declared fields. Descriptions have a 512-character limit, enums allow 1–32 distinct values of the field's type, and the schema has a 32 KiB JSON limit. Nested objects, arrays, nullable fields, references, and other validation keywords are unsupported. Invalid schemas stop locally before any CALL-E request, including in dry run mode.
For unanswered questions, use an explicit value such as unknown and explain it in the task. Do not use a boolean when you need to distinguish “no” from “unknown.” Save the schema with the original task, phone, optional inputs, and idempotency key. Replays must use that same request; editing the schema under an existing key is not a retry.
Business fields remain in the returned result object, including false, 0, {}, and null:
| Tool | Business fields | Readiness and telephone outcome |
|---|---|---|
create_call, create_and_wait |
result.result |
result.result_status, result.call_outcome, result.error |
get_call |
result (also structured_result) |
result_status, call_outcome, error |
In Dify, bind the result object to a Code node to read a custom key. The variable picker lists the built-in fields; custom names are not added to it automatically. The complete json output is an array containing the tool response. A completed telephone outcome or an available result alone does not mean the recipient confirmed anything. Require the explicit business value as well. Keep empty, unavailable, and unknown answers in a separate branch.
Import appointment-confirmation.yml into Dify after installing this plugin and configuring its credentials. The workflow requests appointment_status and preferred_time, reads those fields in a Code node, and uses an If/Else node to select Confirmed, Reschedule requested, or Manual review. It does not interpret the call summary with another model or book a replacement appointment.
The example starts in dry run mode. Supply phone_number, appointment_time, and a call_request_id saved by your upstream application. Inspect the preview, then set the tool's dry_run=false and confirm_live_call=true only for an authorized recipient. Preserve these inputs and the schema when rerunning that same business event. If waiting times out, retain call_id and resume with get_call; do not create a replacement call.
list_goals and get_goal read the published Goal catalog. They do not create a call. Pass the previous page's next_cursor unchanged as after for the next page.
create_goal_run runs one published Goal for one E.164 recipient. The Dify input remains phone_number; the client sends it as phone to POST /v1/goals/{goal_id}/runs. Optional variables_json must contain a flat object of finite strings, numbers, or booleans matching the published Goal's input schema. Voice settings and result schemas belong to the published Goal. Save and reuse the same idempotency_key and inputs after a network failure.
create_goal_run_and_wait uses the same result_status readiness rule as Calls and returns the transcript independently of result availability. A timeout does not cancel the Run: use get_goal_run with the returned goal_run_id. Polling errors retain the accepted resource; uncertain creation requires an exact replay with the original key.
Goal Run tools follow the same live-call boundary as Call tools: live execution requires both dry_run=false and confirm_live_call=true.
Add Prepare CALL-E Goal Run before a creation tool. Its Published Goal selector lists the authenticated owner's executable Goals by title, published version, and stable Goal ID. It reads the existing Goal APIs, including every catalog page.
- Choose the intended Goal and run this read-only node once. The output describes input and result fields, required flags, types, enum options, defaults, and the immutable published RunSpec ID and version.
- Review those fields, then copy the node's
goal_jsonoutput into its Reviewed Goal interface configuration field. Save this value with the workflow. Keep it fixed across business events until you review another publication. - Map upstream business data into Mapped business variables JSON. Bind a Code node's output instead of assembling a JSON string in a prompt. Preparation merges the published defaults and checks missing, unknown, wrongly typed, and invalid enum values. Boolean
false, integer0, and other business values keep their types. - Gate the creation node on
ready=true. Bind its Goal ID and Variables JSON to the preparation node'sgoal_idandvariables_jsonoutputs. These routing outputs are empty when preparation fails. Theerrorsandchangesoutputs identify fields needing attention.
Preparation checks the current interface against the saved one, including result mappings. A new publication or changed field requirement stops this path until the operator reviews and updates the saved interface. Unsupported API/schema shapes also stop preparation with a field-level error. Keep the reviewed interface in configuration; binding it to the current get_goal response on every run would silently approve changes.
The API chooses the RunSpec at acceptance. A read-only preflight is not an atomic version selector: publishing between preparation and creation can still change the accepted version. Compare the returned Run's run_spec with the prepared ID and version before acting on its result. Retain the accepted Run ID when they differ; do not redial.
For recovery, retain the original business event, prepared variables, phone, and idempotency key before submission. If creation was uncertain, replay that exact saved request through the existing Create CALL-E Goal Run tool, which does not depend on the current Goal interface. If the Run ID is known, resume with Get CALL-E Goal Run. A failed configuration check for a new event is not a reason to change a saved recovery key.
Import goal-run-configuration.yml after installing this plugin and configuring its credentials. The example maps a saved record's reference to the published Goal's test_reference input, and its saved event_id to the idempotency key. Use a published integration-test Goal whose required input is a string test_reference and whose nullable confirmed result is yes, no, or unknown. For another Goal, edit the Map business fields and Check accepted version and result Code nodes to match the fields shown by preparation.
Select that Goal in Review and validate Goal inputs, run the workflow, review the returned field report, and save its current_goal_json in Reviewed Goal interface. Supply an owned or explicitly authorized E.164 phone number in business_record.phone_number. The placeholder number is blocked for live calls. Keep the record and event ID unchanged for replay. Only after inspecting the preview, change Execute reviewed Goal to dry_run=false and confirm_live_call=true for one authorized test.
The example gates creation on valid configuration, checks the accepted RunSpec again, and returns confirmed only for explicit result.confirmed=yes with an available result, completed telephone outcome, no error, and no timeout. Empty, unavailable, unknown, and changed-interface results remain for manual review. Preparation and its field selector never place a call. Mapped variables and Run outputs may contain personal data; restrict access to workflow runs and downstream consumers.
Version 0.2.0 moves new Calls to v2. Before upgrading a workflow:
- Add a durable
idempotency_keytocreate_callandcreate_and_wait; do not derive it from each retry or workflow execution ID. - Check stored
regionandlocalevalues. Older nodes may still have explicitUSanden-US; remove them only when you want inference for new requests. Preserve original inputs for retries. - Replace dependencies on recipient/attempt status, completion confidence, or evidence fields. V2 does not expose these fields. Read business fields from
result, telephone outcomes fromcall_outcome, and readiness fromresult_status. - Rename
list_goals.cursortoafter. Goal endpoints remain/v1/goals/.... - Keep historical v1 Call IDs with the original plugin/API interface. Reading an old ID through v2 does not migrate it. Do not create a replacement because an old ID cannot be read.
In Dify, bind Idempotency Key to an upstream field saved with the call request, such as call_request_id. The value can be a UUID generated once by your upstream application; it does not have to be an order number or something a user types. Save it before invoking CALL-E and keep it when retrying the node or rerunning the same business event. Generate a new value only for an intentionally new call.
For order 8472's first confirmation call, a saved value such as order-8472-confirmation-attempt-1 works. Supply that same value and unchanged call inputs on retries. Use order-8472-confirmation-attempt-2 only after deciding to place a second call. Do not use one fixed key for all calls or generate a fresh UUID on each workflow execution. Missing or invalid keys stop locally without sending a CALL-E request.
Dify provides plugin KV storage, but its tool context does not provide a durable logical-call identifier across separate Workflow runs. A plugin session ID changes per invocation, and an app or conversation ID can cover multiple calls. Storage alone therefore cannot tell the plugin which saved key to reuse. This version requires the upstream identifier rather than guessing from the phone number and task. It does not store or generate keys in plugin KV storage.
See the migration guide, Calls contract, and Goal Runs contract.
- Requires E.164 destination phone numbers, for example
+15555550123. - Rejects common placeholder numbers for live calls.
- Masks destination phone fields and phone fields in metadata and error details. Call business results retain their original values and types; results and transcript text may contain personal data.
- Does not create recurring schedules.
- Does not expose API keys in logs or responses.
- Defaults to
dry_run=true.
For recurring workflows, keep recurrence in the host scheduler. CALL-E should receive exactly one call request per scheduled run.
Create a virtual environment and install dependencies:
python3.12 -m venv .venv
source .venv/bin/activate
python -m pip install -e ".[dev]"Run the local checks:
python -m pytest
python -m compileall provider tools utils main.pyRun the plugin locally:
python main.pyFrom the parent directory:
dify plugin package ./call-e-dify-pluginThe package command creates a .difypkg file that can be uploaded to Dify or attached to a GitHub Release.
- Update
versioninmanifest.yamlandpyproject.toml. - Run tests and compile checks.
- Package with
dify plugin package ./call-e-dify-plugin. - Create a GitHub Release.
- Upload the
.difypkgas a release asset.
Use live-call tools only for numbers you own or are explicitly authorized to call. Do not use this plugin for emergency services, deception, harassment, or hidden recurring outreach. For medical, legal, financial, or emergency contexts, keep the call task bounded to administrative or informational workflows and avoid professional advice or urgent decision-making.