MCP server docs / Recipes

MCP recipes

StableLast updated View as Markdown

This page walks through nine everyday tasks done through the Queue CallBack MCP server: for each, an example prompt, the tool calls an agent makes in order with their key arguments, what comes back, the scopes it needs and the pitfalls. The reporting tasks only read. The callback, queue, organization and IVR tasks change something, and each says how the change is confirmed.

For AI agents: this page is also available as Markdown at https://queuecallback.com/docs/mcp/recipes.md. The whole MCP documentation in one file: https://queuecallback.com/docs/llms-full.txt

How to read the examples:

  • Values in angle brackets, such as "<queue id>", are placeholders. Take every id from a list or get tool earlier in the same conversation. Never invent an id or a token.
  • Responses are trimmed to the fields the task needs. A successful call answers with one JSON object, both in structuredContent and as JSON text. A refused call sets isError: true, explains the fix in plain text, and puts code (and retry_after_seconds where retrying helps) in structuredContent.error.
  • Tool names are shown bare (get_callback_summary). Your client may prefix them, for example mcp__queuecallback__get_callback_summary.
  • Every argument is listed in the tool reference, for example the get_callback_summary reference.

Recipes at a glance

Recipe Scopes Changes something Confirmation
Yesterday's callbacks by queue reports:read No None
One caller's journey reports:read (pii:read to search by number) No None
Waiting callbacks right now reports:read, callbacks:read No None
IVR menu choices last week reports:read, as an organization admin with the Custom IVR add-on No None
Complete a callback by hand callbacks:read, callbacks:write Yes, places no call The agent asks the user
Requeue an expired callback callbacks:read, callbacks:dial Yes, places real calls Approval prompt on every call, two-call confirm token
Change a queue's callback settings, voice or language queues:read, queues:write Yes Approval prompt on every call, two-call confirm token
Rename the organization org:read, org:write Yes The agent asks the user
A small IVR change (Preview) ivr:read, ivr:write, ivr:publish Yes, callers hear it after publish Approval prompt on publish_flow, token from validate_flow

Claude Code asks the user before every call of the tools marked "Approval prompt on every call", even when an allow rule matches. Other clients, Claude Desktop through mcp-remote included, may not, so the agent also asks in the conversation (approval prompts). For the tasks marked "The agent asks the user", no client prompt is forced, so the agent confirms in the conversation before it calls the tool. Queue CallBack can switch changes off on the server; while it is off, clients see only the read-only tools. The full scope list is on the access page.

Before you start

Call get_me first. It takes no arguments and needs no scope. Read these fields from its answer:

Field What it tells the agent
scopes The scopes that take effect for this key. A recipe whose scopes are missing will not find its tools.
pii_allowed false: caller and callback numbers keep only their last four digits (+*******4567), keyed-in digits read [redacted], and searching by phone number is refused. See caller data.
custom_ivr_enabled false: the IVR flow tools are not listed.
ivr_reporting_enabled false: the five IVR report tools are not listed, so the IVR recipe cannot run. The callback reports still work.
role The role the key acts with, for example org_admin. Queue settings changes and renames need an organization admin.

Check the connection shows a full get_me answer.

Time zones in reports

Queue CallBack reports count days and show times in a time zone, and the default is America/New_York. An agent that leaves the zone out gives a wrong answer to anyone outside US Eastern time, with no error.

Tools Argument Default
get_callback_summary, list_callbacks, get_callback_journey, get_ivr_summary, list_ivr_calls, get_flow_decisions, get_live_stats tz (get_live_stats also still accepts timezone, the deprecated old name) America/New_York
list_callback_queue none: time_range is a rolling window (1h, 24h, 7d, 30d); today also means the last 24 hours, not since midnight —
  • Pass an IANA zone name such as America/Chicago, Europe/London or Australia/Sydney. The zone sets where each day starts and ends, the buckets of a series, and the offset of every time in the answer. For get_live_stats, today runs from local midnight to the next local midnight in that zone.

  • An unknown name is refused with HTTP 400 and changes nothing: "Invalid tz: must be an IANA timezone name" from most tools, and the code invalid_timezone from get_live_stats. Fix the name; do not retry it unchanged.

  • get_organization carries no time zone. Ask the user which zone their reports use, and name the zone in the answer.

  • preset is today, yesterday, 7d (the default), 30d or 90d. 7d means the last 7 days including today, not last calendar week.

  • For a calendar week or month, send from and to (YYYY-MM-DD, both days included, in tz). Together they replace preset. One request spans at most 400 days by default.

