> ## Documentation Index
> Fetch the complete documentation index at: https://docs.teamfollowup.ai/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Use the public API base URL: https://api.teamfollowup.ai/api.
> Authenticate public API requests with Authorization: Bearer YOUR_API_KEY.
> Use product-owned terms: agents, campaigns, projects (GoHighLevel sub-accounts), contacts, calls, outcomes, skills, cadences, phone numbers.
> Scope requests by locationId rather than projectName.
> Read the guide linked from each API reference group before recommending an endpoint.

# Validate TFU Live workflows

> TFU Live only. Check a complete workflows map with every check a save makes, including the runtime’s node settings and secret headers, without saving anything. Answers the tool name each workflow would have.

Required scope: `agents:write`.

Required API key scope: `agents:write`.



## OpenAPI

````yaml /openapi.yaml post /api/tfu-live-agents/{id}/workflows/validate
openapi: 3.1.0
info:
  title: TFUAI Server API
  version: 1.0.0
  description: >-
    API reference for the TFUAI SaaS server.


    All published endpoints are mounted under `/api`. Responses use a standard
    envelope: `{ "success": true, ... }` on success and `{ "success": false,
    "error": "..." }` on failure.


    Product API modules are being launched module by module. This spec currently
    exposes only contracted public modules.


    Authenticate public API requests with an API key in the `Authorization`
    header: `Authorization: Bearer YOUR_API_KEY`.
servers:
  - url: https://api.teamfollowup.ai
    description: Production API origin. Public API paths are under /api.
security: []
tags:
  - name: Agents
    description: >-
      Build the conversation: identity and settings, script, lead context and
      split testing.
  - name: Analytics
    description: >-
      Understand call volume, pickup, booking and transfer metrics within the
      selected reporting scope.
  - name: Billing
    description: >-
      Balances, spend, purchases and sub-account rebilling. Reference and guides
      only; the builder chatbot has no billing playbook.
  - name: Calls
    description: >-
      Review completed calls, recordings, transcripts and campaign run history;
      distinguish observation from placing a new call.
  - name: Campaigns
    description: >-
      Configure what starts calls, who is eligible, calling windows, caller IDs,
      activation and the actions that follow each outcome.


      What runs after a call lands: for each outcome (booked, opted out, asked
      for a callback…), an ordered chain of actions — tag the contact, move a
      pipeline stage, send an SMS, book an appointment. Stored as a graph:
      `nodes` are the actions, `edges` say what follows what, and
      `dispositionEntries` maps each outcome to the node its chain starts at.


      **Pick the narrowest endpoint that does the job.**


      | To | Call | |

      | --- | --- | --- |

      | See what fires per outcome | `GET .../workflow?view=summary` | Reads as
      plain text, no graph walking |

      | Add steps to an outcome | `POST .../outcomes/{outcome}/nodes` | Server
      derives node ids, edge ids, handles, layout |

      | Change a message, tag or setting | `PATCH .../workflow` | Merges;
      carries no graph, so it cannot damage one |

      | Remove one step | `DELETE .../nodes/{nodeId}` | Also re-links the chain
      around it |

      | Author or replace the whole graph | `PUT .../workflow` | Replaces
      everything you send |


      `PUT` is the only one that can re-wire, position nodes, build an `if`,
      save an unconfigured draft, or copy a whole workflow — and the only one
      that can overwrite a change someone else made after you read it. Use it
      when you are genuinely authoring the graph, and send `expectedVersion`
      when you do. For everything else the narrower endpoints are both easier
      and safer.
  - name: Contacts
    description: >-
      Find contacts and their calls, manage do-not-call status and identify
      internal contacts excluded from reporting.
  - name: In-call Capabilities
    description: >-
      Configure actions and instructions the agent uses during a live call:
      booking, transfers, lookups and custom skills.
  - name: Master agent
    description: >-
      Run a shared agent and campaign across linked projects; manage
      inheritance, local bindings, synchronization and rollout readiness.
  - name: Native Calendar
    description: >-
      The diary of a sub-account with no CRM: its bookable calendars, the free
      slots they offer, and the appointments on them. Availability is computed
      per request against live bookings and is never cached, because a cached
      slot is one somebody else has already taken.
  - name: Native Contacts
    description: >-
      The people a sub-account with no CRM calls, and the fields it keeps on
      them. This is the system of record for those contacts: there is nowhere
      else they exist. A sub-account with a CRM keeps its contacts there and
      every operation here answers 400 for it.
  - name: Phone Numbers
    description: >-
      Find and manage caller IDs, assign numbers to campaigns and understand
      connected Twilio numbers.
  - name: Projects
    description: >-
      Understand project ownership, inspect configuration and validate the local
      resources used by a campaign.
  - name: TFU Live
    description: >-
      Build one TFU Live agent with its Brain, Voice and equipped actions;
      connect it through shared Campaigns.
  - name: Voices
    description: >-
      Browse the voice catalogue and select a voice; configure speech and
      listening behavior in Agents.
