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

# Update an agent

> Accepts **PUT** or **PATCH** (identical partial-merge semantics). Partial-patches an agent — only the fields you send change. `autonomy_type` and `entity_type` are immutable. Send updated `ticket_conditions` / `time_entry_conditions` to change the entity filter; the server rebuilds the `FIND_ENTITIES` action. `actions` is the pipeline AFTER `FIND_ENTITIES` (CHAINED_ACTIONS only) — patching `actions` on an AGENTIC agent or conditions on a CONVERSATIONAL agent returns 400. An `actions` patch MERGES per-action: each entry targets one existing action by `id` (or unique `action_type`) and its `input_as_dict` is merged key-by-key, so changing one setting preserves the rest; unreferenced actions are kept and unmatched entries are appended. There is no wholesale-replace mode — a PUT can never drop or reorder an action (do structural pipeline edits in the dashboard). A `toolbox` patch also MERGES per tool: each entry under `tool_settings_by_type` is merged key-by-key onto that tool's current settings (lists like `allowed_users` replace; `null` for a tool key disables it), so changing one tool's setting no longer requires resending the whole toolbox. `agent_settings`, `integrations`, `schedule`, and `callback_settings` MERGE the same way: send only the fields you're changing — e.g. `{"agent_settings": {"custom_instructions": "…"}}` edits only the instructions and leaves `test_mode`/`model`/`til_channel` intact, sending one integration's permissions leaves the others connected, and `{"schedule": {"timezone": "…"}}` leaves the run cadence and business-hours windows intact. Refuses Neo-managed agents (403 `agent_managed_by_neo`). Editing `actions`, `agent_settings`, `toolbox`, `integrations`, or the conditions is a destructive change (triggers a TIL approval when an agent makes the call via `NEO_API_REQUEST`). 409 `agent_update_conflict` on a concurrent edit.



## OpenAPI

````yaml https://api.neoagent.io/public-api/openapi.json patch /public-api/agents/{agent_id}
openapi: 3.1.0
info:
  description: >-
    Neo's public contract for the dashboard ChatAgent, partner integrations, and
    MSP automation. Every response is wrapped in a `{data, meta}` envelope;
    errors use `{error: {code, message, details?}, meta: {request_id}}`.
    Authenticate with a `Bearer neo_sk_<env>_<secret>` API key (service account)
    or a Microsoft Entra ID JWT (dashboard user). Signed-URL endpoints (end-user
    feedback links) take a `signature` query parameter instead.
  title: Neo Public API
  version: 1.0.0
servers:
  - url: https://api.neoagent.io
security: []
tags:
  - description: Service metadata — health, OpenAPI.
    name: Meta
  - description: Agents and workflows — read, version history, delete, stats.
    name: Agents
  - description: Agent/workflow execution history, sub-resources, retry/cancel.
    name: Executions
  - description: PSA webhook events and their workflow-match results.
    name: Callbacks
  - description: Technician-in-the-loop approval requests.
    name: TIL requests
  - description: RMM script executions triggered by agents.
    name: RMM scripts
  - description: Dispatch-agent field-update decisions.
    name: Dispatch
  - description: The authenticated tenant.
    name: Tenant
  - description: Agent-builder schema catalogs (raw JSON payloads).
    name: Schemas
  - description: Escalate to the Neo team (HubSpot ticket).
    name: Escalation
  - description: Tenant settings.
    name: Settings
  - description: Tenant API-key management (dashboard JWT only).
    name: API keys
  - description: End-user feedback links (signed-URL auth).
    name: Feedback
  - description: End-client companies (CRUD + bulk-update).
    name: End companies
  - description: Channels — bind a CONVERSATIONAL agent to a transport (Teams).
    name: Channels
  - description: PSA/RMM/M365 integration status and connection management.
    name: Integrations
  - description: Technician roster (controls TIL routing and paging).
    name: Technicians
  - description: Future runs queued for TRIGGERED agents.
    name: Scheduled work
  - description: Subscription state and customer-facing credit usage (no provider $).
    name: Billing
  - description: Inbox messages and announcements.
    name: Inbox & Comms
  - description: Tenant-authored agent skills (CRUD) and the built-in skill catalog.
    name: Skills