Yesterday's callbacks by queue

The task: yesterday's callback totals, completion rate and waits, split by queue.

Example prompt: "How did yesterday go? Callbacks per queue, the completion rate and the median wait. We're on Central time."

Scopes: reports:read.

  1. Call get_callback_summary for the whole organization:

    {"preset": "yesterday", "tz": "America/Chicago"}
    

    It returns (trimmed):

    {
      "totals": {"completed": 41, "expired": 5, "failed": 1, "cancelled": 2, "active": 0, "total": 49},
      "completionRate": 83.67,
      "waitMedianSeconds": 210,
      "waitAvgSeconds": 254.3,
      "bucket": "hour",
      "series": [{"t": "2026-11-04T09:00:00-06:00", "completed": 6, "expired": 1, "failed": 0, "cancelled": 0, "waitMedianSeconds": 195, "waitAvgSeconds": 220.5}],
      "byQueue": [
        {"configurationId": "<queue id>", "name": "Main line", "extensionNumber": "8001", "count": 31},
        {"configurationId": "<queue id>", "name": "Billing", "extensionNumber": "8002", "count": 18}
      ],
      "topAgents": [{"agentName": "<agent name>", "agentExtensionNumber": "101", "count": 12, "waitMedianSeconds": 190}],
      "liveNow": {"waiting": 2, "inProgress": 1}
    }
    
  2. For one queue's statuses and waits, call get_callback_summary again with that queue's configurationId as queue_id:

    {"preset": "yesterday", "tz": "America/Chicago", "queue_id": "<queue id>"}
    

What the fields mean:

  • completionRate is completed callbacks as a percentage of finished ones (0 when none finished). Completed covers callbacks where an agent took the callback and the transfer to the customer was started, and callbacks marked complete by hand with complete_callback (transferMethod manual in list_callbacks).
  • waitMedianSeconds and waitAvgSeconds cover completed callbacks, and are null when none completed.
  • byQueue gives only a count per queue, most first. name reads (deleted) for a queue that no longer exists.
  • topAgents lists agents by completed callbacks, at most 50.
  • liveNow counts callbacks waiting and being put through right now, whatever the date range.

Pitfalls:

  • tz defaults to America/New_York.
  • Without status, only finished callbacks are counted (completed, expired, failed, cancelled). To include callbacks from that day that are still open, send "status": "completed,expired,failed,cancelled,active".
  • With bucket left at auto, the series uses hours for ranges up to 2 days and days for longer ones.
  • A busy reporting service refuses with a retry delay. Wait retry_after_seconds, then repeat (retries).

One caller's journey

The task: find one callback and show what happened to it, including the IVR steps of the call it came from.

Example prompt: "A customer whose number ends in 4567 says nobody called back yesterday afternoon. What happened?"

