MCP server docs / Recipes
MCP recipes
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
structuredContentand as JSON text. A refused call setsisError: true, explains the fix in plain text, and putscode(andretry_after_secondswhere retrying helps) instructuredContent.error. - Tool names are shown bare (
get_callback_summary). Your client may prefix them, for examplemcp__queuecallback__get_callback_summary. - Every argument is listed in the tool reference, for example the
get_callback_summaryreference.
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/LondonorAustralia/Sydney. The zone sets where each day starts and ends, the buckets of a series, and the offset of every time in the answer. Forget_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_timezonefromget_live_stats. Fix the name; do not retry it unchanged.get_organizationcarries no time zone. Ask the user which zone their reports use, and name the zone in the answer.presetistoday,yesterday,7d(the default),30dor90d.7dmeans the last 7 days including today, not last calendar week.For a calendar week or month, send
fromandto(YYYY-MM-DD, both days included, intz). Together they replacepreset. 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.
Call
get_callback_summaryfor 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} }For one queue's statuses and waits, call
get_callback_summaryagain with that queue'sconfigurationIdasqueue_id:{"preset": "yesterday", "tz": "America/Chicago", "queue_id": "<queue id>"}
What the fields mean:
completionRateis 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 withcomplete_callback(transferMethodmanualinlist_callbacks).waitMedianSecondsandwaitAvgSecondscover completed callbacks, and arenullwhen none completed.byQueuegives only acountper queue, most first.namereads(deleted)for a queue that no longer exists.topAgentslists agents by completed callbacks, at most 50.liveNowcounts callbacks waiting and being put through right now, whatever the date range.
Pitfalls:
tzdefaults toAmerica/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
bucketleft atauto, 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).
Find the callback with
list_callbacks. Withpii_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 withinsufficient_scope. Leavesearchout, narrow withqueue_idoragent, 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 withid,createdAt,callbackNumber,callerNumber,status,waitSeconds,agentName,configurationName,attemptCount,requeueCount,source) plustotal,limitandoffset. For the next page, raiseoffsetbylimit.Call
get_callback_journeywith the callback'sid:{"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"} ] }waitSecondsisnullfor an expired, failed or cancelled callback that never reached an agent. For a callback still waiting, it counts up to now.
Pitfalls:
tzdefaults toAmerica/New_York, so "yesterday afternoon" can fall on the wrong day without it.- Without
status,list_callbackslists only finished callbacks; callbacks still open needactive. - 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'sinputreads[redacted]and phone numbers inrouteanderrorare masked. ivrStepsis 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.
Call
get_live_statswith the user's zone intz:{"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),totalProcessingandtotalRetrycount callbacks by their current status, with no time limit.queueDepthistotalQueuedplustotalProcessing.totalCompleted,totalFailed,totalExpiredandtotalCancelledcount callbacks created betweentodayStartandtodayEnd: the local day intz, from midnight to midnight, shown with the zone's offset.successRateistotalCompletedas a percentage of those four counts together.List the open callbacks with
list_callback_queue. Withouttime_range,statusactivelists every open callback, however old:{"status": "active", "limit": 50}It returns
callbacks(each withid,callbackNumber,status,attemptCount,requeueCount,nextAttempt,expiresAt,createdAt,agentName;requeueCounthere repeatsattemptCount) plustotal,page,pageSize,totalPagesandhasNextPage. The list is newest first, so the longest-waiting callbacks are at the end of the last page.
Pitfalls:
tzdefaults toAmerica/New_York.get_live_statsstill acceptstimezone, the old name oftz; sendtz. If both are sent, they must name the same zone, or the call is refused.list_callback_queuepages withpage(from 1), notoffset. Itsstatusfield shows the raw status, such asqueued.activeas 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'sstatusandnextAttempt. New statuses can appear: treat a status you do not know as open, not final.- Sending
time_rangewithstatusactivenarrows the list to callbacks created in that window. - These are point-in-time reads, not a stream. Call again for fresh numbers;
refreshRateis 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.
Pick the flow with
list_ivr_flow_stats(no arguments needed). It returnsflows, most used first, each withflowId,name,configurationName,extensionNumber,totalCallsandlastUsedAt.Call
get_flow_decisionswith 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}] } ] }Optional:
get_ivr_summarywith the sameflow_id,from,toandtzgives 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:
preset7dis the last 7 days including today, not last calendar week. Usefromandto.tzdefaults toAmerica/New_York.- Without
pii:read,topValues[].valuereads[redacted]; the counts stay. Thecallerfilter needspii:read. branchesis 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.
Find the callback and check it is still open with
list_callback_queue:{"status": "active", "time_range": "24h"}list_callback_queuerows name no queue. Whenmetadatacarriesconfiguration_id, match it against theidvalues fromlist_queues(needsqueues:read). Otherwise find the callback withlist_callbacksand"status": "active"(needsreports:read), whose rows carryconfigurationName.Show the user the callback (queue, time, masked number) and get a clear yes.
complete_callbackhas no confirm token and no forced approval prompt, so the agent must ask.Call
complete_callback:{"callback_id": "<callback id>"}It returns:
{"message": "Callback completed successfully", "id": "<callback id>", "completedBy": "Claude Code (API key)"}completedByis the API key's name followed by(API key).agentNameis added when an agent took the call.
Pitfalls:
complete_callbackplaces no call. The callback's status becomescompletedwith transfer methodmanual, 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_queuebefore telling the user it failed. - There is no undo:
requeue_callbacktakes 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.
Find the callback with
list_callback_queue:{"status": "expired", "time_range": "24h"}Check that its
statusisexpired,failedorcancelled, and note which one it is. Inlist_callback_queue,requeueCountrepeatsattemptCount; read the real requeue count fromget_callback_journeyorlist_callbacks(both needreports:read).list_callback_queuerows name no queue. Whenmetadatacarriesconfiguration_id, match it against theidvalues fromlist_queues(needsqueues:read). Otherwise find the callback withlist_callbacksand"status": "expired"(needsreports:read), whose rows carryconfigurationName.Show the user which callback will be dialed (queue, time, masked number).
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." }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 tocallback_idonly, 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" }Follow it with
get_requeue_statusevery few seconds untilstatusissucceeded,failed,conflictorinterrupted. 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:
- Do not repeat step 4 with the same token. The token was spent when the call ran, so the repeat answers
confirm_refusedwithconfirm_token_spent: true. A second call that failed says so itself: its error carriesconfirm_token_spent: trueand anext_stepwith the steps below. - If you have the handle, call
get_requeue_status. If not, calllist_callback_queuewith"search": "<callback id>"and"time_range": "30d". A requeued callback is back inqueued(or a later status). Confirm withrequeueCountfromget_callback_journey(needsreports:read). - 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_keyas 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. - If the recorded status is
failedorconflictand the user still wants the requeue, make a new first call and send a newidempotency_key. The old key only replays the failed attempt.
Pitfalls:
interruptedmeans 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 |
Find the queue with
list_queues(no arguments needed). It returnsconfigurations, each withidandname.Read the current values with
get_queue:{"queue_id": "<queue id>"}The answer carries
callback_timeout_minutes,auto_requeue,max_requeue_attemptsandmanual_callback, among the queue's other settings.Show the user each current value next to the new one.
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-useconfirm_token,confirm_expires_at(10 minutes) and amessagethat says nothing was changed yet.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. Heremax_requeue_attemptswas already 3.
Pitfalls:
will_applyonly echoes the arguments; nothing is read from the queue. Readget_queuebefore 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_refusedand 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_limitedwithretry_after_seconds. A call that changes nothing is not counted. - If Queue CallBack has switched queue settings changes off,
update_queue_settingsis not listed or answersqueue_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_voicesfor an active voice id,get_queuefor the current voice, thenset_queue_voicewithqueue_id,voiceand optionallyspeed(0.5 to 2.0). - Language:
get_queuefor the current language, thenset_queue_languagewithqueue_idandlanguage(en-US,es-ESorfr-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
warmsays what is still being prepared, so prefer a quiet time. Voice and language changes count against their ownqueue_write_limitedlimit.
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.
Read the current name with
get_organization(no arguments). It returnsid,name,created_atandassistant_enabled.Confirm the exact new name with the user, spelling and capitals included.
Call
set_organization_name:{"name": "Example Plumbing and Heating"}It returns:
{"message": "Organization updated successfully", "updated_at": "2026-11-05T20:40:00Z"}Optional: call
get_organizationagain to show the new name.
Pitfalls:
set_organization_nameruns 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.
list_flows(no arguments needed) to find the flow'sid.get_flowwith{"flow_id": "<flow id>"}to read the nodes, each node's config, anddraft.etag.Optional:
create_flow_draftwith{"flow_id": "<flow id>"}opens a draft without changing anything (edit_flow_draftstarts one from the live graph by itself). It returnscreated,etagandmessage. Callers keep hearing the live flow.get_node_typefor the node's type, if the config needs a field you have not seen.edit_flow_draftwith one operation.config_jsonreplaces the whole config, so start from the configget_flowreturned:{ "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
namekeeps the current name. The answer carries the new etag.validate_flowwith{"flow_id": "<flow id>"}. It returnsvalid,errors,warnings,digest,draft.base_version,confirm_tokenandconfirm_for: "publish_flow".diff_flowwith{"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.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(withversion_number) andchanges. When prompts are still being rendered, it also returns ahandleforget_publish_status.
Pitfalls:
- A stale etag answers
revision_mismatchwith the current etag, and nothing changes. Read the flow again before retrying. - Any edit after
validate_flowmakes 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.