Build
Tools
A tool is an HTTP endpoint of yours that an agent can call. With tools an agent can look up an order, check a calendar or book a slot while it is still on the line.
Two kinds of tool
| Kind | When it runs | Use it for |
|---|---|---|
during_call | When the model decides to, in the middle of the conversation | Looking something up or doing something the caller has just asked for. |
before_call | Once, as the call starts | Fetching what the agent should know before it says hello: the caller’s profile, open orders, account status. |
Create a tool
A tool belongs to one agent. Name it as you would a function; the name is how the model asks for it. The description is what the model reads to decide when to use it, so say plainly what it does and when.
curl -X POST "https://goblins-call-api.onrender.com/v1/tools" \
-H "Authorization: Bearer $CRACKLE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agentId": "ua_3f9a1c2b",
"name": "check_order_status",
"description": "Looks up an order by its number, which the caller reads out. Use it when the caller asks where their order is.",
"kind": "during_call",
"method": "GET",
"url": "https://your-app.com/api/orders/{{order_id}}",
"params": [
{
"key": "order_id",
"value": "A-1042"
}
],
"auth": "bearer",
"authToken": "YOUR_ENDPOINT_TOKEN",
"timeoutMs": 5000
}'Then switch it on for the agent: add the tool’s ID to the agent’s toolIds, or mention it in the instructions as @check_order_status. Either is enough.
How arguments reach your endpoint
Each entry in params is an argument the model fills in from the conversation. Its value is an example, shown to the model so it knows what the argument looks like; it is also what is sent if the model leaves the argument out. Say what each argument means in the tool’s description. When the tool runs:
{{order_id}}in the URL, in a header or in thebodytemplate is replaced with the argument.- For a
GET, every param is also added to the query string. - For a
POSTwith nobodytemplate, the params are sent as a JSON object. With a template, the template is sent, with its placeholders filled in.
An endpoint for the tool above could look like this:
import express from 'express';
const app = express();
// GET /api/orders/A-1042, called by the agent in the middle of a call
app.get('/api/orders/:orderId', async (req, res) => {
if (req.get('authorization') !== `Bearer ${process.env.TOOL_TOKEN}`) return res.sendStatus(401);
// A test call from the studio has no real caller: answer with sample data and save nothing.
if (req.get('x-goblin-test-call') === 'true') {
return res.json({ status: 'out for delivery', eta: 'today, 4 to 6 pm' });
}
const order = await findOrder(req.params.orderId);
if (!order) return res.json({ found: false });
res.json({ found: true, status: order.status, eta: order.eta });
});What to send back
Answer with JSON. The model reads your response and carries on its reply from it, so return the few facts worth saying and name them clearly. The model is shown about the first 1,800 characters; anything beyond that is cut off.
The caller is waiting while your endpoint works. The agent says a short holding line, such as “one second, let me check”, and then runs the tool. A request is given timeoutMs to answer, 30 seconds at the very most. If it fails or runs out of time, the agent tells the caller it could not check just now and carries on; it does not make up a result.
Authenticating to your endpoint
| `auth` | What is sent |
|---|---|
none | Nothing extra. |
bearer | Authorization: Bearer <authToken> |
api_key | <authToken> in the header named by authKeyHeader (X-API-Key unless set). |
basic | Authorization: Basic … built from authUsername and authToken. |
Credentials are stored encrypted and are never shown to the model. Tool URLs must be public http(s) addresses, and redirects are not followed.
Trusting what reaches your endpoint
Arguments written by the model come from what the caller said, so treat them as you would anything typed into a form. To know for certain which call a request belongs to, put {{call_id}} in the URL, a header or the body. It is filled in from the call itself, as are {{agent_id}} and the variables the call was started with, and none of them can be overridden by the model.
{
"name": "get_my_appointments",
"description": "Lists the caller's upcoming appointments.",
"method": "GET",
"url": "https://your-app.com/api/appointments?call={{call_id}}&customer={{customer_id}}"
}Test calls
A request that is not made for a real caller carries the header X-Goblin-Test-Call: true. That covers test calls started in the studio and Test a tool. Such a call has none of the variables your server sends with a real one, so an endpoint that needs to know who is calling can answer with sample data and save nothing. Only the platform can set this header.
Before-call tools
A before_call tool runs once when the call is started, with the call’s variables as its arguments. Whatever it returns is added to the agent’s instructions for that call, so the agent already knows it when it greets the caller. The call is not set up until these tools have answered, so keep them quick.
Built-in tools
Two actions need no endpoint. Switch them on with the agent’s systemTools:
endCallbooleanThe agent may hang up once the conversation is finished.
changeLanguagebooleanThe agent may switch the language it speaks between Hindi and English when the caller does.
curl -X PATCH "https://goblins-call-api.onrender.com/v1/agents/ua_3f9a1c2b" \
-H "Authorization: Bearer $CRACKLE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"systemTools": {
"endCall": true,
"changeLanguage": true
}
}'