Actions
An action is a tool the chatbot can decide to use mid-conversation. Without them a bot can only talk about what it has read. With them it can check an order, book a slot, capture a lead, or pull in a human.
The bot chooses when to call an action from its description, the same way it chooses what to say. Your job is to describe it well and handle the request.
Types
| Type | What it does |
|---|---|
http_request | Calls an endpoint you own and feeds the response back into the answer |
mcp | Calls a tool exposed by an MCP server (only where the platform has MCP switched on) |
handoff | Flags the conversation for a human and emails your team |
collect_info | Asks for contact details and saves them as a customer |
notify | Emails your team a summary |
button | Shows the visitor a button that opens a page you chose |
GET /v1/actions/types lists the six types and whether this deployment can
create each one. The settings each type takes in config are described on
POST /v1/actions.
Registering an HTTP action
The one you will use most: the bot calls your endpoint and uses the answer.
POST/v1/actions
curl -X POST https://api.agentency.com/v1/actions \
-H "Authorization: Bearer <YOUR_KEY>" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: action-order-lookup-1" \
-d '{
"chatbot_id": 1,
"type": "http_request",
"name": "Look up an order",
"description": "Look up the status of a customer order by its order number.",
"config": {
"method": "GET",
"url": "https://api.example.com/orders/{{order_number}}",
"auth": { "type": "bearer" },
"parameters": [
{
"name": "order_number",
"type": "string",
"description": "The customer order number, e.g. ORD-1234",
"required": true
}
]
},
"credentials": { "token": "<YOUR_SERVICE_TOKEN>" }
}'parameters declares what the model may pass. A {{name}} placeholder in the
URL's path or query, a header value or body_template takes that argument;
the scheme and host never change. The token goes in credentials, not in
config.headers: credentials are stored encrypted and never returned, while
config comes back on every read.
The model decides whether to call an action by reading description and the
parameter descriptions. "Look up the status of a customer order by its order
number" gets used correctly. "Order endpoint" does not. Write them for a
colleague who has never seen your system.
Testing before you ship
POST/v1/actions/{id}/test
A dry run: it shows what the action would do without doing it. Send
arguments to check the config and templating; send a visitor message to
ask the model whether it would call the action at all, and with what.
curl -X POST https://api.agentency.com/v1/actions/1/test \
-H "Authorization: Bearer <YOUR_KEY>" \
-H "Content-Type: application/json" \
-d '{"arguments": {"order_number": "ORD-1234"}}'Nothing is sent: no request reaches your endpoint or MCP server, no email
goes out, no run is recorded and no event fires. request_summary shows the
method and host a live run would call.
With message, the test runs a real model completion against the chatbot's
monthly AI budget, and the endpoint does not accept Idempotency-Key: a
client that retries after a timeout pays again. An arguments-only dry run
is free.
Watching runs
Every time the model calls an action in a conversation, a run is recorded.
GET/v1/actions/{id}/runs
{
"object": "list",
"data": [
{
"object": "action_run",
"id": 1,
"action_id": 1,
"status": "success",
"created_at": "2026-01-01T00:00:00+00:00"
}
]
}status is success, failed, timed_out, pending (a handoff or
notify email still queued) or confirmation_pending (a high-risk call
waiting for the visitor to confirm). A confirmed call is recorded as a new
run, and an unconfirmed request becomes skipped after two hours. The
arguments and request details of a run are shown in the dashboard, not on
/v1. Runs across a whole bot are at GET /v1/chatbots/{id}/action_runs,
including those of deleted actions (action_id: null).
Get told instead of asking
Actions are the point where the product reaches into your systems, so "did my tool run, and did it work" is the question you cannot answer any other way.
action.run.succeeded fires when a run succeeds and action.run.failed when
it ends any other way, whether it ended during the conversation turn or when
its queued email settled. The platform's own clean-up of abandoned runs fires
nothing. An action quietly failing for a week is a real failure mode; this is
how you avoid it.
High-risk actions
A call to a high-risk action runs only after the visitor confirms it in a
later message. Set is_high_risk to require that; an http_request with
POST, PUT, PATCH or DELETE, and an mcp action without
config.low_risk, always require it.
Handoff
The handoff action flags a conversation for a human and emails the
addresses in its targets. When it fires, the conversation gets
handoff_requested: true and a handoff_reason, and
conversation.handoff.requested is emitted once.
Wire that event to wherever your team actually looks. A handoff nobody sees is worse than no handoff, because the visitor has been told someone is coming.
MCP tools
If you already expose tools over MCP, point at the server and discover them rather than transcribing definitions by hand:
POST/v1/actions/mcp/discover
It fetches the server's tool list so you can register one mcp action per
tool you want. It answers 404 MCP_NOT_ENABLED where the platform has MCP
switched off. A token you send is used for that request only; store it in
the action's credentials.token.
Secrets
Actions can hold credentials for your own systems:
credentialsare write-only. They are stored encrypted and never returned;has_credentialstells you whether any are set. APATCHmerges the keys you send, and an empty value removes one.configis returned in full on every read, so keep secrets out of it — includingconfig.headers.- A run records a classified error code (
timeout,upstream_error, …), never your endpoint's raw error text, so a failing upstream cannot leak a token into run history.
Use a dedicated, narrowly-scoped credential for each action, and rotate it on
your side, then send the new value with PATCH /v1/actions/{id}.
Common mistakes
- A vague description. The commonest reason an action "never fires".
- A slow endpoint. The platform gives up after 8 seconds by default, and until then the visitor waits. Return quickly, even if that means returning "still working".
- Assuming it ran. Check the run status, or subscribe to the events.
- A token in
config.headers. It is returned on every read; usecredentials.
What's next
- Events — the action events and everything else.
- Conversations — see actions in context.
- Security — credential handling.