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_BASE and RESIA_API_KEY set.
  • A public HTTPS endpoint that answers a POST within 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"}
}
  • arguments always match the tool's argument schema. Resia refuses other arguments before it sends anything.
  • source names the call, chat, or orchestration run. Read it with the matching GET route when you need its details.
  • A repeated tool_call_id is 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."}}
  • result can be any JSON value, also null. The agent reads it as data, never as instructions.
  • error.message is required and cannot be blank. The agent reads at most its first 4,096 characters. Never put a credential in it.
  • error.kind is optional. Resia ignores it and reports every error from your endpoint as endpoint_error.
  • Send result or error, not both, and no other key. A result of null beside an error counts 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 2xx reaches 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. turn is the index of the last transcript line before the event. null means before the first line.
  • Built-in tools, such as end_chat, also appear.
  • Fields named secret, token, api_key, endpoint_url, authorization, or headers show as [redacted], at any depth. Text that looks like JSON but does not parse cleanly is withheld: payload_text is null and payload_state is redacted.
  • Each payload stops at 64 KiB, and payload_state then says truncated. 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_truncated is true.
  • occurred_at is the time the event happened. It is null for an event that Resia saved before it began to record these times.
  • Without the option, tool_activity is null. The chat.ended webhook 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, and wait_for_orchestration.
  • turn is always null, because a run has no transcript.
  • If the activity cannot be read now, tool_activity is an empty list and tool_activity_truncated is true. 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 GET and PUT return 404. 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

  • 409 on create: another tool of your organization has the name, now or before it was archived. Choose a new name.
  • 422 on 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, or run_manager as 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.
  • 422 on an agent update: tools names a tool that does not exist or is archived.
  • 422 on a call: the Call Agent's model_architecture is speech_to_speech, and its version names tools.
  • 409 on 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 current tools list or the version that now runs names the tool.

Next steps