Scopes: reports:read. Searching by phone number also needs an effective pii:read (pii_allowed: true in get_me).

  1. Find the callback with list_callbacks. With pii_allowed: true, search by number. Send digits only, without spaces or dashes: the search matches part of the number as it is stored.

    {"preset": "yesterday", "tz": "America/Chicago", "status": "completed,expired,failed,cancelled,active", "search": "4567"}
    

    With pii_allowed: false, a search made only of digits and phone punctuation is refused with insufficient_scope. Leave search out, narrow with queue_id or agent, and scan the masked numbers for the last four digits:

    {"preset": "yesterday", "tz": "America/Chicago", "status": "completed,expired,failed,cancelled,active", "sort": "created", "dir": "asc", "limit": 100}
    

    It returns callbacks (each with id, createdAt, callbackNumber, callerNumber, status, waitSeconds, agentName, configurationName, attemptCount, requeueCount, source) plus total, limit and offset. For the next page, raise offset by limit.

  2. Call get_callback_journey with the callback's id:

    {"callback_id": "<callback id>", "tz": "America/Chicago"}
    

    It returns (trimmed, masked key):

    {
      "callback": {
        "id": "<callback id>",
        "createdAt": "2026-11-04T14:10:00-06:00",
        "callbackNumber": "+*******4567",
        "status": "expired",
        "waitSeconds": null,
        "configurationName": "Billing",
        "attemptCount": 2,
        "requeueCount": 0,
        "expiresAt": "2026-11-04T15:10:00-06:00",
        "transferInitiatedAt": null,
        "completedAt": null
      },
      "ivrSteps": [
        {"index": 0, "nodeName": "Greeting", "nodeType": "<node type>", "enteredAt": "2026-11-04T14:09:20-06:00", "durationMs": 4000, "input": null, "route": null, "error": null, "branch": "default"},
        {"index": 1, "nodeName": "Main menu", "nodeType": "<node type>", "enteredAt": "2026-11-04T14:09:24-06:00", "durationMs": 5000, "input": "[redacted]", "route": null, "error": null, "branch": "input_equals:2"}
      ]
    }
    

    waitSeconds is null for an expired, failed or cancelled callback that never reached an agent. For a callback still waiting, it counts up to now.

Pitfalls:

  • tz defaults to America/New_York, so "yesterday afternoon" can fall on the wrong day without it.
  • Without status, list_callbacks lists only finished callbacks; callbacks still open need active.
  • Several callers can share the last four digits. Confirm the match with the user by time and queue before acting on it.
  • Without pii:read, each step's input reads [redacted] and phone numbers in route and error are masked.
  • ivrSteps is empty when the callback did not come from an IVR call.

Waiting callbacks right now

The task: how many callbacks are waiting now, and which ones.

Example prompt: "How many callbacks are waiting right now, and which have been waiting longest?"

Scopes: reports:read for the counts, callbacks:read for the list.

  1. Call get_live_stats with the user's zone in tz:

    {"tz": "America/Chicago"}
    

    It returns (trimmed):

    {
      "success": true,
      "data": {
        "totalQueued": 3,
        "totalActive": 1,
        "totalProcessing": 0,
        "totalRetry": 0,
        "queueDepth": 3,
        "totalCompleted": 22,
        "totalFailed": 0,
        "totalExpired": 2,
        "totalCancelled": 1,
        "successRate": 88,
        "userTimezone": "America/Chicago",
        "todayStart": "2026-11-05T00:00:00-06:00",
        "todayEnd": "2026-11-06T00:00:00-06:00",
        "refreshRate": 30
      }
    }
    

    totalQueued, totalActive (in their call now), totalProcessing and totalRetry count callbacks by their current status, with no time limit. queueDepth is totalQueued plus totalProcessing. totalCompleted, totalFailed, totalExpired and totalCancelled count callbacks created between todayStart and todayEnd: the local day in tz, from midnight to midnight, shown with the zone's offset. successRate is totalCompleted as a percentage of those four counts together.

  2. List the open callbacks with list_callback_queue. Without time_range, status active lists every open callback, however old:

    {"status": "active", "limit": 50}
    

    It returns callbacks (each with id, callbackNumber, status, attemptCount, requeueCount, nextAttempt, expiresAt, createdAt, agentName; requeueCount here repeats attemptCount) plus total, page, pageSize, totalPages and hasNextPage. The list is newest first, so the longest-waiting callbacks are at the end of the last page.