paths:
  /api/tfu-live-agents/{id}/workflows/validate:
    post:
      tags:
        - TFU Live
      summary: Validate TFU Live workflows
      description: >-
        TFU Live only. Check a complete workflows map with every check a save
        makes, including the runtime’s node settings and secret headers, without
        saving anything. Answers the tool name each workflow would have.


        Required scope: `agents:write`.


        Required API key scope: `agents:write`.
      operationId: post-tfu-live-agents-by-id-workflows-validate
      parameters:
        - name: locationId
          in: query
          required: true
          schema:
            type: string
          description: >-
            Sub-account belonging to the authenticated agency and any narrower
            credential location binding.
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: TFU Live agent id. Never use an ordinary provider agent id.
      requestBody:
        description: The complete TFU Live authored agent or an explicit draft.
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TfuLiveWorkflowValidateRequest'
            examples:
              default:
                value:
                  workflows:
                    wf_area2k9x:
                      id: wf_area2k9x
                      name: Check service area
                      description: >-
                        Use when the caller wants to know whether we serve their
                        area.
                      steps:
                        - id: zip
                          type: collect_information
                          config:
                            items:
                              - id: zip_code
                                label: Zip code
                                question: What is the zip code of the address?
                                type: text
                                field: contact.postalCode
                        - id: coverage
                          type: check_zip_code
                          config:
                            field: contact.postalCode
                            codes:
                              - '10001'
                          outputs:
                            in_area:
                              - id: covered
                                type: steer_agent
                                config:
                                  guidance: Offer a consultation.
                            out_of_area:
                              - id: outside
                                type: steer_agent
                                config:
                                  guidance: Explain that this area is not covered.
      responses:
        '200':
          description: >-
            Successful TFU Live response. Setup acceptance is not verified
            readiness or campaign activation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TfuLiveWorkflowValidResponse'
              example:
                success: true
                data:
                  valid: true
                  tools:
                    - workflowId: wf_area2k9x
                      tool: check_service_area
        '400':
          description: >-
            TFU Live request rejected: { success: false, error, code,
            workflowId?, stepId?, field?, setupItems? }. Read the error and
            current agent before retrying.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TfuLiveError'
              example:
                success: false
                error: locationId is required.
                code: invalid_request
        '401':
          description: >-
            TFU Live request rejected: { success: false, error, code,
            workflowId?, stepId?, field?, setupItems? }. Read the error and
            current agent before retrying.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TfuLiveError'
              example:
                success: false
                error: Unauthorized
                message: Access token is required. Please login
        '403':
          description: >-
            TFU Live request rejected: { success: false, error, code,
            workflowId?, stepId?, field?, setupItems? }. Read the error and
            current agent before retrying.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TfuLiveError'
              example:
                success: false
                error: InsufficientScope
                message: This API key does not have the required scope.
                requiredScope: agents:write
        '404':
          description: >-
            TFU Live request rejected: { success: false, error, code,
            workflowId?, stepId?, field?, setupItems? }. Read the error and
            current agent before retrying.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TfuLiveError'
              example:
                success: false
                error: Agent not found.
                code: not_found
        '409':
          description: >-
            TFU Live request rejected: { success: false, error, code,
            workflowId?, stepId?, field?, setupItems? }. Read the error and
            current agent before retrying.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TfuLiveError'
              example:
                success: false
                error: The agent changed. Read the current version before saving.
                code: conflict
        '422':
          description: >-
            TFU Live request rejected: { success: false, error, code,
            workflowId?, stepId?, field?, setupItems? }. Read the error and
            current agent before retrying.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TfuLiveError'
              examples:
                invalid_workflow:
                  value:
                    success: false
                    error: Choose the tag this node checks.
                    code: invalid_workflow
                    workflowId: wf_account1
                    stepId: check
                workflow_resource_not_found:
                  value:
                    success: false
                    error: Choose an active calendar verified for this sub-account.
                    code: workflow_resource_not_found
                    workflowId: wf_book0001
                    stepId: slots
                    field: routing[1].calendarId
                workflow_secret_refused:
                  value:
                    success: false
                    error: >-
                      The saved value of the Authorization header was saved for
                      https://api.example.com; this webhook now sends to
                      https://collector.example.net, so enter the value again.
                    code: workflow_secret_refused
                    workflowId: wf_account1
                    stepId: lookup
                    field: headers.Authorization
                invalid_agent:
                  value:
                    success: false
                    error: >-
                      Choose an agent id using up to 100 letters, digits,
                      underscores or hyphens.
                    code: invalid_agent
                setup_incomplete:
                  value:
                    success: false
                    error: >-
                      Fill what this agent still needs before it is set up: Book
                      appointment: Choose the calendar to search.
                    code: setup_incomplete
                    workflowId: wf_book0001
                    stepId: slots
                    field: calendarId
                    setupItems:
                      - kind: workflow_setting
                        workflowId: wf_book0001
                        workflowName: Book appointment
                        stepId: slots
                        node: find_slots
                        field: calendarId
                        message: Choose the calendar to search.
        '429':
          description: >-
            TFU Live request rejected: { success: false, error, code,
            workflowId?, stepId?, field?, setupItems? }. Read the error and
            current agent before retrying.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TfuLiveError'
              example:
                success: false
                error: TooManyRequests
                message: Too many write requests, please slow down.
        '502':
          description: >-
            TFU Live request rejected: { success: false, error, code,
            workflowId?, stepId?, field?, setupItems? }. Read the error and
            current agent before retrying.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TfuLiveError'
              example:
                success: false
                error: TFU Live request failed.
                code: provider_unavailable
        '503':
          description: >-
            TFU Live request rejected: { success: false, error, code,
            workflowId?, stepId?, field?, setupItems? }. Read the error and
            current agent before retrying.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TfuLiveError'
              example:
                success: false
                error: TFU_LIVE_CAMPAIGN_PENDING
                code: TFU_LIVE_CAMPAIGN_PENDING
                message: >-
                  The agent draft was saved. Retry creation with the same agent
                  id and original body to finish attaching its campaign.
      security:
        - ApiKeyAuth: []
      x-codeSamples:
        - lang: bash
          label: cURL
          source: |-
            curl --request POST \
              --url https://api.teamfollowup.ai/api/tfu-live-agents/{id}/workflows/validate \
              --header 'Authorization: Bearer YOUR_API_KEY' \
              --header 'Content-Type: application/json' \
              --data '{
              "workflows": {
                "wf_area2k9x": {
                  "id": "wf_area2k9x",
                  "name": "Check service area",
                  "description": "Use when the caller wants to know whether we serve their area.",
                  "steps": [
                    {
                      "id": "zip",
                      "type": "collect_information",
                      "config": {
                        "items": [
                          {
                            "id": "zip_code",
                            "label": "Zip code",
                            "question": "What is the zip code of the address?",
                            "type": "text",
                            "field": "contact.postalCode"
                          }
                        ]
                      }
                    },
                    {
                      "id": "coverage",
                      "type": "check_zip_code",
                      "config": {
                        "field": "contact.postalCode",
                        "codes": [
                          "10001"
                        ]
                      },
                      "outputs": {
                        "in_area": [
                          {
                            "id": "covered",
                            "type": "steer_agent",
                            "config": {
                              "guidance": "Offer a consultation."
                            }
                          }
                        ],
                        "out_of_area": [
                          {
                            "id": "outside",
                            "type": "steer_agent",
                            "config": {
                              "guidance": "Explain that this area is not covered."
                            }
                          }
                        ]
                      }
                    }
                  ]
                }
              }
            }'
