Actions
Operations tagged Actions in the public API.
About Actions
A Call Action is a tool one chatbot may use mid-conversation. The chatbot's model reads the action's description and, turn by turn, decides whether to call it and with which arguments. There are six types: button (shows the visitor a link button), handoff (flags the conversation for a human and emails your team), notify (emails your team a summary), collect_info (saves the visitor's contact details as a customer), http_request (calls your HTTPS endpoint and gives the model its answer) and mcp (calls one tool on an MCP server — only where the platform has MCP switched on; GET /actions/types says which types can be created).
Lifecycle. POST /actions creates an action under a chatbot (action.created), PATCH /actions/{id} changes it (action.updated) and DELETE /actions/{id} removes it (action.deleted). Each time the model calls an enabled action, a run is recorded; when the run ends it emits action.run.succeeded (status: success) or action.run.failed (any other final status). POST /actions/{id}/test is a dry run: it calls nothing and records no run.
Secrets. credentials are write-only: stored encrypted and never returned — has_credentials says whether any are set. config is returned as stored, so keep secrets out of it.
IDs you will meet.
id— the action id, an integer, in every/actions/{id}path.chatbot_id— the chatbot the action belongs to;GET /actionsrequires it.tool_name— the name the model calls the action by:act__and a slug, unique within the chatbot. No operation takes it.- Run
id— one run, thedata.object.idofaction.run.*events; no operation takes it. A run'saction_idis the action id, ornullonce the action is deleted.
IDs on this page
- Chatbot id
<CHATBOT_ID> - Integer.
Get it from
- GET /v1/chatbots returns it as
data[].id - POST /v1/chatbots returns it as
id - GET /v1/chatbots/by_handle/{handle} returns it as
id - Webhook chatbot.created carries it as
data.object.id - In the dashboard, it is the number in a chatbot's address:
/dashboard/chatbots/{id}.
- GET /v1/chatbots returns it as
- Action id
<ACTION_ID> - Integer.
Get it from
- GET /v1/actions returns it as
data[].id - POST /v1/actions returns it as
id - GET /v1/chatbots/{id}/action_runs returns it as
data[].action_id - Webhook action.created carries it as
data.object.id action.run.*events name the run, not the action: read itsaction_idfromGET /chatbots/{id}/action_runs.
- GET /v1/actions returns it as
List action types
supported). The config each type takes is described on POST /actions.- Required scope
- actions:read
- Idempotency
- Read-only
- Rate limits
Responses
- 200Success.
objectstringRequiredAlways
listdataarray of objectRequiredEvery action type, in a fixed order.has_morebooleanRequirednext_cursoranynullableRequiredprevious_cursoranynullableRequiredurlstringRequiredAlways
/v1/actions/types
- 400A tenant selector header named a tenant other than the API key's own (
ACCOUNT_SELECTOR_NOT_SUPPORTED): an all-digitX-Account-Idother than the key owner's id or, whileworkspaces.context_enabledis on, anX-Workspace-Idother than the key's workspace (ws_prefix optional, case-insensitive). A header naming the key's own tenant, or a non-numericX-Account-Id, is ignored; a key cannot switch tenants, so send neither.Error codesACCOUNT_SELECTOR_NOT_SUPPORTED
- 401Missing or invalid API key.
Error codesUNAUTHENTICATED
- 403The API key is missing a required scope.
Error codesACCOUNT_SUSPENDEDINSUFFICIENT_SCOPE
- 429Rate limited. Honour the Retry-After header before retrying.
Error codesRATE_LIMITED
- 500Unexpected server error. Retry with the same Idempotency-Key.
Error codesSERVER_ERROR
Request
curl -X GET 'https://api.agentency.com/v1/actions/types' \
-H "Authorization: Bearer <YOUR_KEY>" \
-H "Content-Type: application/json"Response
Example 200 response
{
"object": "list",
"data": [
{
"object": "action_type",
"id": "button",
"supported": true
},
{
"object": "action_type",
"id": "handoff",
"supported": true
},
{
"object": "action_type",
"id": "notify",
"supported": true
},
{
"object": "action_type",
"id": "collect_info",
"supported": true
},
{
"object": "action_type",
"id": "http_request",
"supported": true
},
{
"object": "action_type",
"id": "mcp",
"supported": false
},
{
"object": "action_type",
"id": "web_search",
"supported": false
},
{
"object": "action_type",
"id": "integration",
"supported": false
}
],
"has_more": false,
"next_cursor": null,
"previous_cursor": null,
"url": "/v1/actions/types"
}List a chatbot's actions
chatbot_id (required), enabled or not, newest first and cursor-paginated. A chatbot the API key cannot reach is 404 NOT_FOUND.- Required scope
- actions:read
- Idempotency
- Read-only
- Rate limits
Integer.chatbot_id(query)Chatbot idPlaceholder in the samples:
<CHATBOT_ID>Get it from
- GET /v1/chatbots returns it as
data[].id - POST /v1/chatbots returns it as
id - GET /v1/chatbots/by_handle/{handle} returns it as
id - Webhook chatbot.created carries it as
data.object.id - In the dashboard, it is the number in a chatbot's address:
/dashboard/chatbots/{id}.
- GET /v1/chatbots returns it as
Query parameters
chatbot_idintegerRequiredThe chatbot whose actions to list. Required: actions belong to one chatbot. A chatbot the API key cannot reach is 404NOT_FOUND.Minimum:
1limitintegerOptionalPage size, clamped to 1–100.Default:
20Minimum:1Maximum:100starting_afterstringOptionalCursor from a previous page'snext_cursor: returns the page after it. A cursor minted for another list,sortororderis 422INVALID_CURSOR.ending_beforestringOptionalCursor from a previous page'sprevious_cursor: returns the page before it. Wins when both cursors are sent.created[gte]stringOptionalOnly rows created on or after this instant:YYYY-MM-DD(whole UTC day) or an RFC 3339 timestamp with a zone. Anything else is 422INVALID_PARAMETER.created[lte]stringOptionalOnly rows created on or before this instant:YYYY-MM-DD(whole UTC day) or an RFC 3339 timestamp with a zone. Anything else is 422INVALID_PARAMETER.
Responses
- 200Success.
objectstringRequiredAlways
listdataarray of ActionResourceRequiredhas_morebooleanRequirednext_cursorstringnullableRequiredprevious_cursorstringnullableRequiredurlstringRequiredAlways
/v1/actions
- 400A tenant selector header named a tenant other than the API key's own (
ACCOUNT_SELECTOR_NOT_SUPPORTED): an all-digitX-Account-Idother than the key owner's id or, whileworkspaces.context_enabledis on, anX-Workspace-Idother than the key's workspace (ws_prefix optional, case-insensitive). A header naming the key's own tenant, or a non-numericX-Account-Id, is ignored; a key cannot switch tenants, so send neither.Error codesACCOUNT_SELECTOR_NOT_SUPPORTED
- 401Missing or invalid API key.
Error codesUNAUTHENTICATED
- 403The API key is missing a required scope.
Error codesACCOUNT_SUSPENDEDINSUFFICIENT_SCOPE
- 404The resource does not exist on this account.
Error codesNOT_FOUND
- 422Validation failed. See
error.details. - 429Rate limited. Honour the Retry-After header before retrying.
Error codesRATE_LIMITED
- 500Unexpected server error. Retry with the same Idempotency-Key.
Error codesSERVER_ERROR
Request
curl -X GET 'https://api.agentency.com/v1/actions?chatbot_id=<CHATBOT_ID>' \
-H "Authorization: Bearer <YOUR_KEY>" \
-H "Content-Type: application/json"Response
Example 200 response
{
"object": "list",
"data": [
{
"object": "action",
"id": 1,
"chatbot_id": 1,
"type": "button",
"name": "Book a demo",
"tool_name": "act__book_a_demo",
"description": "Show this when the visitor wants to book a demo call.",
"enabled": true,
"is_high_risk": false,
"config": {
"button": {
"url": "https://shop.example.com/demo",
"label": "Book a demo",
"open_in_new_tab": true
},
"parameters": []
},
"provider": null,
"operation": null,
"connection_id": null,
"channels": null,
"has_credentials": false,
"run_count": 1,
"success_count": 1,
"success_rate": 100,
"last_run_at": "2026-01-01T00:00:00+00:00",
"created_at": "2026-01-01T00:00:00+00:00",
"updated_at": "2026-01-01T00:00:00+00:00"
}
],
"has_more": false,
"next_cursor": null,
"previous_cursor": null,
"url": "/v1/actions"
}Create an action
type is fixed for the action's life, and config holds that type's settings, checked for it (422 VALIDATION_FAILED on a config.* field):
button: button.label and button.url (required), button.open_in_new_tab (default true).
handoff: targets to email and a message_to_visitor. The model passes a reason.
notify: targets to email and a subject. The model passes a summary.
collect_info: parameters, the details to ask the visitor for (default: name, and a required email).
http_request: url (required) and method (GET by default, or POST, PUT, PATCH, DELETE), headers, body_template, auth with its secret in credentials, signing and egress_allowlist. parameters declares what the model passes; a {{name}} placeholder in the URL's path or query, a header value or the body takes its value.
mcp: server_url and tool (required), parameters and egress_allowlist. List a server's tools with POST /actions/mcp/discover.
The action is enabled unless enabled: false. An enabled action counts against the plan's actions per chatbot (402 AI_ACTION_LIMIT_REACHED) and the platform's ceiling of enabled actions per chatbot (422 ACTION_LIMIT_REACHED). A type this deployment cannot run is 422 ACTION_TYPE_UNAVAILABLE.- Required scope
- actions:write
- Idempotency
- Accepts Idempotency-Key
- Webhook events
- action.created
- Rate limits
Integer.chatbot_id(body)Chatbot idPlaceholder in the samples:
<CHATBOT_ID>Get it from
- GET /v1/chatbots returns it as
data[].id - POST /v1/chatbots returns it as
id - GET /v1/chatbots/by_handle/{handle} returns it as
id - Webhook chatbot.created carries it as
data.object.id - In the dashboard, it is the number in a chatbot's address:
/dashboard/chatbots/{id}.
- GET /v1/chatbots returns it as
Header parameters
Idempotency-KeystringOptionalMakes a retry safe. The first 2xx answer is stored for 24 h and replayed for the same key withIdempotency-Replayed: true; a retry while the first request runs is 409IDEMPOTENCY_IN_FLIGHT; the same key with a different payload is 422IDEMPOTENCY_KEY_REUSED; a malformed key is 400INVALID_IDEMPOTENCY_KEY.Min length:
16Max length:128Pattern:^[A-Za-z0-9_-]+$
Request body
chatbot_idintegerRequiredThe chatbot whose model may call the action. A chatbot the API key cannot reach is 404NOT_FOUND.Minimum:
1typestringRequiredWhat the action does when called (see the operation description). Fixed for the action's life.Possible values
buttonhandoffnotifycollect_infohttp_requestmcpweb_searchintegrationnamestringRequiredDisplay name, at most 120 characters.Max length:
120descriptionstringRequiredWhen the model should call the action, 8 to 1000 characters. The model reads it to decide, so write it for someone who has never seen your system: "Look up an order's status by its order number" gets used; "Order endpoint" does not.Min length:
8Max length:1000tool_namestringnullableOptionalThe tool name to derive from: lowercase letters, digits and_, at most 90 characters. The storedtool_nameisact__and this (or a slug ofnamewhen omitted), with a number appended when another action of the chatbot has it.Max length:
90Pattern:^[a-z0-9_]+$enabledbooleanOptionalWhether the chatbot offers the action to its model. Defaults totrue.is_high_riskbooleanOptionalRun a call only after the visitor confirms it in a later turn. Defaults tofalse. Anhttp_requestwithPOST,PUT,PATCHorDELETE, and anmcpaction withoutconfig.low_risk, wait for confirmation regardless.max_iterationsintegernullableOptionalHow many times the model may call this action in one turn, 1 to 5.null(the default) uses the platform's limit.Minimum:
1Maximum:5configobjectRequiredThe settings of the action'stype(see the operation description). Returned on every read, so never put a secret here.credentialsobjectOptionalWrite-only secrets forhttp_requestandmcp:token,usernameandpassword,api_key,signing_secret. Stored encrypted and never returned;has_credentialssays whether any are set.providerstringnullableOptionalIntegration actions only: the provider key from GET /integrations/catalog.Pattern:
^[a-z0-9_]{2,48}$operationstringnullableOptionalIntegration actions only: an executable operation key of that provider.Pattern:
^[a-z0-9_]{1,64}$connection_idintegernullableOptionalIntegration actions only: an active connection of that provider owned by the account.Minimum:
1channelsarray of stringnullableOptionalSurfaces the action is offered on: widget, direct_link, dashboard, api or a messaging platform. Null offers it everywhere.Min items:
1
Responses
- 201
ActionResourceobjectstringRequiredAlways
actionidintegerRequiredThe action id.chatbot_idintegerRequiredThe chatbot whose model may call the action.typestringRequiredWhat the action does when called. Fixed at creation.Possible values
buttonhandoffnotifycollect_infohttp_requestmcpweb_searchintegrationnamestringRequiredDisplay name, shown in the dashboard and run history.tool_namestringRequiredThe name the model calls the tool by:act__and a slug of thetool_nameornamesent at creation, unique within the chatbot (a number is appended on a clash). Fixed at creation.descriptionstringRequiredWhen the model should call the action, in your words. The model reads it to decide.enabledbooleanRequiredWhether the chatbot offers the action to its model.is_high_riskbooleanRequiredYour high-risk flag: a high-risk call runs only after the visitor confirms it in a later turn. Some calls wait whatever this says: anhttp_requestwithPOST,PUT,PATCHorDELETE, and anmcpaction unless itsconfig.low_riskistrue.configobjectnullableRequiredThe type's settings as stored, after validation filled the defaults (seePOST /actions). Never holdscredentials.providerstringnullableRequiredIntegration actions only: the provider key fromGET /integrations/catalog;nullfor every other type. Fixed at creation.operationstringnullableRequiredIntegration actions only: the provider operation the action runs;nullfor every other type. Fixed at creation.connection_idintegernullableRequiredIntegration actions only: the account connection the operation runs through;nullfor every other type.channelsarray of stringnullableRequiredThe surfaces the action is offered on (widget,direct_link,dashboard,apior a messaging platform);nulloffers it everywhere.has_credentialsbooleanRequiredWhether anycredentialsare stored. Their values are never returned.run_countintegerRequiredRuns that have ended, counted as they end. Askippedconfirmation request is not counted.success_countintegerRequiredOf those, the runs that succeeded.success_ratenumbernullableRequiredsuccess_count÷run_count, in percent with one decimal;nullbefore the first counted run.last_run_atstringnullableRequiredWhen the last counted run ended (ISO 8601, UTC);nullbefore the first.created_atstringnullableRequiredWhen the action was created (ISO 8601, UTC).updated_atstringnullableRequiredWhen the action last changed (ISO 8601, UTC).
- 400A tenant selector header named a tenant other than the API key's own (
ACCOUNT_SELECTOR_NOT_SUPPORTED): an all-digitX-Account-Idother than the key owner's id or, whileworkspaces.context_enabledis on, anX-Workspace-Idother than the key's workspace (ws_prefix optional, case-insensitive). A header naming the key's own tenant, or a non-numericX-Account-Id, is ignored; a key cannot switch tenants, so send neither. A mutation that honoursIdempotency-Keyalso returnsINVALID_IDEMPOTENCY_KEYwhen that header is not 16–128[A-Za-z0-9_-]characters.Error codesACCOUNT_SELECTOR_NOT_SUPPORTEDINVALID_IDEMPOTENCY_KEY
- 401Missing or invalid API key.
Error codesUNAUTHENTICATED
- 402A billing gate refused the request (plan limit, monthly AI budget or message credits). See
error.code.Error codesAI_ACTION_LIMIT_REACHED
- 403The API key is missing a required scope.
Error codesACCOUNT_SUSPENDEDINSUFFICIENT_SCOPE
- 404The resource does not exist on this account.
Error codesNOT_FOUND
- 409The request conflicts with the current resource state.
- 422Validation failed. See
error.details.Error codesACTION_LIMIT_REACHEDACTION_TYPE_UNAVAILABLEIDEMPOTENCY_KEY_REUSEDVALIDATION_FAILED
- 429Rate limited. Honour the Retry-After header before retrying.
Error codesRATE_LIMITED
- 500Unexpected server error. Retry with the same Idempotency-Key.
Error codesSERVER_ERROR
Request
curl -X POST 'https://api.agentency.com/v1/actions' \
-H "Authorization: Bearer <YOUR_KEY>" \
-H "Content-Type: application/json"
-d '{
"chatbot_id": 1,
"type": "type",
"name": "name",
"description": "description",
"tool_name": "tool_name",
"enabled": true,
"is_high_risk": true,
"max_iterations": "max_iterations"
}'Response
Example 201 response
{
"object": "action",
"id": 1,
"chatbot_id": 1,
"type": "http_request",
"name": "Track a delivery",
"tool_name": "act__track_a_delivery",
"description": "Look up where a parcel is when the visitor gives a tracking number.",
"enabled": true,
"is_high_risk": false,
"config": {
"method": "GET",
"url": "https://api.example.com/shipments/{{tracking_number}}",
"headers": [],
"auth": {
"type": "bearer",
"header_name": ""
},
"body_template": null,
"response": {
"mode": "full"
},
"signing": {
"enabled": false,
"header": "X-Signature"
},
"egress_allowlist": [],
"parameters": [
{
"name": "tracking_number",
"type": "string",
"required": true,
"description": "The parcel tracking number, e.g. 1Z999AA10123456784"
}
]
},
"provider": null,
"operation": null,
"connection_id": null,
"channels": null,
"has_credentials": true,
"run_count": 0,
"success_count": 0,
"success_rate": null,
"last_run_at": null,
"created_at": "2026-01-01T00:00:00+00:00",
"updated_at": "2026-01-01T00:00:00+00:00"
}Retrieve an action
config, whether it is enabled, and its run counters. credentials are never returned; has_credentials says whether any are stored.- Required scope
- actions:read
- Idempotency
- Read-only
- Rate limits
Integer.id(path)Action idPlaceholder in the samples:
<ACTION_ID>Get it from
- GET /v1/actions returns it as
data[].id - POST /v1/actions returns it as
id - GET /v1/chatbots/{id}/action_runs returns it as
data[].action_id - Webhook action.created carries it as
data.object.id action.run.*events name the run, not the action: read itsaction_idfromGET /chatbots/{id}/action_runs.
- GET /v1/actions returns it as
Path parameters
idintegerRequiredThe action id.
Responses
- 200
ActionResourceobjectstringRequiredAlways
actionidintegerRequiredThe action id.chatbot_idintegerRequiredThe chatbot whose model may call the action.typestringRequiredWhat the action does when called. Fixed at creation.Possible values
buttonhandoffnotifycollect_infohttp_requestmcpweb_searchintegrationnamestringRequiredDisplay name, shown in the dashboard and run history.tool_namestringRequiredThe name the model calls the tool by:act__and a slug of thetool_nameornamesent at creation, unique within the chatbot (a number is appended on a clash). Fixed at creation.descriptionstringRequiredWhen the model should call the action, in your words. The model reads it to decide.enabledbooleanRequiredWhether the chatbot offers the action to its model.is_high_riskbooleanRequiredYour high-risk flag: a high-risk call runs only after the visitor confirms it in a later turn. Some calls wait whatever this says: anhttp_requestwithPOST,PUT,PATCHorDELETE, and anmcpaction unless itsconfig.low_riskistrue.configobjectnullableRequiredThe type's settings as stored, after validation filled the defaults (seePOST /actions). Never holdscredentials.providerstringnullableRequiredIntegration actions only: the provider key fromGET /integrations/catalog;nullfor every other type. Fixed at creation.operationstringnullableRequiredIntegration actions only: the provider operation the action runs;nullfor every other type. Fixed at creation.connection_idintegernullableRequiredIntegration actions only: the account connection the operation runs through;nullfor every other type.channelsarray of stringnullableRequiredThe surfaces the action is offered on (widget,direct_link,dashboard,apior a messaging platform);nulloffers it everywhere.has_credentialsbooleanRequiredWhether anycredentialsare stored. Their values are never returned.run_countintegerRequiredRuns that have ended, counted as they end. Askippedconfirmation request is not counted.success_countintegerRequiredOf those, the runs that succeeded.success_ratenumbernullableRequiredsuccess_count÷run_count, in percent with one decimal;nullbefore the first counted run.last_run_atstringnullableRequiredWhen the last counted run ended (ISO 8601, UTC);nullbefore the first.created_atstringnullableRequiredWhen the action was created (ISO 8601, UTC).updated_atstringnullableRequiredWhen the action last changed (ISO 8601, UTC).
- 400A tenant selector header named a tenant other than the API key's own (
ACCOUNT_SELECTOR_NOT_SUPPORTED): an all-digitX-Account-Idother than the key owner's id or, whileworkspaces.context_enabledis on, anX-Workspace-Idother than the key's workspace (ws_prefix optional, case-insensitive). A header naming the key's own tenant, or a non-numericX-Account-Id, is ignored; a key cannot switch tenants, so send neither.Error codesACCOUNT_SELECTOR_NOT_SUPPORTED
- 401Missing or invalid API key.
Error codesUNAUTHENTICATED
- 403The API key is missing a required scope.
Error codesACCOUNT_SUSPENDEDINSUFFICIENT_SCOPE
- 404The resource does not exist on this account.
Error codesNOT_FOUND
- 429Rate limited. Honour the Retry-After header before retrying.
Error codesRATE_LIMITED
- 500Unexpected server error. Retry with the same Idempotency-Key.
Error codesSERVER_ERROR
Request
curl -X GET 'https://api.agentency.com/v1/actions/<ACTION_ID>' \
-H "Authorization: Bearer <YOUR_KEY>" \
-H "Content-Type: application/json"Response
Example 200 response
{
"object": "action",
"id": 1,
"chatbot_id": 1,
"type": "button",
"name": "Book a demo",
"tool_name": "act__book_a_demo",
"description": "Show this when the visitor wants to book a demo call.",
"enabled": true,
"is_high_risk": false,
"config": {
"button": {
"url": "https://shop.example.com/demo",
"label": "Book a demo",
"open_in_new_tab": true
},
"parameters": []
},
"provider": null,
"operation": null,
"connection_id": null,
"channels": null,
"has_credentials": false,
"run_count": 1,
"success_count": 1,
"success_rate": 100,
"last_run_at": "2026-01-01T00:00:00+00:00",
"created_at": "2026-01-01T00:00:00+00:00",
"updated_at": "2026-01-01T00:00:00+00:00"
}Update an action
config is merged key by key over the stored config, then checked for the action's type; credentials are merged the same way, and an empty value removes that key. type and tool_name cannot change.
Switching a disabled action on (enabled: true) passes the same plan limit and ceiling as a create. Every successful call emits action.updated, even one that changes nothing.- Required scope
- actions:write
- Idempotency
- Accepts Idempotency-Key
- Webhook events
- action.updated
- Rate limits
Integer.id(path)Action idPlaceholder in the samples:
<ACTION_ID>Get it from
- GET /v1/actions returns it as
data[].id - POST /v1/actions returns it as
id - GET /v1/chatbots/{id}/action_runs returns it as
data[].action_id - Webhook action.created carries it as
data.object.id action.run.*events name the run, not the action: read itsaction_idfromGET /chatbots/{id}/action_runs.
- GET /v1/actions returns it as
Path parameters
idintegerRequiredThe action id.
Header parameters
Idempotency-KeystringOptionalMakes a retry safe. The first 2xx answer is stored for 24 h and replayed for the same key withIdempotency-Replayed: true; a retry while the first request runs is 409IDEMPOTENCY_IN_FLIGHT; the same key with a different payload is 422IDEMPOTENCY_KEY_REUSED; a malformed key is 400INVALID_IDEMPOTENCY_KEY.Min length:
16Max length:128Pattern:^[A-Za-z0-9_-]+$
Request body
namestringOptionalDisplay name, at most 120 characters.Max length:
120descriptionstringOptionalWhen the model should call the action, 8 to 1000 characters.Min length:
8Max length:1000enabledbooleanOptionalWhether the chatbot offers the action to its model. Switching it on passes the plan limit and the per-chatbot ceiling.is_high_riskbooleanOptionalRun a call only after the visitor confirms it in a later turn.max_iterationsintegernullableOptionalHow many times the model may call this action in one turn, 1 to 5;nulluses the platform's limit.Minimum:
1Maximum:5configobjectRequiredKeys to change in the type's settings, merged over the stored ones; the result is checked as on create. Send a whole list (parameters,headers,targets) to change it.credentialsobjectOptionalSecrets to set, merged over the stored ones; an empty string ornullremoves that key. Write-only.providerstringnullableOptionalIntegration actions only. Fixed at create: a different value is 422.Pattern:
^[a-z0-9_]{2,48}$operationstringnullableOptionalIntegration actions only. Fixed at create: a different value is 422.Pattern:
^[a-z0-9_]{1,64}$connection_idintegernullableOptionalIntegration actions only. Another active connection of the same provider owned by the account.Minimum:
1channelsarray of stringnullableOptionalSurfaces the action is offered on: widget, direct_link, dashboard, api or a messaging platform. Null offers it everywhere.Min items:
1
Responses
- 200
ActionResourceobjectstringRequiredAlways
actionidintegerRequiredThe action id.chatbot_idintegerRequiredThe chatbot whose model may call the action.typestringRequiredWhat the action does when called. Fixed at creation.Possible values
buttonhandoffnotifycollect_infohttp_requestmcpweb_searchintegrationnamestringRequiredDisplay name, shown in the dashboard and run history.tool_namestringRequiredThe name the model calls the tool by:act__and a slug of thetool_nameornamesent at creation, unique within the chatbot (a number is appended on a clash). Fixed at creation.descriptionstringRequiredWhen the model should call the action, in your words. The model reads it to decide.enabledbooleanRequiredWhether the chatbot offers the action to its model.is_high_riskbooleanRequiredYour high-risk flag: a high-risk call runs only after the visitor confirms it in a later turn. Some calls wait whatever this says: anhttp_requestwithPOST,PUT,PATCHorDELETE, and anmcpaction unless itsconfig.low_riskistrue.configobjectnullableRequiredThe type's settings as stored, after validation filled the defaults (seePOST /actions). Never holdscredentials.providerstringnullableRequiredIntegration actions only: the provider key fromGET /integrations/catalog;nullfor every other type. Fixed at creation.operationstringnullableRequiredIntegration actions only: the provider operation the action runs;nullfor every other type. Fixed at creation.connection_idintegernullableRequiredIntegration actions only: the account connection the operation runs through;nullfor every other type.channelsarray of stringnullableRequiredThe surfaces the action is offered on (widget,direct_link,dashboard,apior a messaging platform);nulloffers it everywhere.has_credentialsbooleanRequiredWhether anycredentialsare stored. Their values are never returned.run_countintegerRequiredRuns that have ended, counted as they end. Askippedconfirmation request is not counted.success_countintegerRequiredOf those, the runs that succeeded.success_ratenumbernullableRequiredsuccess_count÷run_count, in percent with one decimal;nullbefore the first counted run.last_run_atstringnullableRequiredWhen the last counted run ended (ISO 8601, UTC);nullbefore the first.created_atstringnullableRequiredWhen the action was created (ISO 8601, UTC).updated_atstringnullableRequiredWhen the action last changed (ISO 8601, UTC).
- 400A tenant selector header named a tenant other than the API key's own (
ACCOUNT_SELECTOR_NOT_SUPPORTED): an all-digitX-Account-Idother than the key owner's id or, whileworkspaces.context_enabledis on, anX-Workspace-Idother than the key's workspace (ws_prefix optional, case-insensitive). A header naming the key's own tenant, or a non-numericX-Account-Id, is ignored; a key cannot switch tenants, so send neither. A mutation that honoursIdempotency-Keyalso returnsINVALID_IDEMPOTENCY_KEYwhen that header is not 16–128[A-Za-z0-9_-]characters.Error codesACCOUNT_SELECTOR_NOT_SUPPORTEDINVALID_IDEMPOTENCY_KEY
- 401Missing or invalid API key.
Error codesUNAUTHENTICATED
- 403The API key is missing a required scope.
Error codesACCOUNT_SUSPENDEDINSUFFICIENT_SCOPE
- 404The resource does not exist on this account.
Error codesNOT_FOUND
- 409The request conflicts with the current resource state.
Error codesIDEMPOTENCY_IN_FLIGHT
- 422Validation failed. See
error.details.Error codesACTION_LIMIT_REACHEDIDEMPOTENCY_KEY_REUSEDVALIDATION_FAILED
- 429Rate limited. Honour the Retry-After header before retrying.
Error codesRATE_LIMITED
- 500Unexpected server error. Retry with the same Idempotency-Key.
Error codesSERVER_ERROR
Request
curl -X PATCH 'https://api.agentency.com/v1/actions/<ACTION_ID>' \
-H "Authorization: Bearer <YOUR_KEY>" \
-H "Content-Type: application/json"
-d '{
"name": "name",
"description": "description",
"enabled": true,
"is_high_risk": true,
"max_iterations": "max_iterations",
"config": "config",
"credentials": "credentials",
"provider": "provider"
}'Response
Example 200 response
{
"object": "action",
"id": 1,
"chatbot_id": 1,
"type": "http_request",
"name": "Track a delivery",
"tool_name": "act__track_a_delivery",
"description": "Look up where a parcel is when the visitor gives a tracking number.",
"enabled": true,
"is_high_risk": false,
"config": {
"url": "https://api.example.com/shipments/{{tracking_number}}",
"auth": {
"type": "bearer",
"header_name": ""
},
"method": "GET",
"headers": [],
"signing": {
"header": "X-Signature",
"enabled": false
},
"response": {
"mode": "full"
},
"parameters": [
{
"name": "tracking_number",
"type": "string",
"required": true,
"description": "The parcel tracking number, e.g. 1Z999AA10123456784"
}
],
"body_template": null,
"egress_allowlist": []
},
"provider": null,
"operation": null,
"connection_id": null,
"channels": null,
"has_credentials": true,
"run_count": 0,
"success_count": 0,
"success_rate": null,
"last_run_at": null,
"created_at": "2026-01-01T00:00:00+00:00",
"updated_at": "2026-01-01T00:00:00+00:00"
}Delete an action
GET /chatbots/{id}/action_runs with action_id: null.- Required scope
- actions:write
- Idempotency
- Accepts Idempotency-Key
- Webhook events
- action.deleted
- Rate limits
Integer.id(path)Action idPlaceholder in the samples:
<ACTION_ID>Get it from
- GET /v1/actions returns it as
data[].id - POST /v1/actions returns it as
id - GET /v1/chatbots/{id}/action_runs returns it as
data[].action_id - Webhook action.created carries it as
data.object.id action.run.*events name the run, not the action: read itsaction_idfromGET /chatbots/{id}/action_runs.
- GET /v1/actions returns it as
Path parameters
idintegerRequiredThe action id.
Header parameters
Idempotency-KeystringOptionalMakes a retry safe. The first 2xx answer is stored for 24 h and replayed for the same key withIdempotency-Replayed: true; a retry while the first request runs is 409IDEMPOTENCY_IN_FLIGHT; the same key with a different payload is 422IDEMPOTENCY_KEY_REUSED; a malformed key is 400INVALID_IDEMPOTENCY_KEY.Min length:
16Max length:128Pattern:^[A-Za-z0-9_-]+$
Responses
- 200Success.
objectstringRequiredAlways
actionidintegerRequiredThe deleted action's id.deletedbooleanRequiredAlwaystrue.
- 400A tenant selector header named a tenant other than the API key's own (
ACCOUNT_SELECTOR_NOT_SUPPORTED): an all-digitX-Account-Idother than the key owner's id or, whileworkspaces.context_enabledis on, anX-Workspace-Idother than the key's workspace (ws_prefix optional, case-insensitive). A header naming the key's own tenant, or a non-numericX-Account-Id, is ignored; a key cannot switch tenants, so send neither. A mutation that honoursIdempotency-Keyalso returnsINVALID_IDEMPOTENCY_KEYwhen that header is not 16–128[A-Za-z0-9_-]characters.Error codesACCOUNT_SELECTOR_NOT_SUPPORTEDINVALID_IDEMPOTENCY_KEY
- 401Missing or invalid API key.
Error codesUNAUTHENTICATED
- 403The API key is missing a required scope.
Error codesACCOUNT_SUSPENDEDINSUFFICIENT_SCOPE
- 404The resource does not exist on this account.
Error codesNOT_FOUND
- 409The request conflicts with the current resource state.
Error codesIDEMPOTENCY_IN_FLIGHT
- 422Validation failed. See
error.details.Error codesIDEMPOTENCY_KEY_REUSED
- 429Rate limited. Honour the Retry-After header before retrying.
Error codesRATE_LIMITED
- 500Unexpected server error. Retry with the same Idempotency-Key.
Error codesSERVER_ERROR
Request
curl -X DELETE 'https://api.agentency.com/v1/actions/<ACTION_ID>' \
-H "Authorization: Bearer <YOUR_KEY>" \
-H "Content-Type: application/json"Response
Example 200 response
{
"object": "action",
"id": 1,
"deleted": true
}List an action's runs
id, status and start time. A dry run from POST /actions/{id}/test is not a run. To be told as runs end, subscribe to action.run.succeeded and action.run.failed.- Required scope
- actions:read
- Idempotency
- Read-only
- Rate limits
Integer.id(path)Action idPlaceholder in the samples:
<ACTION_ID>Get it from
- GET /v1/actions returns it as
data[].id - POST /v1/actions returns it as
id - GET /v1/chatbots/{id}/action_runs returns it as
data[].action_id - Webhook action.created carries it as
data.object.id action.run.*events name the run, not the action: read itsaction_idfromGET /chatbots/{id}/action_runs.
- GET /v1/actions returns it as
Path parameters
idintegerRequiredThe action id.
Query parameters
limitintegerOptionalPage size, clamped to 1–100.Default:
20Minimum:1Maximum:100starting_afterstringOptionalCursor from a previous page'snext_cursor: returns the page after it. A cursor minted for another list,sortororderis 422INVALID_CURSOR.ending_beforestringOptionalCursor from a previous page'sprevious_cursor: returns the page before it. Wins when both cursors are sent.created[gte]stringOptionalOnly rows created on or after this instant:YYYY-MM-DD(whole UTC day) or an RFC 3339 timestamp with a zone. Anything else is 422INVALID_PARAMETER.created[lte]stringOptionalOnly rows created on or before this instant:YYYY-MM-DD(whole UTC day) or an RFC 3339 timestamp with a zone. Anything else is 422INVALID_PARAMETER.
Responses
- 200Success.
objectstringRequiredAlways
listdataarray of ActionRunResourceRequiredhas_morebooleanRequirednext_cursorstringnullableRequiredprevious_cursorstringnullableRequiredurlstringRequired
- 400A tenant selector header named a tenant other than the API key's own (
ACCOUNT_SELECTOR_NOT_SUPPORTED): an all-digitX-Account-Idother than the key owner's id or, whileworkspaces.context_enabledis on, anX-Workspace-Idother than the key's workspace (ws_prefix optional, case-insensitive). A header naming the key's own tenant, or a non-numericX-Account-Id, is ignored; a key cannot switch tenants, so send neither.Error codesACCOUNT_SELECTOR_NOT_SUPPORTED
- 401Missing or invalid API key.
Error codesUNAUTHENTICATED
- 403The API key is missing a required scope.
Error codesACCOUNT_SUSPENDEDINSUFFICIENT_SCOPE
- 404The resource does not exist on this account.
Error codesNOT_FOUND
- 422Validation failed. See
error.details.Error codesINVALID_CURSORINVALID_PARAMETER
- 429Rate limited. Honour the Retry-After header before retrying.
Error codesRATE_LIMITED
- 500Unexpected server error. Retry with the same Idempotency-Key.
Error codesSERVER_ERROR
Request
curl -X GET 'https://api.agentency.com/v1/actions/<ACTION_ID>/runs' \
-H "Authorization: Bearer <YOUR_KEY>" \
-H "Content-Type: application/json"Response
Example 200 response
{
"object": "list",
"data": [
{
"object": "action_run",
"id": 1,
"action_id": 1,
"status": "success",
"created_at": "2026-01-01T00:00:00+00:00"
}
],
"has_more": false,
"next_cursor": null,
"previous_cursor": null,
"url": "/v1/actions/1/runs"
}List a chatbot's action runs
action_id is null.- Required scope
- actions:read
- Idempotency
- Read-only
- Rate limits
Integer.id(path)Chatbot idPlaceholder in the samples:
<CHATBOT_ID>Get it from
- GET /v1/chatbots returns it as
data[].id - POST /v1/chatbots returns it as
id - GET /v1/chatbots/by_handle/{handle} returns it as
id - Webhook chatbot.created carries it as
data.object.id - In the dashboard, it is the number in a chatbot's address:
/dashboard/chatbots/{id}.
- GET /v1/chatbots returns it as
Path parameters
idintegerRequiredThe chatbot id.
Query parameters
limitintegerOptionalPage size, clamped to 1–100.Default:
20Minimum:1Maximum:100starting_afterstringOptionalCursor from a previous page'snext_cursor: returns the page after it. A cursor minted for another list,sortororderis 422INVALID_CURSOR.ending_beforestringOptionalCursor from a previous page'sprevious_cursor: returns the page before it. Wins when both cursors are sent.created[gte]stringOptionalOnly rows created on or after this instant:YYYY-MM-DD(whole UTC day) or an RFC 3339 timestamp with a zone. Anything else is 422INVALID_PARAMETER.created[lte]stringOptionalOnly rows created on or before this instant:YYYY-MM-DD(whole UTC day) or an RFC 3339 timestamp with a zone. Anything else is 422INVALID_PARAMETER.
Responses
- 200Success.
objectstringRequiredAlways
listdataarray of ActionRunResourceRequiredhas_morebooleanRequirednext_cursorstringnullableRequiredprevious_cursorstringnullableRequiredurlstringRequired
- 400A tenant selector header named a tenant other than the API key's own (
ACCOUNT_SELECTOR_NOT_SUPPORTED): an all-digitX-Account-Idother than the key owner's id or, whileworkspaces.context_enabledis on, anX-Workspace-Idother than the key's workspace (ws_prefix optional, case-insensitive). A header naming the key's own tenant, or a non-numericX-Account-Id, is ignored; a key cannot switch tenants, so send neither.Error codesACCOUNT_SELECTOR_NOT_SUPPORTED
- 401Missing or invalid API key.
Error codesUNAUTHENTICATED
- 403The API key is missing a required scope.
Error codesACCOUNT_SUSPENDEDINSUFFICIENT_SCOPE
- 404The resource does not exist on this account.
Error codesNOT_FOUND
- 422Validation failed. See
error.details.Error codesINVALID_CURSORINVALID_PARAMETER
- 429Rate limited. Honour the Retry-After header before retrying.
Error codesRATE_LIMITED
- 500Unexpected server error. Retry with the same Idempotency-Key.
Error codesSERVER_ERROR
Request
curl -X GET 'https://api.agentency.com/v1/chatbots/<CHATBOT_ID>/action_runs' \
-H "Authorization: Bearer <YOUR_KEY>" \
-H "Content-Type: application/json"Response
Example 200 response
{
"object": "list",
"data": [
{
"object": "action_run",
"id": 1,
"action_id": 1,
"status": "success",
"created_at": "2026-01-01T00:00:00+00:00"
}
],
"has_more": false,
"next_cursor": null,
"previous_cursor": null,
"url": "/v1/chatbots/1/action_runs"
}Dry-run an action
message: asks the chatbot's model — with this action as its only tool and the knowledge a live turn would retrieve — whether it would call the action for that visitor message (invoked) and with which arguments, then dry-runs the action with them.
With arguments only: skips the model and dry-runs the action with your arguments, to check its config and templating.
A dry run makes no outbound call — no request to your URL or MCP server, no email — records no run and emits no event; request_summary shows the call a live run would make. When the chatbot's model cannot call tools, message is ignored and invoked is null.
A message probe is a real model completion, and this endpoint does NOT accept Idempotency-Key: every call with a message — including an automatic client retry after a timeout — runs and pays for another completion. Retry only when you would accept a second cost. The completion counts against the chatbot's monthly AI budget (402 CHATBOT_BUDGET_EXCEEDED / USER_BUDGET_EXCEEDED once it is spent) and needs message credits left (402 MESSAGE_CREDITS_EXCEEDED), though it uses none. An arguments-only dry run is never billing-gated. 404 ACTIONS_DISABLED while Call Actions are switched off platform-wide.- Required scope
- actions:write
- Webhook events
- None
- Rate limits
Integer.id(path)Action idPlaceholder in the samples:
<ACTION_ID>Get it from
- GET /v1/actions returns it as
data[].id - POST /v1/actions returns it as
id - GET /v1/chatbots/{id}/action_runs returns it as
data[].action_id - Webhook action.created carries it as
data.object.id action.run.*events name the run, not the action: read itsaction_idfromGET /chatbots/{id}/action_runs.
- GET /v1/actions returns it as
Path parameters
idintegerRequiredThe action id.
Request body
messagestringnullableOptionalA visitor message, at most 4000 characters: the model decides whether it would call the action for it. A real, paid model completion (see the operation description).Max length:
4000argumentsobjectOptionalArguments for the action, keyed byconfig.parametersname: dry-run with these and skip the model. Also the fallback when the model, givenmessage, does not call the action. At most 50 top-level keys, 5 levels of nesting and 16 KB as JSON; larger is 422VALIDATION_FAILED.
Responses
- 200Success.
objectstringRequiredAlways
action_testidintegerRequiredThe action id.invokedbooleannullableRequiredWhether the model called the action formessage—falsealso when the model could not be reached.nullwhen no model was asked (nomessage, or a model that cannot call tools).argumentsobjectnullableRequiredThe arguments the dry run used: the model's when it called the action, otherwise the ones you sent.nullwhen there were none.assistant_messagestringnullableRequiredThe text the model answered with — when it did not call the action, its reply to the visitor, which usually shows why.nullwhen it wrote none or no model was asked.statusstringRequiredHow the dry run ended:success;pendingforhandoffandnotify, whose email a live run would queue; orfailed, witherror_code. A dry run skips the confirmation and rate gates of a live run.Possible values
successfailedskippedconfirmation_pendingpendingtimed_outmodel_contentstringRequiredThe tool result the model would read back.metadataobjectRequiredWhat the chat would act on: forbuttonitslabel,urlandopen_in_new_tab; forhandoffhandoff_requested,handoff_reasonandmessage_to_visitor.error_codestringnullableRequiredWhy the dry run failed —invalid_arguments(the arguments do not fitconfig.parameters) orinvalid_config;nullotherwise.request_summaryobjectnullableRequiredThe call a live run would make, without secrets:methodandhostforhttp_request,hostandtoolformcp, each withdry_run: true.nullfor the types that call nothing.response_summaryobjectnullableRequiredWhat the action reports without calling anything — thelabelof abutton, thefieldsacollect_infowould save.nullforhttp_requestandmcp: a dry run sends no request, so there is no response.probe_contextstringnullableRequiredWhich prompt the model was asked with:knowledgewhen retrieval found something formessage,no_knowledgewhen it did not.nullwhen no model was asked.Possible values
knowledgeno_knowledgenullknowledge_chunksintegerRequiredHow many knowledge chunks themessageprobe retrieved;0when no model was asked.
- 400A tenant selector header named a tenant other than the API key's own (
ACCOUNT_SELECTOR_NOT_SUPPORTED): an all-digitX-Account-Idother than the key owner's id or, whileworkspaces.context_enabledis on, anX-Workspace-Idother than the key's workspace (ws_prefix optional, case-insensitive). A header naming the key's own tenant, or a non-numericX-Account-Id, is ignored; a key cannot switch tenants, so send neither.Error codesACCOUNT_SELECTOR_NOT_SUPPORTED
- 401Missing or invalid API key.
Error codesUNAUTHENTICATED
- 402A billing gate refused the request (plan limit, monthly AI budget or message credits). See
error.code.Error codesCHATBOT_BUDGET_EXCEEDEDMESSAGE_CREDITS_EXCEEDEDUSER_BUDGET_EXCEEDED
- 403The API key is missing a required scope.
Error codesACCOUNT_SUSPENDEDINSUFFICIENT_SCOPE
- 404The resource does not exist on this account.
Error codesACTIONS_DISABLEDNOT_FOUND
- 422Validation failed. See
error.details.Error codesVALIDATION_FAILED
- 429Rate limited. Honour the Retry-After header before retrying.
Error codesRATE_LIMITED
- 500Unexpected server error. Retry with the same Idempotency-Key.
Error codesSERVER_ERROR
Request
curl -X POST 'https://api.agentency.com/v1/actions/<ACTION_ID>/test' \
-H "Authorization: Bearer <YOUR_KEY>" \
-H "Content-Type: application/json"
-d '{
"message": "message",
"arguments": "arguments"
}'Response
Example 200 response
{
"object": "action_test",
"id": 1,
"invoked": null,
"arguments": {
"tracking_number": "1Z999AA10123456784"
},
"assistant_message": null,
"status": "success",
"model_content": "(dry run) The request was not sent.",
"metadata": [],
"error_code": null,
"request_summary": {
"method": "GET",
"host": "api.example.com",
"dry_run": true
},
"response_summary": null,
"probe_context": null,
"knowledge_chunks": 0
}List an MCP server's tools
server_url for its tools (tools/list) and returns them, so you can create one mcp action per tool. token, when sent, goes to the server as a bearer token and is not stored: put it in the action's credentials.token. The server URL passes the same address checks as any outbound call.
404 MCP_NOT_ENABLED while MCP actions are switched off platform-wide; 422 MCP_DISCOVERY_FAILED when the server cannot be listed. Not idempotency-gated: every call contacts the server.- Required scope
- actions:write
- Webhook events
- None
- Rate limits
Request body
server_urlstringRequiredThe MCP server'shttp(s)URL, at most 2048 characters. A private or internal address is refused.Format:
uriMax length:2048Pattern:^https?:\/\/tokenstringnullableOptionalA bearer token the server needs, at most 2048 characters. Sent with this request only, never stored.Max length:
2048
Responses
- 200Success.
objectstringRequiredAlways
listdataarray of objectRequiredThe server's tools, each exactly as itstools/listanswer gives it:name, and usuallydescriptionandinputSchema. Create one action per tool you want, withtype: mcp,config.toolset to itsnameandconfig.parametersfrom itsinputSchema.has_morebooleanRequirednext_cursoranynullableRequiredprevious_cursoranynullableRequiredurlstringRequiredAlways
/v1/actions/mcp/discover
- 400A tenant selector header named a tenant other than the API key's own (
ACCOUNT_SELECTOR_NOT_SUPPORTED): an all-digitX-Account-Idother than the key owner's id or, whileworkspaces.context_enabledis on, anX-Workspace-Idother than the key's workspace (ws_prefix optional, case-insensitive). A header naming the key's own tenant, or a non-numericX-Account-Id, is ignored; a key cannot switch tenants, so send neither.Error codesACCOUNT_SELECTOR_NOT_SUPPORTED
- 401Missing or invalid API key.
Error codesUNAUTHENTICATED
- 403The API key is missing a required scope.
Error codesACCOUNT_SUSPENDEDINSUFFICIENT_SCOPE
- 404The resource does not exist on this account.
Error codesMCP_NOT_ENABLED
- 422Validation failed. See
error.details.Error codesMCP_DISCOVERY_FAILEDVALIDATION_FAILED
- 429Rate limited. Honour the Retry-After header before retrying.
Error codesRATE_LIMITED
- 500Unexpected server error. Retry with the same Idempotency-Key.
Error codesSERVER_ERROR
Request
curl -X POST 'https://api.agentency.com/v1/actions/mcp/discover' \
-H "Authorization: Bearer <YOUR_KEY>" \
-H "Content-Type: application/json"
-d '{
"server_url": "server_url",
"token": "token"
}'Response
Example 200 response
{
"object": "list",
"data": [
{
"name": "search_orders",
"description": "Find orders by customer email.",
"inputSchema": {
"type": "object",
"properties": {
"email": {
"type": "string"
}
},
"required": [
"email"
]
},
"annotations": [],
"param_headers": []
}
],
"has_more": false,
"next_cursor": null,
"previous_cursor": null,
"url": "/v1/actions/mcp/discover"
}