Pitfalls:

  • tz defaults to America/New_York. get_live_stats still accepts timezone, the old name of tz; send tz. If both are sent, they must name the same zone, or the call is refused.
  • list_callback_queue pages with page (from 1), not offset. Its status field shows the raw status, such as queued. active as a filter means every callback not completed, failed, expired or cancelled; it does not mean every row is waiting to be dialed now, so read each row's status and nextAttempt. New statuses can appear: treat a status you do not know as open, not final.
  • Sending time_range with status active narrows the list to callbacks created in that window.
  • These are point-in-time reads, not a stream. Call again for fresh numbers; refreshRate is the seconds between refreshes the server recommends.

IVR menu choices last week

The task: which options callers chose in an IVR menu during last calendar week, and where they hung up.

Example prompt: "Which menu options did callers pick on the main line last week, Monday to Sunday, and where did they drop off?"

Scopes: reports:read. The IVR call reports are for organization admins of organizations with the Custom IVR add-on: get_me must show ivr_reporting_enabled: true. Otherwise the tools below are not listed, and a call to one answers ivr_reporting_not_enabled.

  1. Pick the flow with list_ivr_flow_stats (no arguments needed). It returns flows, most used first, each with flowId, name, configurationName, extensionNumber, totalCalls and lastUsedAt.

  2. Call get_flow_decisions with explicit dates (here, for a request made on Thursday 2026-11-05):

    {"flow_id": "<flow id>", "from": "2026-10-26", "to": "2026-11-01", "tz": "America/Chicago"}
    

    It returns (trimmed, masked key):

    {
      "flowId": "<flow id>",
      "flowName": "Main line callback flow",
      "nodes": [
        {
          "nodeId": "<node id>",
          "name": "Main menu",
          "nodeType": "<node type>",
          "hits": 420,
          "endedHereCount": 18,
          "isBranching": true,
          "branches": [
            {"condition": "input_equals:1", "targetNodeId": "<node id>", "targetName": "Sales", "count": 250, "isSelfLoop": false},
            {"condition": "input_equals:2", "targetNodeId": "<node id>", "targetName": "Request a callback", "count": 152, "isSelfLoop": false}
          ],
          "variable": "menu_choice",
          "topValues": [{"value": "[redacted]", "count": 250}, {"value": "[redacted]", "count": 152}]
        }
      ]
    }
    
  3. Optional: get_ivr_summary with the same flow_id, from, to and tz gives outcome rates (handed off, callback requested, completed, abandoned) and the nodes where abandoned calls ended.

What the fields mean: hits counts how many times calls entered the node (a caller who hears the menu twice counts twice), endedHereCount counts calls whose last node it was (the drop-off), and branches lists the exits calls took, most first.

Pitfalls:

  • preset 7d is the last 7 days including today, not last calendar week. Use from and to.
  • tz defaults to America/New_York.
  • Without pii:read, topValues[].value reads [redacted]; the counts stay. The caller filter needs pii:read.
  • branches is empty for a node that does not branch.

Complete a callback by hand

The task: mark an open callback as done so it leaves the queue and is not dialed again.

Example prompt: "The customer on the 9:40 callback in the support queue already reached us by email. Take it out of the queue."

Scopes: callbacks:read, callbacks:write.

  1. Find the callback and check it is still open with list_callback_queue:

    {"status": "active", "time_range": "24h"}
    

    list_callback_queue rows name no queue. When metadata carries configuration_id, match it against the id values from list_queues (needs queues:read). Otherwise find the callback with list_callbacks and "status": "active" (needs reports:read), whose rows carry configurationName.

  2. Show the user the callback (queue, time, masked number) and get a clear yes. complete_callback has no confirm token and no forced approval prompt, so the agent must ask.

  3. Call complete_callback:

    {"callback_id": "<callback id>"}
    

    It returns:

    {"message": "Callback completed successfully", "id": "<callback id>", "completedBy": "Claude Code (API key)"}
    

    completedBy is the API key's name followed by (API key). agentName is added when an agent took the call.