components:
  schemas:
    TfuLiveWorkflowValidateRequest:
      type: object
      required:
        - workflows
      additionalProperties: false
      properties:
        workflows:
          type: object
          maxProperties: 20
          additionalProperties:
            $ref: '#/components/schemas/TfuLiveWorkflow'
          description: >-
            The complete workflows map to check, as
            brain.capabilityConfig.workflows would hold it.
    TfuLiveWorkflowValidResponse:
      type: object
      required:
        - success
        - data
      properties:
        success:
          const: true
        data:
          type: object
          required:
            - valid
            - tools
          additionalProperties: false
          properties:
            valid:
              const: true
            tools:
              type: array
              items:
                type: object
                required:
                  - workflowId
                  - tool
                properties:
                  workflowId:
                    type: string
                  tool:
                    type: string
      additionalProperties: false
    TfuLiveError:
      type: object
      required:
        - success
        - error
      properties:
        success:
          const: false
        error:
          type: string
          description: >-
            What to do about it, in words. A status-wide gate (authentication,
            scope, rate limit) may use its own error name with a message.
        code:
          type: string
          enum:
            - invalid_workflow
            - setup_incomplete
            - workflow_resource_not_found
            - workflow_resource_unavailable
            - workflow_secret_refused
            - invalid_agent
            - invalid_request
            - unauthorized
            - insufficient_balance
            - forbidden
            - not_found
            - conflict
            - too_large
            - provider_unavailable
            - internal_error
            - AGENT_OPERATION_UNSUPPORTED
            - TFU_LIVE_CAMPAIGN_PENDING
          description: >-
            The kind of refusal: invalid_workflow (a workflow’s structure or a
            node’s settings), setup_incomplete (a setting still to fill or a
            Brain template placeholder still in the prompt, where the agent must
            be complete: preparing it, or a save that sets it up; setupItems
            lists every one), workflow_resource_not_found (a calendar, custom
            field, pipeline stage, team or rep the sub-account lacks, or an
            inactive calendar), workflow_resource_unavailable (503: the
            sub-account’s resources could not be read; retry),
            workflow_secret_refused (a secret header rule), invalid_agent (any
            other part of the agent), else by status. TFU_LIVE_* names a product
            state.
        workflowId:
          type: string
          description: >-
            The workflow a workflow refusal is about. Absent on a workflow being
            created, which has no id yet.
        stepId:
          type: string
          description: The step inside it.
        field:
          type: string
          description: >-
            The setting inside the step: calendarId, routing[1].calendarId,
            items[0].field, destination.teamId, headers.Authorization;
            brain.prompt for a placeholder. On a create from a Brain template,
            the request value: values.<to-fill id>, values or brainTemplateId.
        setupItems:
          type: array
          items:
            type: object
            required:
              - kind
              - message
            additionalProperties: false
            description: >-
              Something the agent still needs before it can be prepared for real
              calls.
            properties:
              kind:
                type: string
                enum:
                  - workflow_setting
                  - prompt_placeholder
                description: >-
                  workflow_setting: a setting still to fill (a calendar, a tag,
                  who takes a transfer, zip codes, a choice’s answers, an item’s
                  label); prompt_placeholder: a Brain template [bracketed]
                  placeholder still in the prompt (legacy: templates no longer
                  hold any).
              workflowId:
                type: string
              workflowName:
                type: string
              stepId:
                type: string
              node:
                type: string
                description: The node type.
              field:
                type: string
                description: >-
                  The setting inside the step (calendarId, destination,
                  items[0].choices), or brain.prompt.
              placeholder:
                type: string
              message:
                type: string
                description: What to do, in words.
          description: >-
            On setup_incomplete: everything the agent still needs, the first of
            which the refusal names.
      additionalProperties: {}
    TfuLiveWorkflow:
      type: object
      required:
        - id
        - name
        - description
        - steps
      additionalProperties: false
      properties:
        id:
          type: string
          pattern: ^wf_[a-z0-9]{8}$
          description: >-
            Stable for the workflow’s life, never derived from the name; the key
            it is stored under.
        name:
          type: string
          minLength: 1
          maxLength: 60
          description: >-
            Becomes the AI’s tool name: lowercased, anything but a-z and 0-9
            turned into _, collapsed, trimmed, at most 64, starting with a
            letter.
        description:
          type: string
          minLength: 1
          maxLength: 3000
          description: >-
            The trigger: when the AI should run it (the first 1024 characters
            reach the AI).
        steps:
          type: array
          maxItems: 60
          items:
            $ref: '#/components/schemas/TfuLiveWorkflowStep'
    TfuLiveWorkflowStep:
      type: object
      required:
        - id
        - type
        - config
      additionalProperties: false
      description: >-
        One node. A node with outputs ends its list: the steps after it go on
        its outputs.
      properties:
        id:
          type: string
          pattern: ^[a-z0-9_-]{1,40}$
          description: Unique in the workflow; receipts and resume use it.
        type:
          type: string
          enum:
            - collect_information
            - condition
            - check_tag
            - add_tag
            - remove_tag
            - set_field
            - move_stage
            - add_note
            - send_sms
            - send_email
            - send_webhook
            - notify_teammate
            - webhook_request
            - transfer
            - dnd
            - find_slots
            - book_appointment
            - cancel_appointment
            - schedule_ai_call
            - check_zip_code
            - cancel_scheduled
            - steer_agent
        label:
          type: string
          minLength: 1
          maxLength: 120
        config:
          type: object
          additionalProperties: {}
          description: >-
            The node’s settings: the catalog’s nodes[].settingsSchema for its
            type.
        outputs:
          type: object
          additionalProperties:
            type: array
            items:
              $ref: '#/components/schemas/TfuLiveWorkflowStep'
          description: >-
            Only on a node that has outputs: { <output>: [steps] }, the
            catalog’s nodes[].outputs (a Condition’s are its case ids, then
            otherwise).
  securitySchemes:
    ApiKeyAuth:
      type: http
      scheme: bearer
      bearerFormat: API key
      description: 'Send your API key as `Authorization: Bearer YOUR_API_KEY`.'

````