Building with AI
The four places AI appears on the Tandem platform, and how to point your own AI assistant at Tandem.
AI appears in four places on Tandem. They are separate features with separate configuration, and picking the wrong one is the usual reason something is harder to build than it should be. If you want to go the other way and give the AI assistant you already work with access to Tandem, skip to Connect your own AI assistant.
Voice agents
A voice agent answers a phone call, listens, decides what to say, calls out to your systems when it needs to, and reports how the call ended. You give it a system prompt, a voice, a model, and a set of tools; it works out the rest at call time.
The same agent answers on your website through the chat widget, so an agent you have already tested by phone can go on a web page without being rebuilt.
Use an agent when the conversation is open-ended and you cannot enumerate what the caller will say.
Workflows
A workflow is a graph you draw: check the time, look a record up, branch, send a message. It does exactly what you wired and nothing else.
Use a workflow when the path is known: a menu, a business-hours check, a notification, a lookup with two outcomes. A workflow costs no model call and adds no model latency, and it takes the same path every time, so what you tested is what runs.
The two compose. A workflow hands a call to an agent with the AI Voice Agent node and routes on the outcome; an agent runs a workflow as a tool when it needs a defined sequence of steps. See Using an Agent in a Workflow.
Knowledge bases
Anything the agent should quote (hours, fees, policies, product detail) belongs in a knowledge base rather than in the prompt. The agent searches it during a call and answers from what it finds.
The practical reason is maintenance: a fact in a knowledge base can be corrected without re-testing the prompt, and one knowledge base serves every agent that attaches it.
Tandy
Tandy is the assistant inside the product. Your callers never reach it. It can build a voice agent or a workflow, change an agent you already have, schedule a workflow, reroute a phone number, invite or remove a user from Manage Users, run a security review over one of your agents and offer to apply the fixes, and report a problem or request a feature on your behalf.
Tandy can also make any other change the MCP server offers, such as editing a workflow's steps or publishing a version.
Tandy knows which page you sent each message from. In the workflow editor, the voice agent editor, or a conversation in Insights → Call Logs, you can say "this workflow", "this agent", or "this call" and Tandy reads that one. It reads the last saved copy, so save your edits before you ask about them.
Tandy never makes a change on its own. It shows you a card describing what it understood, and nothing happens until you press the button on it. For the changes above, the card is a draft you can adjust. For any other change, the card is an approval: it names the action and every value Tandy will send, you cannot edit it, and Approve makes exactly that call. To change a value, decline the card and tell Tandy what to change.
Tandy keeps your chats on Tandem's servers, per user and per organization. They appear on every device you sign in on, and nobody else in your organization can see them. A chat is deleted 90 days after its last message. An attached file is kept for 30 days, then its chip shows Expired and Tandy is told the file is gone. Delete on a chat and Delete All remove chats immediately. When an administrator removes or disables you in Manage Users, your chats in that organization are deleted, along with your chats in any sub-tenant the disable removed you from. If you are removed some other way, they expire after 90 days. Personal access tokens cannot read Tandy chats.
After the conversation
Two features read finished conversations rather than running them:
- Post-call analysis reads a call's transcript once it ends and pulls out sentiment, an outcome, a summary, and any fields you define.
- Insights reports volume, call logs, and the paths conversations actually took.
Connect your own AI assistant
Everything above is AI running inside Tandem. This section is the other direction: giving the assistant you already write code with access to the Tandem documentation and to your own organization's configuration, so it can answer questions about Tandem without you pasting pages into it.
There are two ways in. The MCP server lets an assistant call Tandem and read your own data;
the llms.txt files are static and need no setup at all.
The Tandem MCP server
Tandem hosts a Model Context Protocol server at
https://mcp.tandemcx.ai, also reachable at https://mcp.spark.sharpencx.com. Both
addresses reach the same server. Any MCP client can connect to it with a personal
access token. The server then acts as you, in the organization the token belongs to, with
exactly the permissions your own account has.
Fifty-nine tools are available:
- Documentation —
search_docsandfetch_docsearch and read these pages. They are the only two that work without a token. - Your organization —
list_phone_numbers,list_sip_trunks,list_workflows,get_workflow,list_knowledge_bases,list_integrations,list_voice_agents,get_voice_agent,list_voice_agent_versions,list_agent_models,list_voice_options,get_me,list_users,list_roles,list_chat_widgets,get_chat_widget,list_chat_updates, andget_chat_update. - Conversations —
list_conversations,get_conversation, andget_conversation_transcriptread what Insights → Call Logs shows: which calls and chats happened, what each one did, and what was said. - Workflow authoring —
list_node_types,get_node_schema,list_node_field_options,find_nodes, andbuild_workflow_draft, which turns described steps into a node graph Tandem will accept.build_workflow_draftcreates nothing. It returns either the graph or every reason the steps cannot become one. - Agent tests —
list_agent_tests,get_agent_test, andget_agent_test_runread an agent's tests and their results.draft_agent_test_from_conversationdrafts a test from a past conversation and saves nothing. - Twenty-seven writes —
create_voice_agent,update_voice_agent,publish_voice_agent_version,create_workflow,update_workflow,update_workflow_nodes,create_workflow_version,publish_workflow_version,schedule_workflow,update_phone_number,update_sip_trunk,invite_user,delete_user,create_agent_test,update_agent_test,delete_agent_test,run_agent_tests,create_chat_widget,update_chat_widget,delete_chat_widget,create_chat_update,update_chat_update,publish_chat_update,unpublish_chat_update,delete_chat_update,create_integration, andupdate_integration. Every one is marked as destructive in the tool definition, so a client that reads that marking stops and asks you to approve the call before it runs.run_agent_testsspends model tokens.
create_integration and update_integration take no credential. An integration is created
with no authentication, and you add its password, API key, or AWS keys on its settings page
under Integrations. For the same reason, update_integration can't change a REST/SOAP
integration's base URL, or an MCP server integration's URL while it has a stored API key; change
those on the settings page too. A new MCP server integration has every tool disabled. When the server
can be reached, the create call returns the tools it offers, and update_integration with
enabledTools turns them on.
A write that takes a payload, such as an agent configuration, a workflow, or a test, reports a rejection per field, so a client can correct one value and retry rather than re-sending the whole request blind.
create_voice_agent takes an agent configuration in the same shape get_voice_agent
returns and saves it as version 0.1. A new agent is not live until a version is
published: call publish_voice_agent_version with that version, or publish it in the agent
editor. Build the configuration from the
catalog tools rather than from guesses: list_agent_models for the model, list_voice_options
for the voice, list_workflows and list_integrations for tools, list_knowledge_bases for
knowledge. If Tandem rejects the configuration, the error names each field and why, so the
call can be corrected and retried.
update_voice_agent changes settings on an agent that already exists. It takes a list of
{path, value} changes — safety.redactPii, limits.maxCallDurationMs, systemPrompt —
rather than a whole configuration, because it reads the agent's current settings and applies
your changes to those. That is what stops the settings you did not mention from being wiped,
and it is the only way to change one field of an agent whose variable values you cannot read.
Pass observed_updated_at (the updatedAt from your get_voice_agent read) and the call
refuses rather than overwriting a change someone else saved while you were working.
On an agent that has a published version, update_voice_agent saves your changes as a new
version and publishes it, so they are live when the call returns. On an agent with nothing
published it refuses with DRAFT_NOT_EDITABLE; publish a version with
publish_voice_agent_version first. The same tool rolls an agent back: pass an earlier
version from list_voice_agent_versions. Publishing checks the whole configuration, and a
refusal names each field and why.
create_chat_widget and update_chat_widget manage a chat widget.
A widget answers with exactly one agent or one workflow: the agent needs Chat among its
Channels, and the workflow needs a published
version. An empty allowedOrigins list
means the widget works on no site. update_chat_widget takes only the fields that change and
keeps the rest; null clears a field, and a list such as allowedOrigins is replaced whole.
Sending a new agentId or workflowId clears the other. delete_chat_widget stops the embed
snippet on your site working at once.
create_chat_update saves a post for the Updates tab as a
draft. Visitors see it only after publish_chat_update, and unpublish_chat_update takes it
back to a draft. The post body is a list of blocks (headings, paragraphs, quotes, lists, images,
and one button), not HTML or Markdown; create_chat_update lists the exact shapes. An empty
widgetIds list shows the post on every widget with the Updates tab turned on.
Both update tools take observed_updated_at (the updatedAt from get_chat_widget or
get_chat_update) and refuse with STALE_CONFIG rather than overwriting a change someone saved
while you were working. A field Tandem does not recognize, or one it sets itself such as a
post's state, is not saved, and the answer lists it under notApplied.
list_node_field_options lists the real values for a step setting backed by a dropdown: a
prompt file, a queue, a voice. Some of those settings depend on another setting beside them.
get_node_schema reports that as derivedBy, naming the setting whose value the lookup
needs, and a few name two. Play Message's Voice Engine is the one to know about. A voice
name is only unique within a language, so the engine list needs the language and the voice
together: pass both as a list, in the order the schema gives them. One value on its own comes
back empty, which reads as though your organization has no engines to choose from.
create_workflow commits a graph from build_workflow_draft. A new workflow is not
live: it has no published version until someone publishes it, with
publish_workflow_version or in the workflow editor, and an
unpublished workflow does not answer calls or run on a schedule. schedule_workflow refuses
an unpublished workflow for that reason, rather than arming a series that would fail every
occurrence.
get_workflow returns a workflow's settings and the steps of one version. The steps are a
separate record from the workflow itself, so the call reads both. It reports published as
its own field: an unpublished workflow does not answer calls or run on a schedule, however
complete its steps look. Omit version for the published one, or for the newest draft when
nothing is published.
The steps come back in full by default, which on a large workflow is a large answer. Two
options narrow it: include="outline" returns each step's name, type, and exits but not its
settings, which is what reading the shape of a flow needs; names returns only the steps you
name, in full. Find the step in the outline, then read that one. On a workflow of fewer than
about ten steps the full answer is already small enough that one call beats two.
update_workflow changes a workflow's name, description, or timezone, its steps, or both.
Settings merge, so a rename leaves the description and timezone alone. Steps go to one
version, and a published version cannot be updated — that is what live calls are running,
so change a published workflow through a new version (see below). Settings and steps
are separate records and are written in separate calls, so the answer lists what actually
landed rather than implying both did. Pass observed_updated_at — the workflow.updatedAt
from your get_workflow read — and the call refuses with STALE_WORKFLOW rather than
overwriting a change someone saved in the editor while you were working.
find_nodes answers questions about a workflow's steps without reading it whole: which step
plays a given prompt file, which steps transfer to a queue, what leads to a given step. The
outline above shows names, types and wiring; this searches the settings, including the nested
ones on a Switch evaluation or a Time Control event, and tells you which setting matched. Give
at least one of text, node_type, or exits_to; they combine with AND. Long values are
shortened, so a match tells you where to look rather than handing back the whole setting.
update_workflow_nodes changes individual steps. Prefer it over update_workflow for editing
a workflow that already exists: update_workflow replaces every step, so your tool has to read
the whole workflow and send it all back to change one setting, while this one merges on the
server and you send only what changes. You give it a list of operations, applied in order:
setFields and setExits on a named step, add, remove, and rename. Steps are identified
by name, so rename also repoints every exit aimed at the old name and tells you which steps it
touched. setFields merges, so changing a prompt leaves the language and voice beside it alone.
The whole workflow is checked before anything is written: if removing a step leaves another one
pointing at it, nothing is saved and you get the reason per step. A published version cannot be
changed, the same as update_workflow.
To change a published workflow, make a draft, edit it, and publish it:
create_workflow_versioncopies the published version (or the one you name infrom_version) into a new unpublished version. It is numbered one above the highest version the workflow holds, so1.3becomes1.4, and the answer tells you the number.update_workflow_nodesorupdate_workflowedits that draft. Pass the new number asversion. Callers keep getting the published version while you work.publish_workflow_versionmakes the draft the version live calls and schedules run. Tandem checks the whole workflow first and refuses a version with errors, naming each step and what is wrong with it.
Each new version counts toward your organization's version limit, so make one draft per round of edits rather than one per change.
list_conversations returns the Call Logs list, newest first, with the same filters: a start
time range, workflows, statuses, channels, and a search over conversation ids, workflow names,
and phone numbers. With no range it covers the last 30 days, and with only an end time it
covers the 30 days before it. A range longer than 90 days is rejected. get_conversation
returns one conversation's record, the log of what happened on it (the agent answering, a
workflow starting, a transfer), and each workflow run with the steps it took and the exit each
step chose. Conversations from before that log was recorded report
actionsAvailable: false. Their workflow runs are still returned.
get_conversation_transcript takes the same conversation id and works for both calls and
chats. Tool calls the agent made appear as turns, with their arguments and, when a call failed,
the reason. A transcript that was never stored or has expired comes back with
available: false and a reason rather than an error. Voice transcripts are kept for 90 days.
The transcript is the caller's own words and can include personal details, and your AI tool
receives it in full.
update_phone_number changes where a number routes or what it is called, and takes effect on
the next inbound call.
A SIP trunk number is not a phone number to these tools. list_phone_numbers leaves it out;
list_sip_trunks lists it under its trunk, and update_sip_trunk changes where it routes. A
trunk has a default route that every number without its own route uses, so for a trunk with one
number, route the trunk. To route one number differently, send the trunk's whole numbers list
with that one entry changed: the list replaces the stored one, so a number left out is removed.
That change also needs the trunk's updatedAt from list_sip_trunks, and it is refused if the
trunk changed after that read. update_sip_trunk changes routing only; a trunk's connection
settings, such as its credentials and allowed IPs, are changed in Tandem itself. A trunk managed by the Sharpen integration refuses a change to its numbers, though its default
route can still change.
get_voice_agent returns an agent's stored configuration, and it never returns the value of
an agent variable. You get the name, the length, and whether the value looks like a
credential, which is enough to review an agent for a pasted secret without copying that
secret into the conversation. Tandy's security
review works from the same reads.
It reads an agent you have not published yet. By default it returns the configuration the
agent answers calls with; if the agent is serving nothing — which every agent is between
being created and being published for the first time — it returns the newest version saved
instead and sets published to false. Read that field before you act on the answer: a
draft is not what callers hear, however finished it looks. list_voice_agent_versions lists
every version an agent holds and marks the one that is live, and passing a version to
get_voice_agent reads that one specifically.
Before you start
Access tokens are being rolled out. If Access Tokens isn't in your user menu, it isn't enabled for your organization yet — contact support.
You need a personal access token. Open the user menu (your avatar, top right), choose Access Tokens, and select Create Token. The token is shown once, so copy it before you leave that screen. Authentication covers expiration, revocation, and the per-organization limit.
Set it up
claude mcp add --transport http tandem \
https://mcp.tandemcx.ai \
--header "Authorization: Bearer $TANDEM_PAT"Add --scope user to make the connection available in every project rather than only
the current one. Run /mcp inside Claude Code to see the server's status, or
claude mcp list from a shell.
Most clients are configured from a JSON file of roughly this shape. Check your client's own documentation for which file it reads and what it names the keys.
{
"mcpServers": {
"tandem": {
"type": "http",
"url": "https://mcp.tandemcx.ai",
"headers": {
"Authorization": "Bearer spark_pat_..."
}
}
}
}The transport is streamable HTTP. There is no SSE endpoint, and a browser-based client cannot connect: the route answers no CORS preflight.
Check that it worked
This asks the server for its tool list, which needs no Tandem data and so fails only if the connection itself is wrong:
curl -sX POST https://mcp.tandemcx.ai \
-H "Authorization: Bearer $TANDEM_PAT" \
-H 'content-type: application/json' \
-H 'accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'A JSON-RPC response listing the tools means you are connected. Two failures are worth telling apart:
401 Unauthorized— the token is wrong, expired, or revoked. Create a new one.404 Not Found— usually the old endpoint. The server used to answer athttps://api.spark.sharpencx.com/v1/mcp, which has been retired; the address is nowhttps://mcp.tandemcx.ai, with no path after the host.
A revoked token can keep working for up to a minute, because the gateway caches its verification decision.
llms.txt and Markdown pages
Every page in this portal is also published as plain Markdown, so an assistant can read the documentation without an MCP connection.
| File | What it holds |
|---|---|
https://docs.tandemcx.ai/llms.txt | An index of every page: title, URL, and one-line description. |
https://docs.tandemcx.ai/llms-full.txt | The full text of every page, concatenated. |
https://docs.tandemcx.ai/sitemap.xml | Every page's canonical URL, for a crawler that wants the page list. |
<any docs URL>.md | That one page as Markdown, headed with its title and canonical URL. |
The last row is the one to reach for most often. Append .md to any page's URL and you get
its source, so this page is at
https://docs.tandemcx.ai/docs/overview/building-with-ai.md. Every page also carries
a Copy Markdown button, with View as Markdown, Open in ChatGPT, and Open in
Claude behind it.
Reach for llms.txt when your assistant needs to find the right page, and a single .md
when you already know which page that is. llms-full.txt is the whole corpus, which is large
enough that it is usually the wrong choice for a single question.
Tips
- Point at a specific page rather than the whole corpus. A single
.mdfile answers a question with far less context thanllms-full.txt. - Let it read your configuration before it proposes a change.
list_voice_agentsthenget_voice_agentgives an assistant the actual agent, so its suggestions apply to what you have rather than to a generic one. - Never paste a token into a prompt. Put it in an environment variable and let the client send the header, so the token stays out of your conversation history.
- Keep a token per tool. Revoking one then costs you that tool's connection and nothing else.