Let an agent carry out an action with a tool¶
At the end of this tutorial the agent support creates a ticket in Noordwind's ticket system when
a customer asks for one. You have registered that system's endpoint as an API connector, allowed
the host and tried the tool yourself first.
| Building blocks | Tools: API connector and allowed destinations. Agents, Keys, capabilities and policy |
| You need | a key with the capabilities agents and tooling; an HTTP endpoint that creates a ticket, with a token for it; an administrator of your organisation, or a key with management, for step 2; for the trace in the check, trace storage must be on for your organisation (Read a trace) |
| Builds on | Build your first agent. The search tool from the second tutorial is not needed, but the examples carry it along |
The examples use https://tickets.example.com/api/tickets as the endpoint. Replace it with the
address of your own system; the example assumes an API that expects a POST with subject and
description and answers 201 with the new ticket.
An API connector is the shortest way to an existing HTTP API: Core calls the endpoint with the model's arguments and keeps the token. When your own application must carry out the action, for example because it updates a screen, use a function tool.
1. Register the API connector¶
curl -s -X POST "$CORE_URL/api_connectors" \
-H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
-d '{"name": "create_ticket",
"http_url": "https://tickets.example.com/api/tickets",
"description": "Creates a support ticket. Use this only when the customer asks for a ticket or when you cannot solve the question yourself.",
"parameters": {"type": "object",
"properties": {"subject": {"type": "string", "description": "Short title of the problem"},
"description": {"type": "string", "description": "What the customer reports, in their own words"}},
"required": ["subject", "description"]},
"request": {"method": "POST",
"body": {"subject": "{subject}", "description": "{description}"}},
"auth": {"type": "bearer", "secret": "Bearer <the ticket system token>"}}'
The answer is 201 (abridged):
{
"id": "tool_3Lm8...",
"name": "create_ticket",
"version": 1,
"auth": { "type": "bearer", "header_name": "Authorization", "has_secret": true },
"egress": [{ "host": "tickets.example.com", "allowed": false, "blocked": false }]
}
Four choices in this request:
namebecomes the tool nameapi__create_ticketand does not change afterwards.descriptionandparametersare all the model knows about the tool. Write in the description when the tool is meant to be used, and give each argument a description of its own.requesttranslates the arguments into the body your API expects. Leaverequestout and Core sends a fixed body withtool,arguments,response_idandcall_id.authkeeps the token with the connector. The model and your application never see it, and no answer returns it. Abearersecret is sent literally, so the prefixBeareris part of it.
egress says the host is not allowed yet. You solve that in the next step.
2. Allow the host¶
An organisation reaches no host until it allows one. An administrator does that in the console:
open Settings, the tab Access & policy, and choose Add a host on the card Allowed
hosts. Enter tickets.example.com and choose Allow host.
With a key that has the capability management you do the same through the API:
curl -s -X POST "$CORE_URL/organisation/allowed-hosts" \
-H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
-d '{"host": "tickets.example.com"}'
{
"object": "allowed_hosts",
"everywhere": false,
"hosts": [{ "host": "tickets.example.com", "blocked": false }]
}
The list holds for the whole organisation: every key reaches the hosts on it. A key can narrow
that with rules of its own over Destinations and never widen it. That way the organisation,
and not one application, decides where its data may go. When you see blocked: true, your
installation blocks the host for everyone; ask your operator.
3. Try the tool yourself¶
curl -s -X POST "$CORE_URL/tools/api__create_ticket/call" \
-H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
-d '{"arguments": {"subject": "Test from Core", "description": "This ticket may be removed."}}'
{
"object": "tool_call",
"name": "api__create_ticket",
"status": "ok",
"error_code": null,
"text": "{\"id\": \"T-1042\", \"subject\": \"Test from Core\"}"
}
text is what a model would read; the content here is an example of what a ticket system answers.
This call creates a real ticket. You make it anyway, because it tests the connector, the token and
the host with no model in between. status is ok, denied, error or cancelled.
4. Give the agent the tool¶
curl -s -X POST "$CORE_URL/agents/$AGENT_ID/versions" \
-H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
-d '{"model": "<a model id>",
"instructions": "You are the support assistant of Noordwind. Answer briefly. For every question about policy, search the handbook first and refer to the passage with [n]. Create a ticket only when the customer asks for one or when you cannot solve the question. Then name the ticket number.",
"temperature": 0.2,
"tools": [{"type": "tool", "name": "knowledge__handbook"},
{"type": "tool", "name": "api__create_ticket"}]}'
If you skipped the second tutorial, leave out knowledge__handbook and the sentence about the
handbook. The instruction says when the agent may create a ticket. A tool that changes something
needs such a boundary: without that sentence a model creates a ticket sooner than you want.
5. Ask for a ticket¶
curl -s -X POST "$CORE_URL/responses" \
-H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
-d '{"agent": {"id": "support"},
"input": "My order 88213 arrived damaged. Will you make a ticket for this?"}'
Check that it works¶
The answer (abridged) shows the tool call and then the agent's text:
{
"id": "resp_0KQ2mR...",
"status": "completed",
"output": [
{ "type": "mcp_call", "name": "api__create_ticket", "server_label": "api",
"arguments": "{\"subject\": \"Damaged order 88213\", \"description\": \"...\"}",
"status": "completed", "output": "{\"id\": \"T-1043\", ...}" },
{ "type": "message", "role": "assistant",
"content": [{ "type": "output_text", "text": "I have created ticket T-1043 for you." }] }
]
}
The ticket is now in your ticket system too. The trace of the turn shows what Core ran and what the endpoint answered:
curl -s "$CORE_URL/responses/resp_0KQ2mR.../trace" -H "Authorization: Bearer $CORE_KEY"
{
"items": [
{ "id": "...", "round": 0, "status": "ok",
"tool_calls": [
{ "tool_name": "api__create_ticket", "source": "http", "status": "ok", "vendor_status": 201 }
] }
]
}
vendor_status is the HTTP status of your own endpoint. In the console the same turn is under
Traces (Read a trace).
If it does not work¶
| You see | Cause | Fix |
|---|---|---|
404 on POST /api_connectors |
the capability tooling is off on your key or in your organisation's ceiling |
ask your administrator to switch it on (Capabilities) |
422 when registering |
a {name} that is not in parameters.properties, or a credential header in request.headers |
declare the argument; put the token in auth (Send your own request) |
status: "denied" in step 3, or "Denied by policy." in the agent's answer |
the host is not allowed. In step 3 it can also be your key's policy refusing the tool; in a turn the model is not even offered such a tool | repeat step 2 and check egress on GET /api_connectors/{id}; read the rules over Tools with GET /key |
| the host is on your list and the call is still refused | an organisation above yours does not permit the host (egress then reads allowed: false), or it restricts regions the host is not served in |
ask the administrator of the organisation above to permit the host (Policy) |
"Tool endpoint answered 401." |
the endpoint refuses the token | set the right secret with PATCH /api_connectors/{id}; remember the prefix Bearer |
"Tool endpoint unreachable." |
a timeout or a network error | check the address; Core follows no redirects |
404 or 403 on POST /organisation/allowed-hosts |
your key has no management, or the organisation above keeps key management to itself |
have an administrator allow the host in the console |
A failed tool call does not fail the turn: the model gets the message and answers with it.
Read on¶
- Tools: every source of tools and when you choose which.
- API connectors in the console and Tools through the API: authentication, your own requests and versions.
- A person's own login: a tool that acts on behalf of the person the turn is for.
- Tool-result trust: why a tool result is never an instruction.
Next tutorial: Connect an external capability through MCP.