paths:
  /public-api/agents/{agent_id}:
    patch:
      tags:
        - Agents
      summary: Update an agent
      description: >-
        Accepts **PUT** or **PATCH** (identical partial-merge semantics).
        Partial-patches an agent — only the fields you send change.
        `autonomy_type` and `entity_type` are immutable. Send updated
        `ticket_conditions` / `time_entry_conditions` to change the entity
        filter; the server rebuilds the `FIND_ENTITIES` action. `actions` is the
        pipeline AFTER `FIND_ENTITIES` (CHAINED_ACTIONS only) — patching
        `actions` on an AGENTIC agent or conditions on a CONVERSATIONAL agent
        returns 400. An `actions` patch MERGES per-action: each entry targets
        one existing action by `id` (or unique `action_type`) and its
        `input_as_dict` is merged key-by-key, so changing one setting preserves
        the rest; unreferenced actions are kept and unmatched entries are
        appended. There is no wholesale-replace mode — a PUT can never drop or
        reorder an action (do structural pipeline edits in the dashboard). A
        `toolbox` patch also MERGES per tool: each entry under
        `tool_settings_by_type` is merged key-by-key onto that tool's current
        settings (lists like `allowed_users` replace; `null` for a tool key
        disables it), so changing one tool's setting no longer requires
        resending the whole toolbox. `agent_settings`, `integrations`,
        `schedule`, and `callback_settings` MERGE the same way: send only the
        fields you're changing — e.g. `{"agent_settings":
        {"custom_instructions": "…"}}` edits only the instructions and leaves
        `test_mode`/`model`/`til_channel` intact, sending one integration's
        permissions leaves the others connected, and `{"schedule": {"timezone":
        "…"}}` leaves the run cadence and business-hours windows intact. Refuses
        Neo-managed agents (403 `agent_managed_by_neo`). Editing `actions`,
        `agent_settings`, `toolbox`, `integrations`, or the conditions is a
        destructive change (triggers a TIL approval when an agent makes the call
        via `NEO_API_REQUEST`). 409 `agent_update_conflict` on a concurrent
        edit.
      operationId: public_api.agents_update_patch
      parameters:
        - in: path
          name: agent_id
          required: true
          schema:
            type: string
      requestBody:
        content:
          application/json:
            examples:
              cosmetic_patch:
                summary: >-
                  Rename — cosmetic patch, no destructive change, no TIL
                  approval
                value:
                  name: Triage agent (v2)
              disable_agent:
                summary: Disable an agent — flip state to DISABLED
                value:
                  state: DISABLED
              merge_one_action_setting:
                summary: >-
                  CHAINED_ACTIONS — merge one setting onto an existing action
                  (preserves the rest)
                value:
                  actions:
                    - id: 0f8d6e2a-1c4b-4a9e-9b7a-2e5f1d3c6b80
                      input_as_dict:
                        dispatch_calendar_settings:
                          scheduled_entry_settings:
                            allow_update_existing_tentative_scheduled_entries: false
              merge_one_toolbox_tool:
                summary: >-
                  AGENTIC — merge one tool's settings (other tools preserved,
                  TIL-approved)
                value:
                  toolbox:
                    tool_settings_by_type:
                      SEND_TEAMS_MESSAGE_TO_INTERNAL_TEAM:
                        allowed_users:
                          - id: 11111111-1111-1111-1111-111111111111
                            name: Alex Doe
                        limit_to_allowed_users: true
              patch_ticket_conditions:
                summary: >-
                  Patch the entity filter — server rebuilds FIND_ENTITIES
                  (TIL-approved)
                value:
                  ticket_conditions:
                    conditions:
                      combinator: and
                      rules:
                        - field: priority
                          operator: in
                          value:
                            - high
                            - urgent
                    custom_field_conditions: null
                    workload_conditions: null
            schema:
              $ref: '#/components/schemas/UpdateAgentRequest'
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  data:
                    $ref: '#/components/schemas/Agent'
                  meta:
                    $ref: '#/components/schemas/SuccessMeta'
                required:
                  - data
                  - meta
                type: object
          description: Success.
        '400':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
          description: Bad request — malformed input.
        '401':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
          description: Unauthenticated — missing or invalid credentials.
        '403':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
          description: Forbidden — authenticated but not allowed.
        '404':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
          description: Not found.
        '409':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
          description: Conflict — the resource is in a state that blocks this operation.
        '422':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
          description: Request validation failed.
        '429':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
          description: Rate limited — see Retry-After.
        '500':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
          description: Internal server error.
      security:
        - bearerAuth: []
components:
  schemas:
    UpdateAgentRequest:
      description: >-
        `PUT /public-api/agents/<id>` body — a partial patch. Only the fields
        you send are

        changed; everything else keeps its current value. `autonomy_type` and
        `entity_type` are

        immutable. Editing `actions`, `agent_settings`, `toolbox`,
        `integrations`, or the

        conditions is a destructive change and triggers a TIL approval when an
        agent makes the

        call via `NEO_API_REQUEST`.


        The intent-shape applies on update too: send updated `ticket_conditions`
        /

        `time_entry_conditions` to change the entity filter; the server rebuilds
        the

        `FIND_ENTITIES` action. `actions` is the pipeline AFTER `FIND_ENTITIES`
        (CHAINED_ACTIONS

        only) — do not include `FIND_ENTITIES` yourself.


        `actions` MERGES (ENG-5712): each entry targets one existing action by
        `id` (preferred —

        read it from `GET /agents/<id>`) or, when `id` is omitted, by
        `action_type` if exactly one

        existing action has that type. The targeted action keeps its other
        config — its

        `input_as_dict` is merged key-by-key with what you send (nested objects
        merged, an explicit

        `null` deletes a key, scalars/lists replace), so changing one dispatch
        setting no longer

        wipes the technician pool, instructions, or calendar settings you didn't
        resend. Entries

        that match nothing are appended as new actions; existing actions you
        don't mention are left

        untouched. There is deliberately no wholesale-replace mode: a PUT can
        never drop or reorder

        an action — do structural pipeline edits (remove/reorder) in the
        dashboard.


        `toolbox`, `agent_settings`, `integrations`, `schedule`, and
        `callback_settings` all MERGE the

        same way (RFC 7396) — send only what you're changing, and everything you
        omit keeps its current

        value:

        - `toolbox` merges per tool under `tool_settings_by_type` (nested
        objects merge; scalars and
          lists — e.g. `allowed_users` — replace wholesale; a `null` tool key disables that tool).
        - `agent_settings` merges per field, so `{"agent_settings":
        {"custom_instructions": "…"}}`
          changes only the instructions and leaves `test_mode`, `model`, `reasoning_effort`,
          `til_channel`, etc. untouched (a partial send used to silently reset them to defaults, e.g.
          flipping an agent out of test mode).
        - `integrations` merges per integration, so sending one integration's
        permissions leaves every
          other connected integration untouched (a partial send used to disable them all).
        - `schedule` merges per field, so `{"schedule": {"timezone": "…"}}`
        changes only the timezone
          and leaves the run cadence / business-hours windows / weekend-skip intact.
        - `callback_settings` merges per field (`target_changes` is still
        required).

        Nested objects merge key-by-key, scalars/lists replace wholesale, and an
        explicit `null`

        deletes that key. An explicit top-level `"toolbox": null` still clears
        the whole toolbox.
      properties:
        actions:
          anyOf:
            - items:
                additionalProperties: true
                type: object
              type: array
            - type: 'null'
          default: null
          title: Actions
        agent_settings:
          anyOf:
            - additionalProperties: true
              type: object
            - type: 'null'
          default: null
          title: Agent Settings
        callback_settings:
          anyOf:
            - additionalProperties: true
              type: object
            - type: 'null'
          default: null
          title: Callback Settings
        description:
          anyOf:
            - type: string
            - type: 'null'
          default: null
          title: Description
        group_name:
          anyOf:
            - type: string
            - type: 'null'
          default: null
          title: Group Name
        group_order:
          anyOf:
            - type: integer
            - type: 'null'
          default: null
          title: Group Order
        integrations:
          anyOf:
            - additionalProperties: true
              type: object
            - type: 'null'
          default: null
          title: Integrations
        name:
          anyOf:
            - type: string
            - type: 'null'
          default: null
          title: Name
        process_entities_sequentially:
          anyOf:
            - type: boolean
            - type: 'null'
          default: null
          title: Process Entities Sequentially
        process_sub_entities_only_once:
          anyOf:
            - type: boolean
            - type: 'null'
          default: null
          title: Process Sub Entities Only Once
        schedule:
          anyOf:
            - additionalProperties: true
              type: object
            - type: 'null'
          default: null
          title: Schedule
        show_in_psa_pod:
          anyOf:
            - type: boolean
            - type: 'null'
          default: null
          title: Show In Psa Pod
        state:
          anyOf:
            - $ref: '#/components/schemas/WorkflowState'
            - type: 'null'
          default: null
        ticket_conditions:
          anyOf:
            - $ref: '#/components/schemas/PsaTicketConditions'
            - type: 'null'
          default: null
          description: >-
            Trigger/entity filter for TICKET agents — which tickets this agent
            fires on. Send it at the top level to set or change the filter; the
            server (re)builds the FIND_ENTITIES action from it (do not put the
            filter in `actions`). Use `time_entry_conditions` instead for
            TIME_ENTRY agents. Shape: `conditions` holds a `combinator`
            (`and`/`or`) and a `rules` list of leaves
            (`field`/`operator`/`value`) and/or nested groups (`rules` +
            `combinator`). A rule's position in the tree changes the logic — a
            company exclusion belongs as a top-level rule under the root `and`,
            not nested inside an `or`, or it only excludes when the other
            branches also miss.
        time_entry_conditions:
          anyOf:
            - $ref: '#/components/schemas/PsaConditions'
            - type: 'null'
          default: null
          description: >-
            Trigger/entity filter for TIME_ENTRY agents — the analogue of
            `ticket_conditions`. Ignored for TICKET agents.
        toolbox:
          anyOf:
            - additionalProperties: true
              type: object
            - type: 'null'
          default: null
          title: Toolbox
        trigger_type:
          anyOf:
            - $ref: '#/components/schemas/WorkflowTriggerType'
            - type: 'null'
          default: null
      title: UpdateAgentRequest
      type: object
    Agent:
      description: >-
        Lean summary of a workflow row — the per-item shape `GET
        /public-api/agents`

        (list) returns. The single-resource `GET /public-api/agents/<id>`
        returns the

        richer `AgentDetail`; historical snapshots come back as `AgentVersion`.
      properties:
        access:
          $ref: '#/components/schemas/AgentAccess'
        audience:
          $ref: '#/components/schemas/ToolAudience'
        autonomy_type:
          $ref: '#/components/schemas/AutonomyType'
        created_at:
          format: date-time
          title: Created At
          type: string
        entity_type:
          $ref: '#/components/schemas/EntityType'
        id:
          title: Id
          type: string
        name:
          title: Name
          type: string
        state:
          $ref: '#/components/schemas/WorkflowState'
        trigger_type:
          $ref: '#/components/schemas/WorkflowTriggerType'
        updated_at:
          format: date-time
          title: Updated At
          type: string
      required:
        - id
        - name
        - state
        - autonomy_type
        - entity_type
        - trigger_type
        - audience
        - access
        - created_at
        - updated_at
      title: Agent
      type: object
    SuccessMeta:
      properties:
        pagination:
          $ref: '#/components/schemas/Pagination'
        request_id:
          format: uuid
          type: string
        timings_ms:
          additionalProperties:
            type: number
          type: object
        warnings:
          description: >-
            Non-fatal warnings about the created/updated resource (e.g. an
            unhealthy PSA callback).
          items:
            type: string
          type: array
      required:
        - request_id
        - timings_ms
      type: object
    ErrorEnvelope:
      properties:
        error:
          properties:
            code:
              description: Stable machine-readable error code.
              type: string
            details:
              additionalProperties: true
              type: object
            message:
              type: string
          required:
            - code
            - message
          type: object
        meta:
          properties:
            request_id:
              format: uuid
              type:
                - string
                - 'null'
          type: object
      required:
        - error
        - meta
      type: object
    WorkflowState:
      enum:
        - ENABLED
        - DISABLED
        - DELETED
      title: WorkflowState
      type: string
    PsaTicketConditions:
      properties:
        conditions:
          $ref: '#/components/schemas/RulesGroup'
        custom_field_conditions:
          $ref: '#/components/schemas/CustomFieldConditionGroup'
          default: null
          title: Use Custom Field Conditions
          type:
            - 'null'
            - object
        workload_conditions:
          $ref: '#/components/schemas/WorkloadConditions'
          default: null
          description: >-
            Define a time period to filter tickets that were actively worked on
            by a technician. Only tickets with service activity logged within
            the specified date range will be included.
          title: Workload Conditions
          type:
            - 'null'
            - object
      required:
        - conditions
      title: PsaTicketConditions
      type: object
    PsaConditions:
      properties:
        conditions:
          $ref: '#/components/schemas/RulesGroup'
      required:
        - conditions
      title: PsaConditions
      type: object
    WorkflowTriggerType:
      enum:
        - SCHEDULED
        - TRIGGERED
        - NONE
      title: WorkflowTriggerType
      type: string
    AgentAccess:
      description: >-
        Who, within the MSP's own staff, may message an INTERNAL chat agent.


        ALL_USERS — any active member of the MSP tenant may message the agent.
        The default, so a
          newly created agent is usable by the whole team out of the box.
        ADMINS_ONLY — only tenant admins (and Neo super-admins). A natural
        choice for privileged
          agents such as the Neo Support Agent (its toolbox can retry/cancel runs and approve TILs),
          which an MSP admin can opt into — it is never forced; even managed agents default to ALL_USERS.
        SPECIFIC_USERS — admins, the agent's creator, and users it has been
        explicitly shared with
          (reuses the existing per-user `shared_workflows` grant).
      enum:
        - ALL_USERS
        - ADMINS_ONLY
        - SPECIFIC_USERS
      title: AgentAccess
      type: string
    ToolAudience:
      description: >-
        Trust domain a tool / agent / channel may be exposed to.


        MSP — technician/dashboard surfaces; tools may take MSP-wide scope and
        accept company/

        contact identifiers as LLM-supplied parameters. An agent/channel with
        this audience is the

        MSP's own INTERNAL surface (the dashboard labels MSP as "Internal").

        END_USER — the MSP's end-client users (white-label Teams bots, embedded
        end-company chat).

        END_USER tools must derive ALL identity (end-company, contact) from the
        session context,

        never from tool parameters, so a prompt-injected or curious end user
        cannot widen scope.

        The two surfaces are disjoint by construction:
        `resolve_end_user_toolbox` resolves only

        END_USER tools (deny-by-default, no auto-injection), and
        `resolve_full_toolbox` is for

        MSP surfaces only.
      enum:
        - MSP
        - END_USER
      title: ToolAudience
      type: string
    AutonomyType:
      enum:
        - CHAINED_ACTIONS
        - AGENTIC
        - CONVERSATIONAL
      title: AutonomyType
      type: string
    EntityType:
      enum:
        - ticket
        - project_ticket
        - task
        - scheduled_entry
        - time_entry
        - project
        - company
        - contact
        - resource
        - contract
        - opportunity
        - none
        - configuration
        - note
        - team
        - role
        - audit_trail
        - appointment
        - servicecall
        - contract_service
        - company_location
        - product
      title: EntityType
      type: string
    Pagination:
      properties:
        has_more:
          type: boolean
        next_cursor:
          type:
            - string
            - 'null'
      required:
        - next_cursor
        - has_more
      type: object
    RulesGroup:
      properties:
        combinator:
          $ref: '#/components/schemas/Combinator'
        rules:
          items:
            anyOf:
              - $ref: '#/components/schemas/Rule'
              - $ref: '#/components/schemas/RulesGroup'
          title: Rules
          type: array
      required:
        - rules
        - combinator
      title: RulesGroup
      type: object
    CustomFieldConditionGroup:
      properties:
        combinator:
          $ref: '#/components/schemas/Combinator'
        conditions:
          items:
            $ref: '#/components/schemas/CustomFieldCondition'
          title: Conditions
          type: array
      required:
        - conditions
        - combinator
      title: CustomFieldConditionGroup
      type: object
    WorkloadConditions:
      properties:
        companies:
          description: >-
            The names of the companies to filter tickets by. Empty list means no
            filtering by company
          items:
            type: string
          title: Companies
          type: array
        end_date_excluding:
          format: date
          title: End Date Excluding
          type: string
        start_date_including:
          format: date
          title: Start Date Including
          type: string
      required:
        - start_date_including
        - end_date_excluding
      title: WorkloadConditions
      type: object
    Combinator:
      enum:
        - and
        - or
      title: Combinator
      type: string
    Rule:
      properties:
        field:
          title: Field
          type: string
        id:
          anyOf:
            - type: string
            - type: 'null'
          default: null
          title: Id
        operator:
          $ref: '#/components/schemas/Operator'
        value:
          title: Value
        valueProviderInput:
          anyOf:
            - type: string
            - type: 'null'
          default: null
          title: Valueproviderinput
      required:
        - field
        - operator
        - value
      title: Rule
      type: object
    CustomFieldCondition:
      properties:
        field:
          title: Field
          type: string
        operator:
          $ref: '#/components/schemas/Operator'
          default: '='
          enum:
            - '='
            - '!='
            - <
            - '>'
            - <=
            - '>='
            - contains
            - 'null'
            - notNull
        value:
          title: Value
          type: string
      required:
        - field
        - value
      title: CustomFieldCondition
      type: object
    Operator:
      enum:
        - '='
        - '!='
        - <
        - '>'
        - <=
        - '>='
        - beginsWith
        - endsWith
        - contains
        - doesNotContain
        - doesNotBeginWith
        - doesNotEndWith
        - 'null'
        - notNull
        - in
        - notIn
      title: Operator
      type: string
  securitySchemes:
    bearerAuth:
      description: >-
        `Authorization: Bearer <token>` where `<token>` is either a
        `neo_sk_<env>_<secret>` API key (service account) or a Microsoft Entra
        ID access token (dashboard user).
      scheme: bearer
      type: http

````