Make it yours
Let an assistant call your API
Give an assistant a tool that calls your own HTTPS endpoint mid-conversation, on calls and in chat, and build the endpoint that answers it.
A tool is something the assistant can do while it talks: look up an order, check a date is free, create a ticket. You describe the tool and where to send the request; when the model decides it needs it, the platform calls your endpoint from our servers and hands your answer back to the model. Your URL and any secret headers never leave our servers.
The same tools work on phone calls, browser calls, web chat and WhatsApp.
Describe the tool
Tools live in the assistant's config.tools.definitions. Each one has what the model sees (name, description, parameters) and where the platform sends it (request):
{
"definitions": [
{
"name": "check_availability",
"description": "Check whether a trip can be booked for the given destination and month. Call it before promising dates.",
"parameters": {
"type": "object",
"properties": {
"destination": { "type": "string", "description": "City name, as the visitor said it" },
"month": { "type": "string", "enum": ["2026-05", "2026-06", "2026-07"] },
"travellers": { "type": "integer" }
},
"required": ["destination", "month"],
"additionalProperties": false
},
"request": {
"url": "https://tools.acmetravel.com/availability",
"method": "POST",
"headers": { "Authorization": "Bearer YOUR_TOOL_SECRET" },
"timeout_ms": 8000
}
}
]
}| Field | Meaning |
|---|---|
name | How the model calls it. Letters, digits and underscores read best. |
description | When to use it. The model decides from this, so say when, not only what. |
parameters | A JSON Schema object for the arguments. |
request.url | Your endpoint. Required. HTTPS on a public hostname. |
request.method | POST (the default), GET, PUT, PATCH or DELETE. |
request.headers | Headers sent as they are. Put your own authentication here. |
request.timeout_ms | How long to wait, in milliseconds. Defaults to 15,000. The platform never waits more than 30,000, whatever you set. |
Save it as part of the assistant's config, on create or with PATCH/api/assistants/{id}. A config you send replaces the stored one, so send the whole config. The tool's request, which the API never returns, is kept when you send the tool back under the same name.
What your endpoint receives
When the model calls check_availability({ "destination": "Lisbon", "month": "2026-05" }), your endpoint receives:
POST /availability HTTP/1.1
Host: tools.acmetravel.com
content-type: application/json
Authorization: Bearer YOUR_TOOL_SECRET
x-mlf-assistant-id: asst_7d2e9b14c6a04f3e8b5d1a9c0e7f6b23
x-mlf-project-id: proj_2f9c41d0a8b34e6f9d1c7a5b3e8f0a12
x-mlf-conversation-id: conv_0c8e5f2a9b7d4c1e86f3a0d2b4c6e8f1
{"destination":"Lisbon","month":"2026-05"}- The body is the arguments object, as JSON, with nothing wrapped around it.
- On a call,
x-mlf-call-idis sent instead ofx-mlf-conversation-id. Use either to look up the call or conversation with the API. - A
GETtool sends no body, and the arguments are not added to the URL. UsePOSTunless your endpoint takes no input. - The platform adds no signature. Check the header you set in
request.headers.
Before calling you, the platform checks the arguments against parameters: the type, required properties, each property's type, enum, maxLength, maxItems, and unknown properties when additionalProperties is false. Arguments that fail are not sent; the model is told what was wrong and can try again.
What to send back
Reply with a 2xx and the result as JSON, or as plain text. The model receives it as the tool's result, so write it for the model: short, specific, with the facts it needs to answer.
{ "available": true, "price_from": 640, "currency": "EUR", "note": "Two seats left on 14 May." }| Your endpoint | What the model gets |
|---|---|
| 2xx with JSON | The parsed JSON. |
| 2xx with text or an empty body | The text, or nothing. |
| 3xx | A failure: redirects are not followed. |
| 4xx or 5xx | A failure saying the endpoint returned that status. Your body is not passed on. |
| No answer within the timeout | A failure. |
To tell the model something went wrong in a way it can explain to the visitor, return a 2xx with that information ({ "available": false, "reason": "Fully booked in May" }) rather than an error status. Responses over 1 MB are refused.
A minimal endpoint
import { createServer } from 'node:http'
const SECRET = process.env.TOOL_SECRET
createServer(async (req, res) => {
if (req.headers.authorization !== `Bearer ${SECRET}`) {
res.writeHead(401).end()
return
}
let raw = ''
for await (const chunk of req) raw += chunk
const { destination, month } = JSON.parse(raw || '{}')
// look it up in your own system
const available = destination === 'Lisbon' && month === '2026-05'
res.writeHead(200, { 'content-type': 'application/json' })
res.end(JSON.stringify(available ? { available, price_from: 640, currency: 'EUR' } : { available, reason: 'No seats that month' }))
}).listen(8080)Put it behind HTTPS on a public hostname. Then test it from a chat before you put it in front of visitors: the quickest way is the quickstart's chat requests, asking something that needs the tool.
Rules that keep it safe
- The URL must be HTTPS, with no username or password in it. Hostnames such as
localhostand literal private, loopback and link-local addresses are refused. - The URL is checked each time the tool runs, not when you save the assistant. A bad URL shows up as a tool failure in the conversation, so try every tool once after you add it.
- On a call, the person waits in silence while your endpoint works. Keep tools that run on calls well under a few seconds, and set
timeout_msto what you can promise.