curl --request PUT \
--url https://api.neoagent.io/public-api/agents/{agent_id} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"agent_settings_edits": {
"custom_instructions": {
"append": "- Only cite ticket history you have actually read."
}
}
}
'{
"data": {
"access": "ALL_USERS",
"audience": "MSP",
"autonomy_type": "CHAINED_ACTIONS",
"created_at": "2023-11-07T05:31:56Z",
"created_via": "EDITOR",
"entity_type": "ticket",
"group_name": "<string>",
"group_order": 123,
"id": "<string>",
"name": "<string>",
"source_template_id": 123,
"state": "ENABLED",
"trigger_type": "SCHEDULED",
"updated_at": "2023-11-07T05:31:56Z"
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"timings_ms": {},
"pagination": {
"has_more": true,
"next_cursor": "<string>"
},
"warnings": [
"<string>"
]
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}Update an agent
Accepts PUT or PATCH (identical partial-merge semantics). Partial-patches an agent — only the fields you send change. autonomy_type, entity_type, and trigger_type are immutable — trigger_type may be sent but must match the agent’s current value, and a differing value returns 400. 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). On a TRIAGE_TICKET / TRIAGE_TIME_ENTRY action, fields_to_triage is a list and so replaces wholesale under the normal merge — send fields_to_triage_edits in that action’s input_as_dict to change one field keyed by field_name: a matching entry merges key-by-key (every other triage field stays byte-identical), an unmatched one is appended (field_name is the only setting it must carry), and an entry carrying remove: true drops its target instead of editing it — a removal names its target and nothing else, must match a configured field (a no-match returns 400 rather than silently doing nothing), and cannot remove the last remaining field. A PSA-unsupported field_name returns 400, and sending both fields_to_triage and fields_to_triage_edits in one action returns 400. Use the field_name spelling GET /agents/<id> returns (the PSA name, e.g. contact, customFields); the enum member name (CONTACT, CUSTOM_FIELD) is also accepted. A custom field is keyed by field_name (customFields) plus custom_field_name, since all custom fields share one field_name — send both to target the right one. 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. To edit a long custom_instructions runbook without resending the whole blob, send agent_settings_edits.custom_instructions with either edits (a list of exact old_string/new_string search-replaces — each must match exactly once, or pass replace_all; zero or multiple matches reject the request and write nothing) or append (text added with a newline separator). An optional base_hash (sha256:… of the instructions you read) gives optimistic concurrency: a stale hash returns 409 agent_stale_write instead of clobbering a concurrent dashboard edit. This form is mutually exclusive with a full agent_settings.custom_instructions in the same request (400), and applies only to AGENTIC / CONVERSATIONAL agents (CHAINED_ACTIONS has no agent_settings layer → 400). 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.
curl --request PUT \
--url https://api.neoagent.io/public-api/agents/{agent_id} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"agent_settings_edits": {
"custom_instructions": {
"append": "- Only cite ticket history you have actually read."
}
}
}
'{
"data": {
"access": "ALL_USERS",
"audience": "MSP",
"autonomy_type": "CHAINED_ACTIONS",
"created_at": "2023-11-07T05:31:56Z",
"created_via": "EDITOR",
"entity_type": "ticket",
"group_name": "<string>",
"group_order": 123,
"id": "<string>",
"name": "<string>",
"source_template_id": 123,
"state": "ENABLED",
"trigger_type": "SCHEDULED",
"updated_at": "2023-11-07T05:31:56Z"
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"timings_ms": {},
"pagination": {
"has_more": true,
"next_cursor": "<string>"
},
"warnings": [
"<string>"
]
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}Authorizations
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).
Path Parameters
Body
PUT /public-api/agents/<id> body — a partial patch. Only the fields you send are
changed; everything else keeps its current value. autonomy_type, entity_type, and
trigger_type are immutable: autonomy_type/entity_type aren't fields here at
all, and trigger_type may be sent but must match the agent's current value — a differing
value returns 400. 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.
Because an id-less entry matches by action_type, it can only ever ADD an action of a type
the agent does not already have. Send an id-less entry for a type that already has exactly one
action and you EDIT that action (200), rather than adding a second one; two or more of that
type return 400. There is therefore no supported way to add a second action of an existing
type through this endpoint — add it in the dashboard.
On a TRIAGE_TICKET / TRIAGE_TIME_ENTRY action, fields_to_triage is a list and so replaces
wholesale under that per-action merge; to change one triage field without resending the others
send fields_to_triage_edits in the action's input_as_dict. Each entry is keyed by
field_name: a match merges key-by-key (every other triage field stays byte-identical), an
unmatched field_name is appended (field_name is the only setting it must carry), an entry
carrying remove: true drops its target, a PSA-unsupported field_name is rejected 400, and
sending both fields_to_triage and fields_to_triage_edits in one action is a 400. A removal
names its target and nothing else, must match a configured field, and cannot empty the list.
A custom field is keyed by field_name AND custom_field_name, so an edit to one must send
both — custom_field_name alone says which custom field you mean. An edit that omits it keys
to a different slot, matches nothing, and is appended as a new entry; on an enabled agent that
entry is rejected 400 (custom_triage_field_without_a_name), because a custom field with no
name triages nothing.
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:
toolboxmerges per tool undertool_settings_by_type(nested objects merge; scalars and lists — e.g.allowed_users— replace wholesale; anulltool key disables that tool).agent_settingsmerges per field, so{"agent_settings": {"custom_instructions": "…"}}changes only the instructions and leavestest_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).integrationsmerges per integration, so sending one integration's permissions leaves every other connected integration untouched (a partial send used to disable them all).schedulemerges per field, so{"schedule": {"timezone": "…"}}changes only the timezone and leaves the run cadence / business-hours windows / weekend-skip intact.callback_settingsmerges per field (target_changesis still required). Nested objects merge key-by-key, scalars/lists replace wholesale, and an explicitnulldeletes that key. An explicit top-level"toolbox": nullstill clears the whole toolbox.
Surgical, config-preserving edits to agent_settings string fields. Today only
custom_instructions is editable this way. Use this instead of sending the whole
agent_settings.custom_instructions string when you want to change one span of a long runbook
without a full read-modify-write of the blob. Mutually exclusive with a full
agent_settings.custom_instructions in the same request.
Show child attributes
Show child attributes
ENABLED, DISABLED, DELETED Trigger/entity filter — 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). A TIME_ENTRY agent carries this field too — there it selects the tickets whose time entries are searched, and time_entry_conditions then selects the time entries on them. 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. A leaf's value is a single value; only the in and notIn operators take a list. A list under any other operator returns 400.
Show child attributes
Show child attributes
Trigger/entity filter for TIME_ENTRY agents — which time entries this agent fires on, on the tickets ticket_conditions selected. Same shape as ticket_conditions. Ignored for TICKET agents.
Show child attributes
Show child attributes
SCHEDULED, TRIGGERED, NONE Response
Success.
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.
Show child attributes
Show child attributes
Show child attributes
Show child attributes