Pitfalls:

  • complete_callback places no call. The callback's status becomes completed with transfer method manual, so it counts as completed in callback reports and in the completion rate.
  • A callback already completed or cancelled answers 404. A retry of a call that worked also answers 404, so check list_callback_queue before telling the user it failed.
  • There is no undo: requeue_callback takes only expired, failed or cancelled callbacks.

Requeue an expired callback

The task: put an expired callback back in the queue. This places real calls: the dialer rings the queue and, once an agent answers, calls the customer.

Example prompt: "The 2:10 pm callback on the billing queue expired before anyone took it. Requeue it."

Scopes: callbacks:read and callbacks:dial. The portal never preselects callbacks:dial, and it warns that the scope places real phone calls and belongs only to a client you trust to dial.

  1. Find the callback with list_callback_queue:

    {"status": "expired", "time_range": "24h"}
    

    Check that its status is expired, failed or cancelled, and note which one it is. In list_callback_queue, requeueCount repeats attemptCount; read the real requeue count from get_callback_journey or list_callbacks (both need reports:read).

    list_callback_queue rows name no queue. When metadata carries configuration_id, match it against the id values from list_queues (needs queues:read). Otherwise find the callback with list_callbacks and "status": "expired" (needs reports:read), whose rows carry configurationName.

  2. Show the user which callback will be dialed (queue, time, masked number).

  3. First call of requeue_callback, without a token. It changes nothing and sends nothing to the Queue CallBack API:

    {"callback_id": "<callback id>"}
    

    It returns:

    {
      "confirmation_required": true,
      "tool": "requeue_callback",
      "will_apply": {"callback_id": "<callback id>"},
      "confirm_token": "<confirm token>",
      "confirm_expires_at": "2026-11-05T20:25:00Z",
      "message": "Nothing was changed yet. Show the user what requeue_callback will change. Once they agree, call requeue_callback again with the same arguments and confirm_token set to confirm_token (single use, valid until confirm_expires_at). Send an idempotency_key with that call and reuse it on any retry, so the change is never applied twice."
    }
    
  4. Once the user agrees, the second call with the token and a fresh idempotency_key (letters, digits, . _ : -, at most 255 characters; a random UUID works). The confirm token is bound to callback_id only, and the first call sends nothing to the Queue CallBack API, so the key matters only on this second call: send it here.

    {"callback_id": "<callback id>", "confirm_token": "<confirm token>", "idempotency_key": "<fresh random key>"}
    

    It returns (trimmed):

    {
      "request_id": "<request id>",
      "callback_id": "<callback id>",
      "status": "pending",
      "handle": "<handle>",
      "handle_expires_at": "2026-11-06T20:16:00Z",
      "handle_status_tool": "get_requeue_status"
    }
    
  5. Follow it with get_requeue_status every few seconds until status is succeeded, failed, conflict or interrupted. A requeue can take about a minute.

    {"handle": "<handle>"}
    

    It returns (trimmed):

    {
      "request_id": "<request id>",
      "callback_id": "<callback id>",
      "status": "succeeded",
      "mode": "async",
      "idempotency_key": "<fresh random key>",
      "completed_at": "2026-11-05T20:16:40Z",
      "result": {"http_status": 200, "body": {"message": "Callback requeued successfully", "callback_id": "<callback id>"}},
      "handle_kind": "requeue"
    }
    

Claude Code shows the approval prompt on every call of requeue_callback, the first call included. Other clients, Claude Desktop through mcp-remote included, may not, so the agent also asks in the conversation.

Never re-run a requeue without checking. If the answer to step 4 was lost, or came back as backend_timeout:

  1. Do not repeat step 4 with the same token. The token was spent when the call ran, so the repeat answers confirm_refused with confirm_token_spent: true. A second call that failed says so itself: its error carries confirm_token_spent: true and a next_step with the steps below.
  2. If you have the handle, call get_requeue_status. If not, call list_callback_queue with "search": "<callback id>" and "time_range": "30d". A requeued callback is back in queued (or a later status). Confirm with requeueCount from get_callback_journey (needs reports:read).
  3. If it was not requeued and the user still wants it: make the first call again for a new token, get the user's yes, then send the second call with the same idempotency_key as before. If the first attempt reached Queue CallBack, this returns that attempt's recorded status (and a handle) instead of dialing again. If it never arrived, the requeue runs now, once.
  4. If the recorded status is failed or conflict and the user still wants the requeue, make a new first call and send a new idempotency_key. The old key only replays the failed attempt.

