For the complete documentation index, see llms.txt. Any page on this site can be fetched as Markdown by appending .md to its URL.

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_docs and fetch_doc search 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, and get_chat_update.
  • Conversations — list_conversations, get_conversation, and get_conversation_transcript read 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, and build_workflow_draft, which turns described steps into a node graph Tandem will accept. build_workflow_draft creates nothing. It returns either the graph or every reason the steps cannot become one.
  • Agent tests — list_agent_tests, get_agent_test, and get_agent_test_run read an agent's tests and their results. draft_agent_test_from_conversation drafts 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, and update_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_tests spends 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:

  1. create_workflow_version copies the published version (or the one you name in from_version) into a new unpublished version. It is numbered one above the highest version the workflow holds, so 1.3 becomes 1.4, and the answer tells you the number.
  2. update_workflow_nodes or update_workflow edits that draft. Pass the new number as version. Callers keep getting the published version while you work.
  3. publish_workflow_version makes 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 at https://api.spark.sharpencx.com/v1/mcp, which has been retired; the address is now https://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.

FileWhat it holds
https://docs.tandemcx.ai/llms.txtAn index of every page: title, URL, and one-line description.
https://docs.tandemcx.ai/llms-full.txtThe full text of every page, concatenated.
https://docs.tandemcx.ai/sitemap.xmlEvery page's canonical URL, for a crawler that wants the page list.
<any docs URL>.mdThat 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 .md file answers a question with far less context than llms-full.txt.
  • Let it read your configuration before it proposes a change. list_voice_agents then get_voice_agent gives 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.

Next steps

On this page