BrightshoalDocsOpen console
MenuLet an assistant call your API

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):

config.tools
{
  "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
      }
    }
  ]
}
FieldMeaning
nameHow the model calls it. Letters, digits and underscores read best.
descriptionWhen to use it. The model decides from this, so say when, not only what.
parametersA JSON Schema object for the arguments.
request.urlYour endpoint. Required. HTTPS on a public hostname.
request.methodPOST (the default), GET, PUT, PATCH or DELETE.
request.headersHeaders sent as they are. Put your own authentication here.
request.timeout_msHow 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:

Request
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-id is sent instead of x-mlf-conversation-id. Use either to look up the call or conversation with the API.
  • A GET tool sends no body, and the arguments are not added to the URL. Use POST unless 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.

Response
{ "available": true, "price_from": 640, "currency": "EUR", "note": "Two seats left on 14 May." }
Your endpointWhat the model gets
2xx with JSONThe parsed JSON.
2xx with text or an empty bodyThe text, or nothing.
3xxA failure: redirects are not followed.
4xx or 5xxA failure saying the endpoint returned that status. Your body is not passed on.
No answer within the timeoutA 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 localhost and 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_ms to what you can promise.

Stuck on let an assistant call your api?