Pitfalls:

  • interrupted means the server stopped before recording the outcome. Check the callback before requeueing again.
  • At most 5 requeues per callback in 24 hours by default; more answers requeue_limit_reached.
  • A callback that is still waiting cannot be requeued; the API refuses it.
  • Idempotency keys are unique per organization. A key already used for another callback answers idempotency_key_reused, so use a fresh random key for each requeue.
  • The confirm token is single use, valid 10 minutes and bound to callback_id (confirm tokens).

Change a queue's callback timeout and requeue settings

The task: change how long callbacks wait before they expire, and whether and how often expired callbacks are dialed again.

Example prompt: "On the support queue, let callbacks wait 30 minutes before they expire, and requeue expired ones up to 3 times."

Scopes: queues:read, queues:write. The key's creator must be an organization admin. The queue itself is created in the portal; these tools change its settings.

Portal label Argument Values
Queue Timeout (minutes) callback_timeout_minutes 1 to 1440
Auto-Requeue Expired Callbacks auto_requeue true or false
Max Requeue Attempts max_requeue_attempts 1 to 10
Manual Callback Only manual_callback true or false; stored and shown in the portal, nothing in the call path reads it
  1. Find the queue with list_queues (no arguments needed). It returns configurations, each with id and name.

  2. Read the current values with get_queue:

    {"queue_id": "<queue id>"}
    

    The answer carries callback_timeout_minutes, auto_requeue, max_requeue_attempts and manual_callback, among the queue's other settings.

  3. Show the user each current value next to the new one.

  4. First call of update_queue_settings, without a token. It changes nothing:

    {"queue_id": "<queue id>", "callback_timeout_minutes": 30, "auto_requeue": true, "max_requeue_attempts": 3}
    

    It returns confirmation_required: true, tool: "update_queue_settings", will_apply (the arguments above, echoed), a single-use confirm_token, confirm_expires_at (10 minutes) and a message that says nothing was changed yet.

  5. Once the user agrees, repeat exactly the same arguments with the token:

    {"queue_id": "<queue id>", "callback_timeout_minutes": 30, "auto_requeue": true, "max_requeue_attempts": 3, "confirm_token": "<confirm token>"}
    

    It returns:

    {
      "queue_id": "<queue id>",
      "settings": {"callback_timeout_minutes": 30, "max_requeue_attempts": 3, "auto_requeue": true, "manual_callback": false},
      "changes": [
        {"field": "callback_timeout_minutes", "from": 60, "to": 30, "takes_effect": "applies to callbacks queued from now on; callbacks already waiting keep their expiry"},
        {"field": "auto_requeue", "from": false, "to": true, "takes_effect": "applies at the next expiry of every callback of the queue, those already waiting included"}
      ],
      "updated_at": "2026-11-05T20:31:02Z",
      "message": "2 setting(s) changed (changes says when each takes effect). The queue keeps running and was not re-registered."
    }
    

    A setting that already had the new value is not listed in changes. Here max_requeue_attempts was already 3.

Pitfalls:

  • will_apply only echoes the arguments; nothing is read from the queue. Read get_queue before the first call to show what changes.
  • The second call must send the same settings with the same values. Adding, dropping or changing one answers confirm_refused and changes nothing; make the first call again.
  • Give at least one setting. Only the settings given change. The queue keeps running, and its voice, extensions and status stay as they are. The requeue delay cannot be set.
  • Out-of-range values are refused on the first call, before a token is issued.
  • An organization can make a few settings changes at once, then about one a minute; more answers queue_write_limited with retry_after_seconds. A call that changes nothing is not counted.
  • If Queue CallBack has switched queue settings changes off, update_queue_settings is not listed or answers queue_settings_disabled. Change the settings in the portal under Configurations.

