/api/tfu-live-agents module. Ordinary agents keep their existing creation body and
purpose types. Runtime is not a fourth outreach/inbound/appointment purpose.
Use the returned tfu_live:<profileId> public id on the TFU Live routes.
The server verifies the stored tenant-owned identity; an id prefix grants no
access. Runtime conversion and mixed ordinary/TFU authored bodies are
refused.
Saving an agent, finishing its setup and activating its campaign are separate
steps. A saved draft is not ready to handle calls until setup succeeds, and
preparing it does not turn its campaign on.
Agency owners can also create agents from the dashboard, using Build with AI
or Configure myself. The same sub-account ownership and resource checks apply
to manual edits and typed or spoken co-authoring. project_user access does not
permit creation. Provider administration remains restricted to staff.
Creation pricing
A successfully generated agent costs $0.50, whether created through MCP, TARS Home or Build with AI. Clarification questions, starter agents and saving your own instructions are free. Retrying the same build does not charge again. Voice-session minutes are billed separately at your normal rate. Subsequent AI edits and an explicit Sync workflows action retain their usual usage charges. Every new TFU Live rep is created with its linked, paused campaign. Going live remains a separate action. For AI creation, use modegenerate with the business information and, optionally,
agentType inbound_receptionist or outbound_sales. WHO, WHAT and HOW must be
known before generation proceeds. The response is building until polling the
creation request returns created or asks for missing information. Reuse the
request id when answering. Mode template creates a free instructional draft.
If omitted, the name is Tars and the voice is River (cedar). Explicit choices are preserved.
Discover the workspace with List projects
using projects:read. If none exists, create a native sub-account
with projects:write, then use its locationId. No external CRM is required.
Orb appearance
Orb appearance is cosmetic. Use Set TFU Live agent orb colour withstyle set to default, curated, preset or custom. default follows
the voice; curated derives a stable colour pair from the agent. custom takes
two #rrggbb values in colors, for the orb and its glow. preset takes one of
these identifiers in preset:
This endpoint does not require
ifVersion or rebuild the agent.
For the dashboard control, see Conversation Style.
Prerequisites
- An agency API key with
agents:readto inspect an agent oragents:writeto create, edit and prepare it. Write scope also covers reads. - A connected sub-account belonging to that key’s agency. A narrower credential
location binding still applies. Supplying another
locationIddoes not grant access. - For trial runs, use an authorised test environment and synthetic records. Check the actual database and external providers before using a local server; running locally does not make its actions simulated.
Authorization: Bearer YOUR_API_KEY, as described in
Authentication. The examples use placeholder IDs, not working
customer records. No staff credential or internal service key is required.
Step 1: Create an empty draft
Send this body to Create agent with?locationId=YOUR_LOCATION_ID:
id using letters, digits, underscores or hyphens, up to 100
characters. Keep it if you lose the response: read that ID before retrying.
Retry creation with the same id and original body to finish an interrupted
campaign attachment. The retry keeps an existing campaign and its settings.
Campaign and private cadence writes commit together, so a failed attachment
cannot leave a partial campaign or duplicate cadence. A saved provider draft
remains available for the same-id retry; creation is not reported as complete
until the campaign is confirmed. TFU_LIVE_CAMPAIGN_PENDING reports that recoverable partial creation.
TFU_LIVE_CAMPAIGN_REFUSED is different: the agent was saved, but its campaign
was refused, for example no access to the sub-account, a missing project, a
conflicting campaign binding or an invalid campaign. It keeps the refusal’s own
status (400, 403, 404, 409 or 422) and message. Retrying without
changing anything repeats the refusal, so fix what the message names first.
TFU Live does not use the ordinary agent’s Idempotency-Key replay contract.
An existing conflicting ID returns an error rather than creating another agent.
The response is 201 with data.agent and data.campaign. New agents, including
empty drafts, automatically receive a linked paused campaign and a private Power
Dialer cadence. Stop contact and its first Put on DND action are protected.
When the call ends holds the actions that run as the call ends; the agent says
goodbye and the call ends on its own. Action-enabled agents also receive Schedule a callback, unless a
workflow already schedules an AI call, its name is taken or the custom slots are full. data.campaign.id is the campaign identifier; profileId is its
internal TFU Live profile binding. Caller numbers and trigger tags start empty.
Choose them in Settings and the Brain trigger before going live.
Read the actual data.agent object for id, ifVersion, brain, voice and
setup. A draft has provisioningPolicy: "explicit". Creating an empty draft
sets nothing up. Each later save sets the saved version up (see Step 4). Nothing
here activates a campaign.
Alternatively, supply id, name, brain and voice together to create a
complete agent. Complete creation starts setup immediately. A successful create
still does not mean setup has finished.
Or start from a Brain template, a ready-made agent: send id, brainTemplateId
and the values it needs (a calendar, who takes transfers) to create an explicit
draft that already has its workflows and a starting prompt. List them with
List TFU Live Brain templates;
the workflows contract describes
each and what it needs.
Step 2: Discover only the capabilities you need
Read Discover TFU Live capabilities at/api/tfu-live-agents/tfu_live:example_agent/brain/catalog?locationId=YOUR_LOCATION_ID.
An agent’s abilities are workflows: each has a name (the AI’s tool name),
a description (when the AI should run it) and steps built from nodes. The
catalog’s data.workflows lists every node with its settings (in words and as
JSON Schema), outputs, timing and the arguments it takes, the templates to start
from (Live transfer, Book appointment, Cancel appointment, Human assistance, Stop
contact, Check tag, Add tag, Schedule a callback, Check service area) and which
of their settings must be filled before saving, the path rules and the limits.
data.subAccount says which CRM the sub-account uses: on the native CRM
(nativeCrm: true) there are no tags, so tag nodes are refused. The catalog is
not an agent’s saved configuration; read the agent separately for its current
state. Create a draft before requesting its catalog. The
workflows contract describes every shape.
data.capabilities is the short index of the tool groups that stay tools. Add
group=outreach to retrieve that group’s tool schema, configuration and business
questions. supported reports the connected runtime’s support, not business readiness.
The catalog also lists
voices and background rooms. Do not invent names or
infer what a voice sounds like from its name.
Resolve calendars, tags, custom fields, pipelines, reps and teams with
Look up TFU Live resources
(GET /api/tfu-live-agents/{id}/resources/{kind}). It reads the same lookups a
save verifies against, with this module’s scope and sub-account checks. An
unavailable lookup (status: "unavailable") is unknown, not an empty list.
Calendars come from the sub-account’s CRM and from the calendar systems linked
to it. A native sub-account lists its own native calendars (source: "native"):
its real calls book on them, and a roleplay books test data that never reaches
them. GoHighLevel calendars listed beside them (a native sub-account holding a
GoHighLevel token) are not bookable on its real calls, and going live refuses a
step that names one (calendar_not_on_crm). A GoHighLevel sub-account lists its
GoHighLevel calendars (source: "gohighlevel"), and a save there refuses a
native calendar. On either CRM, the lookup also lists the calendars of each
linked system the sub-account has connected: Cal.com (source: "calcom", ids
such as calcom:4711) and Calendly (source: "calendly", ids such as
calendly:<event type uuid>). Real calls book on them. Calendly books through its
API only on a paid Calendly plan. Both send each booking to the caller by email,
so a workflow that books on them needs a Collect information
step before Book appointment, with a required item that saves the caller’s
email to contact.email (the contact’s Email field). A save refuses a new or
changed workflow without one (invalid_workflow, with stepId pointing at the
Book appointment). The step reuses an email already on the contact without
asking.
sources reports each source on
its own: a source that could not be read is unavailable while the others are
still listed.
A workflow books on calendars from one system. A save refuses a new or changed
workflow whose Find slots and Cancel appointment calendars, on any of its
branches, come from more than one of native, GoHighLevel, Cal.com and Calendly
(invalid_workflow, with stepId and field pointing at the first calendar on
a second system). A GoHighLevel calendar on a native sub-account is left out of
that count, since going live already refuses it, so it can be replaced one at a
time. Each workflow may use a different system.
Read data.agent.authoring.editable before editing an existing agent. A false
value with legacy_voice_contract, unrepresented_capability or
outdated_format in authoring.reasons means the API cannot represent that agent
losslessly; outdated_format is an agent saved before the workflow model. Edits and preparation return 409 until an explicit
migration is completed. Reading it does not change existing behaviour.
Step 3: Translate the business request into a reviewed edit
For “cover postal code 10001, explain the boundary elsewhere, and speak calmly”, send the complete authored object below to Update agent with?locationId=YOUR_LOCATION_ID:
ifVersion with the value from your last read. This is a complete
TFU Live authoring replacement, unlike the ordinary agent’s partial config
patch. To change one workflow without sending the rest, use
Replace TFU Live workflow
and its neighbours, which run the same checks. Preserve unrelated prompt text, capability configuration and existing
workflows. The response returns the next ifVersion. A workflow id is wf_
followed by 8 lowercase letters or digits; keep an existing workflow’s id when you
change it, so its tool keeps its history.
Saving your own prompt is free and invokes no AI. Existing workflows stay as
authored. Use Sync workflows in the Instructions view, or call
Sync TFU Live workflows with
the loaded ifVersion, to explicitly request the AI comparison. It can adjust
existing workflow triggers, required information and eligibility; it cannot add
new bound actions or destinations, and it never rewrites your prompt. This
separate action uses wallet credit.
A sync asked for while the saved version is being prepared is accepted, and
starts once that preparation finishes.
Poll Get TFU Live agent for workflowSync.status. not_requested means the
prompt was saved without synchronization; pending or running means the
requested work is underway; synced means it completed.
When the check finds that your change asks for something the agent cannot do
yet, such as an action or destination that is not set up, the change comes back
out of the prompt, and Saved changes marks each edit taken out Not
added. If the change was in use, the script returns to the last checked
version in a new revision, set up straight away on a live agent when nothing is
missing for setup: workflowSync reads synced, with pulledHash naming the
version taken out and reason saying why. If the change was still a draft, the
draft is removed and the version in use stays: workflowSync reads
needs_attention with one of these reasons: unsupported_action,
unsupported_execution, unsupported_workflow or unsupported_binding.
Any other needs_attention reason means the check did not finish or was not
sure. Your change stays saved and in use, and the next sync compares only what
you change after it. A refused or unfinished sync never stops a save, a
practice or a setup. A practice waits a moment for a check that is already
running, so it starts on the matching workflows.
Read the latest ifVersion after sync, because a workflow change or a change
taken out creates another revision. Retrying the same completed request does
not start another paid check. Tenant, data-shape and version checks still
apply.
Brain text may remain incomplete while an explicit draft is being built. Voice
instructions must remain non-empty. Fields outside this authoring contract,
including model selection, runtime changes and campaign activation, are refused.
For appointments, build a workflow from the Book appointment template. Find
slots offers times from one calendar, or from ordered routing rules over collected
answers: the first matching rule’s calendar is used, an unknown answer is asked
for, and there is no default calendar, so add a last rule with is_set for a
catch-all. When one choice item decides, every one of its answers must choose a
calendar. Every calendar must be active in this sub-account. The calendar’s CRM
sub-account supplies its timezone at runtime; do not ask for an appointment
timezone. Collect information gathers the answers during the call, before
availability and booking; post-call extraction is too late for this decision.
Step 4: Prepare the exact saved version
New instructional starters and Brain templates request test preparation automatically. Every save through this API then sets the saved version up as well: an edit, a restore, and adding, changing or deleting a workflow. A draft that still has setup items is set up for test calls only. A complete one is set up for real calls and stops being a draft. The builder also prepares saved changes and an older draft when a test starts, without a separate setup button. Incomplete settings keep an agent test-only; its campaign stays paused. Read the agent’s setup status before using it. You need Prepare only to retry a save whosesetupError says setup was not
started. Send this body to
Prepare TFU Live agent at
/api/tfu-live-agents/tfu_live:example_agent/prepare?locationId=YOUR_LOCATION_ID:
ifVersion, not authored fields. Preparation validates the saved
configuration and starts setup for that exact version. Read the agent again to
observe progress.
A 202 response means the request was accepted. setupStarted reports whether
setup was requested; setupError reports an edit saved without confirmed setup.
setup.status can be draft, queued, provisioning, ready,
provisioning_blocked or provisioning_uncertain. Inspect failureCode on a
blocked or uncertain revision before retrying. setup.revision and
setup.verifiedAt describe verified setup, not campaign activation.
An edit can be pending while the prior verified version remains in service.
Always use the newly returned ifVersion and the current setup.status; an old
verification timestamp does not make a pending edit ready.
To go back to an earlier save, find its revision number in
GET /api/tfu-live-agents/{id}/history, which lists the saves newest first.
Send it with ifVersion to POST /api/tfu-live-agents/{id}/restore. The authored text of that revision
comes back as a new revision, through the same save and setup as an edit.
If that save was still awaiting workflow alignment, alignment resumes before
the restored version is prepared for a new call.
History is never rewritten, and the last fifty saves can be restored.
Boundaries
- Creating a TFU Live agent also creates its linked, paused campaign. Saving or preparing it does not activate that campaign. Campaign permissions and activation checks remain separate. Campaign settings stay shared; current customer TFU campaign mutation adapters remain incomplete. Ordinary outcome prompts and workflow graphs are intentionally not applicable, not aliases for Brain text.
- TFU Live create/get/update own complete authored revisions. Use
GET /api/tfu-live-agents?locationId=...for its list andGET /api/tfu-live-agents/{id}/operationsfor current runtime support. A split test is run from the dashboard; the API does not offer it for a TFU Live agent yet (Agent split tests). - The agent’s abilities are
brain.capabilityConfig.workflows, part of the complete authored aggregate. Each workflow is a tool for the AI: its name gives the tool name, its description is when to run it, and renaming it renames the tool. There are no special workflows; every node can be used in any workflow. Collect information is the only way the AI supplies inputs: its items become the tool’s arguments. A node with outputs ends its list: the steps after it go on its outputs. Tags are never chosen by the AI: Check tag, Add tag and Remove tag each hold one tag. A Transfer node has the outputs Connected, Nobody available and Not connected; a call makes one transfer attempt, a fallback transfer goes only on Nobody available, and after Connected only actions and Conditions may follow. Put on DND ends the lead’s journey, so only internal steps may follow it, in one list (Add tag, Remove tag, Set field, Move stage, Add note, Notify teammate, Send webhook); nothing that contacts the lead or needs the call, and no Condition. The catalog’sworkflowscontract lists the node formats and limits. Saving validates the workflows with the runtime validator but executes nothing, and refuses an invalid workflow with422, naming the workflow and step. Custom fields, pipelines and calendars a new or changed step names must exist in this sub-account. HTTPS webhooks also pass outbound URL policy. A webhook sends the call’s context (workflow,sentAt,ids,contact,call) plus itsfields, each from fixed text, a contact field, a call detail or a collected answer. Authentication headers must be secret: send{"secret":{"value":"…"}}once, and the value is stored encrypted and read back only as{"secret":{"id":"…"}}, which you send back unchanged to keep it. The formats workflows replaced (brain.hooks,followUps,outcomeSteering,customTools,appointments, per-tool settings such astransfer_callorcheck_area_code, and workflows keyed by tool id) are refused until the agent’s migration has run. - A Notify teammate node sending to Slack uses symbolic
mainoralertschannels, resolved by the runtime from the call tenant.authoring.bindingWarningsdistinguishes an unverified mapping from a ready integration. - Transfer destinations and team-channel mappings require existing authorised configuration. A Transfer node names its destination; it does not create one.
- Split tests, master setup and provider-specific tuning are not part of this contract. Voice delivery instructions do not implement numeric provider controls.
- The internal conversational builder retrieves focused authoring guidance and capability details through its own authenticated tools. Customer backends can use this guide and the discovery requests above; this API does not expose the internal builder conversation, voice session or arbitrary tool executor.
Shared campaign controls, separate authoring
Caller-number ownership, calling windows, timezones and concurrency use shared campaign services and checks. Follow-up timing uses the campaign-bound cadence. TFU Live still has its own verified profile and call lifecycle. Workflows run when the AI calls their tool, during the conversation. They are not the ordinary campaign outcome workflow graph. Some action implementations are shared, but this API does not accept that graph’s schema or claim its endpoint compatibility. Campaign association requires the TFU Live profile and the campaign binding to be visible to the runtime that validates them. A local setup with separate SaaS and voice databases does not prove this association works end to end. This change does not switch databases or migrate records.Handle errors
TFU Live errors use the standard envelope,
{ "success": false, "error": "<code>", "message": "…", "code": "<code>" },
and a workflow refusal also names workflowId, stepId and field. Branch on
error (code holds the same value), never on message, which is for people
and can change: the codes are listed in the
workflows contract. Missing-scope
responses also identify requiredScope.
In the API
Share a client test
Withagents:write, issue or retrieve links using
POST /api/tfu-live-agents/tfu_live:<profileId>/test-share-links?locationId=<locationId>
and { "campaignId": "<campaignId>", "scope": "campaign" }.
The campaign must belong to the same sub-account and run this TFU Live profile.
This request only returns links; it does not call anyone or change the campaign.
For password-protected coaching, use agent scope and add
"roleplay": { "password": "a-separate-strong-password" } to that same request.
The web page then offers Test the agent and Role-play & improve. Testing
cannot change the agent. Unlocking role-play allows spoken feedback to update
this agent’s instructions through the same editor as Build with AI; lead actions
remain simulated. It does not grant account setup or campaign activation access.
Anyone holding both the link and password can edit, including instructions used
by later live calls. Give the password separately from the URL.
Use 12–256 characters. The password is stored as a salted hash and is never
returned. Omitting roleplay preserves the setting. Send "roleplay": false to
disable coaching; setting another password invalidates previous unlocks. Link
rotation or revocation also ends access. data.roleplay.enabled and
data.roleplay.passwordRequired are true when enabled. No dashboard control is
needed to issue this optional access through MCP.
For end-to-end campaign testing, share data.links.phone or data.links.web.
Both run the real campaign with real effects for the contact made from the name
and phone the tester enters. The phone link calls that number. The web link opens
the same call in the tester’s browser, so it needs no calling line or caller ID.
Like the phone test run, it is charged nothing. Like every test run, it stays
out of the performance figures.
The web link finds the contact by the phone number the tester enters, as the
phone link does:
- A number no contact holds yet gets a new contact. In GoHighLevel that contact
carries the source
TFU AI Test Link, and a campaign that starts on a new contact skips it, so a number typed into a test page is not dialled for real. - A number that already belongs to a contact uses that contact, and it keeps its own name, because anyone with the link can type any number. Answers to Personalise the call are saved on the contact.
- A stop on the number never refuses the test. Before the run, the test lifts every stop that would block it: the do-not-call list, the kill switch, and Do Not Disturb for calls and texts. A stop the person asked for is lifted too, and the list keeps a record of who lifted it.
- The upcoming appointments that earlier test calls booked are cancelled, without the calendar’s notifications. Appointments a person or a real lead booked stay.
- A channel that GoHighLevel holds as permanent Do Not Disturb stays on. The person set it, for example by texting STOP, and only they can lift it, by texting START. If the call channel is held that way, the run cannot start.
- A booking made by a test call starts the calendar’s confirmation campaign, and
any campaign that starts on
tfu_ai_booked, as a test run, not as a real lead. - The billing checks run before the contact is found or made, so a run refused for billing leaves nothing in the CRM.
The response carries
data.web.available: false with one blocker code when the
web link starts nothing:
For a conversation demo with simulated actions, choose
scope: "agent" and
share either the phone or web link.
Links expire 30 days after issuance. The default reuses valid links. Set
rotate: true to invalidate previous links and their sessions. To revoke,
use DELETE /api/tfu-live-agents/tfu_live:<profileId>/test-share-links/<scope>?locationId=<locationId>&campaignId=<campaignId>.
The returned URL contains a secret grant fragment; share it only with intended
testers and keep it out of ordinary logs. Test readiness is evaluated when the
link is opened; issuance does not activate a paused campaign.
