> ## 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.

# Update contact

> Update calling controls on one contact. Send only the fields to change: doNotCall stops or resumes AI calling through the connected CRM; isInternal marks or clears your own test contact. Other fields are unchanged. CRM profile fields such as name and email are managed in your CRM. locationId and optional idType belong in the body. Requires contacts:write and an agency administrator or project user role.

The two controls save to different services. If a save is interrupted, a 502 CONTACT_UPDATE_INCOMPLETE response includes actionResults with saved and unconfirmed fields. Do not replay the entire request blindly.

Required API key scope: `contacts:write`.



## OpenAPI

````yaml /openapi.yaml patch /api/contacts/{contactId}
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/contacts/{contactId}:
    patch:
      tags:
        - Contacts
      summary: Update contact
      description: >-
        Update calling controls on one contact. Send only the fields to change:
        doNotCall stops or resumes AI calling through the connected CRM;
        isInternal marks or clears your own test contact. Other fields are
        unchanged. CRM profile fields such as name and email are managed in your
        CRM. locationId and optional idType belong in the body. Requires
        contacts:write and an agency administrator or project user role.


        The two controls save to different services. If a save is interrupted, a
        502 CONTACT_UPDATE_INCOMPLETE response includes actionResults with saved
        and unconfirmed fields. Do not replay the entire request blindly.


        Required API key scope: `contacts:write`.
      operationId: update-contact
      parameters:
        - name: contactId
          in: path
          required: true
          schema:
            type: string
            minLength: 1
          description: >-
            The contactId returned by List contacts. For contacts without one,
            use an encoded international phone number and idType=phone. This is
            not the list row id.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ContactUpdateRequest'
            example:
              locationId: loc_123
              isInternal: true
      responses:
        '200':
          description: Only requested flags and their action results are returned.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContactUpdateResponse'
              example:
                success: true
                data:
                  contactId: contact_123
                  isInternal: true
                actionResults:
                  isInternal: saved
        '400':
          description: Invalid parameters or unsupported fields.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContactsErrorResponse'
              example:
                success: false
                error: VALIDATION
                code: VALIDATION
                message: locationId is required.
        '401':
          description: Authentication required.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContactsErrorResponse'
              example:
                success: false
                error: UNAUTHORIZED
                code: UNAUTHORIZED
                message: Unauthorized
        '403':
          description: Required role, API scope, or project access is missing.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContactsErrorResponse'
              example:
                success: false
                error: FORBIDDEN
                code: FORBIDDEN
                message: Access denied to this project
        '404':
          description: Contact not found in the selected project.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContactsErrorResponse'
              example:
                success: false
                error: NOT_FOUND
                code: NOT_FOUND
                message: Contact not found in the selected project.
        '409':
          description: A phone number matches multiple contacts. Use a contact ID.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContactsErrorResponse'
              example:
                success: false
                error: AMBIGUOUS_CONTACT
                code: AMBIGUOUS_CONTACT
                message: A phone number matches multiple contacts. Use a contact ID.
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          description: The contact could not be read or updated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContactsErrorResponse'
              example:
                success: false
                error: INTERNAL_ERROR
                code: INTERNAL_ERROR
                message: The contact could not be read.
        '502':
          description: A change could not be confirmed; already-saved changes remain saved.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    const: false
                  error:
                    const: CONTACT_UPDATE_INCOMPLETE
                  message:
                    type: string
                  actionResults:
                    $ref: '#/components/schemas/ContactActionResults'
              example:
                success: false
                error: CONTACT_UPDATE_INCOMPLETE
                message: >-
                  The do-not-call flag was saved; the internal mark could not be
                  confirmed.
                actionResults:
                  doNotCall: saved
                  isInternal: unconfirmed
      security:
        - ApiKeyAuth: []
      x-codeSamples:
        - lang: bash
          label: cURL
          source: |-
            curl --request PATCH \
              --url https://api.teamfollowup.ai/api/contacts/{contactId} \
              --header 'Authorization: Bearer YOUR_API_KEY' \
              --header 'Content-Type: application/json' \
              --data '{
              "locationId": "loc_123",
              "isInternal": true
            }'
components:
  schemas:
    ContactUpdateRequest:
      type: object
      properties:
        locationId:
          type: string
          minLength: 1
        idType:
          type: string
          enum:
            - contact
            - phone
        doNotCall:
          type: boolean
        isInternal:
          type: boolean
      required:
        - locationId
      additionalProperties: false
      anyOf:
        - required:
            - doNotCall
        - required:
            - isInternal
    ContactUpdateResponse:
      type: object
      required:
        - success
        - data
        - actionResults
      properties:
        success:
          const: true
        data:
          type: object
          properties:
            locationId:
              type: string
            contactId:
              type: string
            phoneNumber:
              type:
                - string
                - 'null'
            projectName:
              type:
                - string
                - 'null'
            doNotCall:
              type: boolean
            isInternal:
              type: boolean
            tags:
              type: array
              items:
                type: string
        actionResults:
          $ref: '#/components/schemas/ContactActionResults'
    ContactsErrorResponse:
      type: object
      additionalProperties: {}
      required:
        - success
        - error
      properties:
        success:
          type: boolean
        error:
          type: string
          example: Access denied to this project
        code:
          type: string
          example: FORBIDDEN
        message:
          type: string
          example: Access denied to this project
    ContactActionResults:
      type: object
      additionalProperties: false
      properties:
        isInternal:
          type: string
          enum:
            - saved
            - unconfirmed
        doNotCall:
          type: string
          enum:
            - saved
            - unconfirmed
    ApiError:
      type: object
      description: Standard error envelope returned by the API.
      properties:
        success:
          type: boolean
          example: false
        error:
          type: string
          description: Short machine-readable error code.
          example: Unauthorized
        message:
          type: string
          description: Human-readable explanation of the error.
          example: Authentication required.
      required:
        - success
        - error
  responses:
    RateLimited:
      description: Rate limit exceeded.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
  securitySchemes:
    ApiKeyAuth:
      type: http
      scheme: bearer
      bearerFormat: API key
      description: 'Send your API key as `Authorization: Bearer YOUR_API_KEY`.'

````