welldun. offhook

Core resources

Tools

Tools let your agent fetch live data from your own systems in the middle of a call, such as checking an order or reading a booking. You describe the function; Offhook calls your endpoint with the model's arguments and gives the answer back to the model.

How it works

  1. You define a tool: a name, a description that tells the model when to use it, the arguments it takes, and an HTTP endpoint.
  2. You give the tool to an agent through tool_ids.
  3. During a call, when the model decides it needs that data, it calls the tool. Offhook sends the request to your endpoint with the model's arguments and passes your JSON response back to the model, which carries on talking.

Your code, your servers

Offhook doesn't run your code; it calls it. To give the agent a database lookup, a calculation or another API, put that logic behind a small HTTP endpoint on your side and point a tool at it. Your code runs with your own dependencies and secrets, and Offhook only sends the arguments and reads the result.

# Your own server: FastAPI, Flask, a Lambda, a Cloud Function, anything.
from fastapi import FastAPI

app = FastAPI()

@app.get("/tools/orders/{order_id}")
def check_order_status(order_id: str):
    order = db.query(Order).get(order_id)   # your code, your DB, your deps
    if order is None:
        return {"found": False}
    return {
        "found": True,
        "status": order.status,
        "items": order.item_count,
        "delivery_date": str(order.delivery_date),
    }

Two rules for the endpoint:

  • It must be public HTTPS. Requests to private, loopback and cloud-metadata addresses are blocked. During local development, expose your endpoint with a tunnel such as ngrok and use that URL.
  • It must answer within timeout_seconds. For slow work, start it in the background and return straight away, then report the result later, for example through a webhook.

Create a tool

POST /v1/tools

Body parameters

FieldTypeDescription
namerequired string The function name the model calls: letters, digits and underscores, e.g. check_order_status. Must be unique within your tenant. record_result and end_call are reserved.
descriptionrequired string What the tool does and when to use it. This is what the model reads to decide whether to call it.
parametersoptional object The arguments, in JSON Schema style: { "properties": { … }, "required": [ … ] }.
methodoptional string GET (the default) or POST.
urlrequired string Your HTTPS endpoint. Write {name} in the path to insert an argument there.
headersoptional object Fixed headers, such as Authorization. Stored encrypted and never returned.
timeout_secondsoptional int 1 to 20. The default is 10.
curl -X POST http://localhost:8000/v1/tools \
  -H "Authorization: Bearer $OFFHOOK_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "check_order_status",
    "description": "Look up an order by its ID and return its status, items, and delivery date.",
    "method": "GET",
    "url": "https://api.yourapp.com/orders/{order_id}",
    "parameters": {
      "properties": {
        "order_id": { "type": "string", "description": "The order ID, e.g. ORD-1042" }
      },
      "required": ["order_id"]
    },
    "headers": { "Authorization": "Bearer sk_live_…" },
    "timeout_seconds": 8
  }'

How arguments become a request

The model's arguments only ever fill in the request. They can't change the host, scheme, method or headers.

  • Path: each {name} in the URL is replaced with that argument, URL-encoded.
  • GET: the other arguments become the query string.
  • POST: the other arguments become the JSON body.

So check_order_status(order_id="ORD-1042") sends GET https://api.yourapp.com/orders/ORD-1042 with your Authorization header.

Give a tool to an agent

Pass tool_ids when you create the agent. It can then use any of those tools during a call.

cURL
curl -X POST http://localhost:8000/v1/agents \
  -H "Authorization: Bearer $OFFHOOK_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Order support",
    "instructions": "Help the caller with questions about their order. Use check_order_status to look up specifics.",
    "stt_connection_id": "<stt-connection-id>",
    "tts_connection_id": "<tts-connection-id>",
    "llm_connection_id": "<llm-connection-id>",
    "tool_ids": ["c4e81b2f-9a3d-4c6e-8f1b-7d2a5e9c3b10"]
  }'

List, retrieve, update and delete

GET /v1/tools
GET /v1/tools/{id}
PATCH /v1/tools/{id}
DELETE /v1/tools/{id}

Header values are never returned. has_auth tells you whether any are set.

What the model gets back

  • Success: { "ok": true, "result": <your JSON> }. Very large responses are shortened.
  • Your endpoint returned an error status: { "error": "endpoint returned HTTP 404", "body": … }.
  • Timeout, blocked URL or missing argument: { "error": "…" }.

In every error case the call carries on, so the agent can apologise and move on.