A queue's voice or language changes the same way, in two calls with a confirm token, after the user agrees:

  • Voice: list_voices for an active voice id, get_queue for the current voice, then set_queue_voice with queue_id, voice and optionally speed (0.5 to 2.0).
  • Language: get_queue for the current language, then set_queue_language with queue_id and language (en-US, es-ES or fr-FR).
  • A stored change reaches the queue's next callers. The server first renders the queue's prompts in the new voice or language (up to 15 seconds), and the answer's warm says what is still being prepared, so prefer a quiet time. Voice and language changes count against their own queue_write_limited limit.

Rename the organization

The task: change the organization's name.

Example prompt: "Rename our organization to Example Plumbing and Heating."

Scopes: org:read, org:write. The key's creator must be an organization admin.

  1. Read the current name with get_organization (no arguments). It returns id, name, created_at and assistant_enabled.

  2. Confirm the exact new name with the user, spelling and capitals included.

  3. Call set_organization_name:

    {"name": "Example Plumbing and Heating"}
    

    It returns:

    {"message": "Organization updated successfully", "updated_at": "2026-11-05T20:40:00Z"}
    
  4. Optional: call get_organization again to show the new name.

Pitfalls:

  • set_organization_name runs on the first call: no confirm token and no forced approval prompt. The agent must confirm first.
  • Every user of the organization sees the new name in the portal.
  • The name is 1 to 200 characters.
  • About one rename every 6 seconds; faster answers 429.

A small IVR change (Preview)

The task: change one step of an IVR flow and publish it, with the user seeing the change first. The IVR tools are Preview: node types, their fields and validation messages will change, so discover them at runtime (node types). The full loop, with restore and discard, is in Edit a flow safely.

Example prompt: "In the main line flow, change the greeting to say we close at 6 pm on Fridays. Show me the change before it goes live."

Scopes: ivr:read, ivr:write, ivr:publish. The organization needs the Custom IVR add-on (custom_ivr_enabled: true) and must edit its flows through drafts.

  1. list_flows (no arguments needed) to find the flow's id.

  2. get_flow with {"flow_id": "<flow id>"} to read the nodes, each node's config, and draft.etag.

  3. Optional: create_flow_draft with {"flow_id": "<flow id>"} opens a draft without changing anything (edit_flow_draft starts one from the live graph by itself). It returns created, etag and message. Callers keep hearing the live flow.

  4. get_node_type for the node's type, if the config needs a field you have not seen.

  5. edit_flow_draft with one operation. config_json replaces the whole config, so start from the config get_flow returned:

    {
      "flow_id": "<flow id>",
      "etag": "<draft.etag from get_flow, or the etag from create_flow_draft if you called it>",
      "ops": [
        {"op": "update_node", "node_id": "<node id>", "name": "", "config_json": "<the node's whole config with the one change, as a JSON-encoded string>"}
      ]
    }
    

    An empty name keeps the current name. The answer carries the new etag.

  6. validate_flow with {"flow_id": "<flow id>"}. It returns valid, errors, warnings, digest, draft.base_version, confirm_token and confirm_for: "publish_flow".

  7. diff_flow with {"flow_id": "<flow id>"}. The default compares the published version with the draft, which is exactly what a publish changes. Show it to the user.

  8. Once the user agrees, publish_flow (asks for approval on every call):

    {"flow_id": "<flow id>", "expected_digest": "<digest from validate_flow>", "base_version": 4, "confirm_token": "<confirm token from validate_flow>"}
    

    It returns message, version (with version_number) and changes. When prompts are still being rendered, it also returns a handle for get_publish_status.

Pitfalls:

  • A stale etag answers revision_mismatch with the current etag, and nothing changes. Read the flow again before retrying.
  • Any edit after validate_flow makes the publish fail. Validate again for a new digest and token.
  • An organization that edits flows live, not through drafts, is refused; use the portal's flow builder.