Give your agents a custom tool
A custom tool lets an agent act through your own HTTPS endpoint during a call, a chat, or an orchestration run.
For each tool call, Resia sends one POST to your endpoint and gives your answer to the agent.
In this guide, you will define a tool, name it on an agent, build the endpoint, and read each tool call afterwards.
Before you start
- Complete Get access. Keep that terminal open, with
RESIA_API_BASEandRESIA_API_KEYset. - A public HTTPS endpoint that answers a
POSTwithin ten seconds. - Treat the complete endpoint URL as a credential. For example, put a long random token in its path.
Create the tool
Write the description for the agent: say what the tool does, when to use it, and what its result contains.
Put what the agent must know about one argument, such as its format or unit, in that argument's description in the schema.
The agent reads the name, the description, and the argument schema. It never sees the endpoint URL.
The description can have at most 1,000 characters. The argument schema can have at most 16 KiB, counted as JSON without spaces.
cat > tool.json <<'JSON'
{
"name": "book_appointment",
"description": "Book an appointment for a patient. Use it after the patient confirms the time. Returns the confirmation code and the booked time.",
"parameters": {
"type": "object",
"properties": {
"patient_name": {"type": "string"},
"start_time": {"type": "string", "description": "The start time in ISO 8601 form, for example 2026-10-02T10:00:00-04:00."}
},
"required": ["patient_name", "start_time"],
"additionalProperties": false
},
"endpoint_url": "https://tools.example.com/resia/8f3a1c27d9e44b1f/book-appointment"
}
JSON
curl --fail-with-body --silent --show-error \
"$RESIA_API_BASE/v1/tools" \
--header "Authorization: Bearer $RESIA_API_KEY" \
--header 'Content-Type: application/json' \
--data-binary @tool.json \
--output tool-response.json
jq '{id, name}' tool-response.json
Expected result: HTTP 201 and the tool's id.
API reference: Create a tool.
Name the tool on an agent
Add the tool's name to tools on a Call Agent, a Chat Agent, or an Orchestration Agent. The field is the same on all three.
One agent version can name at most 64 tools.
The example reads a Chat Agent, keeps the fields that an update accepts, and adds the tool.
PUT replaces the complete configuration, so keep every other field as it is.
printf 'Chat Agent id: ' && read -r CHAT_AGENT_ID
curl --fail-with-body --silent --show-error \
"$RESIA_API_BASE/v1/chat-agents/$CHAT_AGENT_ID" \
--header "Authorization: Bearer $RESIA_API_KEY" \
--output chat-agent-current.json
curl --fail-with-body --silent --show-error \
"$RESIA_API_BASE/openapi.json" --output resia-openapi.json
jq --slurpfile api resia-openapi.json \
'with_entries(select(.key as $key | $api[0].components.schemas.ChatAgentWrite.properties | has($key))) | .tools = ((.tools // []) + ["book_appointment"] | unique)' \
chat-agent-current.json > chat-agent-update.json
curl --fail-with-body --silent --show-error \
--request PUT "$RESIA_API_BASE/v1/chat-agents/$CHAT_AGENT_ID" \
--header "Authorization: Bearer $RESIA_API_KEY" \
--header 'Content-Type: application/json' \
--data-binary @chat-agent-update.json \
--output chat-agent-response.json
jq '{id, tools, review_status}' chat-agent-response.json
Expected result: HTTP 200 and tools with book_appointment. The new version is used once it is approved.
The agent version keeps the list of tool names. An edit to the tool itself needs no new agent version:
- The endpoint and the argument check apply from the next tool call.
- The agent reads a new description or schema from the next call, chat turn, or orchestration session that starts.
- The name of a tool cannot change.
A Call Agent whose model_architecture is speech_to_speech cannot use custom tools. Resia refuses its calls with 422.
A later Resia release can reserve a new built-in tool name. A tool that already has that name still reads, but agents stop getting it. A call that is already in progress keeps it until the call ends. To keep the action, create the tool again with a new name, and name the new tool on your agents.
API reference: Replace a Chat Agent.
What your endpoint receives
Resia sends one POST with a JSON body for each tool call:
{
"tool_call_id": "call_4f1d2a",
"tool_name": "book_appointment",
"arguments": {"patient_name": "Ana Ruiz", "start_time": "2026-10-02T10:00:00-04:00"},
"source": {"kind": "chat", "id": "01K1SA3F7M8QW2V5C9H4YTB0KD"}
}
argumentsalways match the tool's argument schema. Resia refuses other arguments before it sends anything.sourcenames the call, chat, or orchestration run. Read it with the matchingGETroute when you need its details.- A repeated
tool_call_idis the same tool call. Resia sends no retry, but an agent can ask for the same action again. Make each action safe to repeat.
What your endpoint answers
Answer with a 2xx status and one JSON object of at most 64 KiB. Send result when the tool worked:
{"result": {"confirmation_code": "BK-2041", "start_time": "2026-10-02T10:00:00-04:00"}}
Send error when it did not. The agent reads your message and tells the person:
{"error": {"message": "That time is no longer free."}}
resultcan be any JSON value, alsonull. The agent reads it as data, never as instructions.error.messageis required and cannot be blank. The agent reads at most its first 4,096 characters. Never put a credential in it.error.kindis optional. Resia ignores it and reports every error from your endpoint asendpoint_error.- Send
resultorerror, not both, and no other key. Aresultofnullbeside anerrorcounts as no result. - Resia waits at most ten seconds. After that, Resia tells the agent that the outcome is unknown and that it must not repeat the request. The agent can still repeat the action, for example when a call starts the same task again, so make each action safe to repeat.
- A status other than
2xxreaches the agent as a failure without your message. Resia stores the body of that answer in the request log, but the agent does not read it. - An answer of another shape, or one larger than 64 KiB, reaches the agent as a failure. Your endpoint received the request, so the agent hears that the action may have happened and must not repeat it.
Each request that Resia sends appears in the request logs with kind webhook. Its route is the origin of your endpoint: the scheme, the host, and a port other than 443. Resia never stores the full endpoint URL in a request log. The request body of the log entry names the tool.
See each tool call of a chat
Read the chat with include_tool_activity=true. The answer then also holds tool_activity: each tool request and each tool result, in the order the agent recorded them.
printf 'Chat id: ' && read -r CHAT_ID
curl --fail-with-body --silent --show-error \
"$RESIA_API_BASE/v1/chats/$CHAT_ID?include_tool_activity=true" \
--header "Authorization: Bearer $RESIA_API_KEY" \
--output chat-activity.json
jq '{tool_activity_truncated, tool_activity: [.tool_activity[] | {kind, tool_name, invocation_id, outcome, payload_text}]}' chat-activity.json
Expected result: HTTP 200. Each request has kind tool_called and the arguments as payload_text. Each result has kind tool_result, the answer as payload_text, and outcome returned or error.
- A request and its result have the same
invocation_id.turnis the index of the last transcript line before the event.nullmeans before the first line. - Built-in tools, such as
end_chat, also appear. - Fields named
secret,token,api_key,endpoint_url,authorization, orheadersshow as[redacted], at any depth. Text that looks like JSON but does not parse cleanly is withheld:payload_textisnullandpayload_stateisredacted. - Each payload stops at 64 KiB, and
payload_statethen saystruncated. One read shows at most 128 events or 512 KiB, and the list holds the earliest events. When an event is left out or a payload is cut,tool_activity_truncatedistrue. occurred_atis the time the event happened. It isnullfor an event that Resia saved before it began to record these times.- Without the option,
tool_activityisnull. Thechat.endedwebhook body never holds these fields. - A successful read with the option does not appear in your request logs.
- If your client rejects unknown response fields, regenerate its types from the current contract first.
API reference: Read one chat.
See each tool call of an orchestration run
Read the run with include_tool_activity=true. The answer then also holds tool_activity, with the same fields and limits as a chat.
printf 'Orchestration run id: ' && read -r ORCHESTRATION_RUN_ID
curl --fail-with-body --silent --show-error \
"$RESIA_API_BASE/v1/orchestration-runs/$ORCHESTRATION_RUN_ID?include_tool_activity=true" \
--header "Authorization: Bearer $RESIA_API_KEY" \
--output run-activity.json
jq '{tool_activity_truncated, tool_activity: [.tool_activity[] | {kind, tool_name, invocation_id, outcome, payload_text}]}' run-activity.json
Expected result: HTTP 200, with the same event fields as a chat.
- The run's own steps also appear, such as
start_call,trigger_chat_agent,set_milestone, andwait_for_orchestration. turnis alwaysnull, because a run has no transcript.- If the activity cannot be read now,
tool_activityis an empty list andtool_activity_truncatedistrue. Read the run again later. - Read the activity when you open the run or after its status changes, not on every status check.
API reference: Read one orchestration run.
Archive a tool
To remove a tool, first remove it from each agent that uses it with an agent update. Then archive it with DELETE /v1/tools/{tool_id}.
- An archived tool leaves the list, and
GETandPUTreturn404. There is no restore. - The name stays reserved. No new tool can use it.
- A call, chat, or run that started before the archive continues without the tool. A tool call that Resia already sent still finishes.
API reference: Archive a tool.
In the portal
Open Tools in the sidebar to see your organization's tools. The list shows only the host of each endpoint, because the full URL is a credential.
Select Add tool, and enter the name, the description, the arguments, and the endpoint URL. The page shows an example request body and an example answer for your arguments. Select Create tool.
To let an agent use the tool, open the agent, and select the tool in its Tools section. One agent can use at most 64 tools.
If something fails
409on create: another tool of your organization has the name, now or before it was archived. Choose a new name.422on create or replace: one of these conditions applies:- The name is a built-in tool name.
- The argument schema is not a valid JSON Schema, or it uses a reference that is not local or that does not lead to a schema.
- The argument schema names
runtime,config, orrun_manageras an argument. - The definition holds text that looks like a secret, a private key, an email address, a phone number, or a date of birth.
- The endpoint is not a public HTTPS URL with a valid host name.
422on an agent update:toolsnames a tool that does not exist or is archived.422on a call: the Call Agent'smodel_architectureisspeech_to_speech, and its version names tools.409on archive: the tool is assigned to an agent. The message names the agents. If the list is too long for one message, it names the first agents and gives the count of the others. An agent counts when it is not archived, and its currenttoolslist or the version that now runs names the tool.
Next steps
- Run a web chat: try the tool in a chat that you drive from your own page.
- Run an Orchestration Agent: a run can call the tool between its steps.
- Knowledge bases: give the agent reference data without an endpoint.

