> ## Documentation Index
> Fetch the complete documentation index at: https://docs.truedy.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom Tools

> Build your own tools — connect your agents to any API or internal system

# Custom Tools

Custom tools let you extend your agents with any capability your system can expose over HTTP. When the agent decides it needs to call your tool, Truedy sends a POST (or GET) request to your endpoint, your server responds with JSON, and the agent uses that data to continue the conversation.

**Build a custom tool when you need to:**

* Look up a customer account by phone number or email
* Check order status, shipping information, or account balance
* Verify identity or eligibility
* Create a support ticket, send a notification, or trigger any internal workflow
* Do anything not covered by the built-in integrations

## Tool definition fields

Every tool is created with a `name` and a `definition` object. The definition describes your endpoint and its parameters.

**Top-level request body:**

| Field        | Type   | Description                                                                     |
| ------------ | ------ | ------------------------------------------------------------------------------- |
| `name`       | string | Human-readable display name (e.g. `"Look Up Customer"`). Used in the dashboard. |
| `definition` | object | Full tool specification — see below.                                            |

**`definition` fields:**

| Field                 | Type   | Description                                                                                                                              |
| --------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `description`         | string | Plain-English description of what the tool does and **when to call it**. This is what the AI reads to decide whether to invoke the tool. |
| `http.baseUrlPattern` | string | Your HTTPS endpoint URL, e.g. `"https://api.example.com/lookup"`.                                                                        |
| `http.httpMethod`     | string | HTTP method: `"POST"`, `"GET"`, `"PUT"`, `"PATCH"`, or `"DELETE"`. Use `POST` for operations that send parameters in the body.           |
| `dynamicParameters`   | array  | Parameters the AI extracts from the conversation and sends with each call.                                                               |
| `staticParameters`    | array  | Fixed parameters always sent (e.g. an auth header). The AI never changes these.                                                          |

**`dynamicParameters` / `staticParameters` item shape:**

| Field      | Type    | Description                                                                                                  |
| ---------- | ------- | ------------------------------------------------------------------------------------------------------------ |
| `name`     | string  | Parameter name                                                                                               |
| `location` | string  | Where to put it: `"PARAMETER_LOCATION_BODY"`, `"PARAMETER_LOCATION_QUERY"`, or `"PARAMETER_LOCATION_HEADER"` |
| `schema`   | object  | JSON Schema for the parameter (for `dynamicParameters`)                                                      |
| `value`    | string  | Fixed value (for `staticParameters` only)                                                                    |
| `required` | boolean | Whether this parameter is required                                                                           |

<Warning>
  Never embed API keys or secrets in `http.baseUrlPattern` as query parameters. Use a `staticParameters` entry with `location: "PARAMETER_LOCATION_HEADER"` instead, so credentials are not logged in request URLs.
</Warning>

## Full workflow

<Steps>
  <Step title="Build your webhook endpoint">
    Create an HTTPS endpoint on your server that accepts POST requests and returns JSON. See the example implementations below.
  </Step>

  <Step title="Define the tool in Truedy">
    Create the tool in the dashboard (**Tools → New Tool → Custom HTTP**) or via the API. Provide the name, description, URL, method, and parameters schema.
  </Step>

  <Step title="Attach the tool to an agent">
    Go to your agent's **Tools** tab and enable the custom tool, or use the `PATCH /agents/{id}` endpoint.
  </Step>

  <Step title="Test with a live call">
    Use the dashboard's **Test Agent** panel to start a test call. Trigger the tool by speaking the scenario it is designed for, and verify your endpoint receives the request and the agent responds correctly.
  </Step>
</Steps>

## Example tool definition

This tool looks up a customer's account status by phone number:

```json theme={null}
{
  "name": "Look Up Customer",
  "definition": {
    "description": "Look up a customer's account status and last order by their phone number. Call this tool when the caller asks about their account, order history, or current status, and you have their phone number.",
    "http": {
      "baseUrlPattern": "https://your-server.com/truedy-tools/lookup",
      "httpMethod": "POST"
    },
    "dynamicParameters": [
      {
        "name": "phone_number",
        "location": "PARAMETER_LOCATION_BODY",
        "schema": {
          "type": "string",
          "description": "Customer's phone number in E.164 format, e.g. +14155551234"
        },
        "required": true
      },
      {
        "name": "lookup_type",
        "location": "PARAMETER_LOCATION_BODY",
        "schema": {
          "type": "string",
          "enum": ["account", "orders"],
          "description": "What to look up — account details or order history"
        }
      }
    ],
    "staticParameters": [
      {
        "name": "x-truedy-secret",
        "location": "PARAMETER_LOCATION_HEADER",
        "value": "your-shared-secret"
      }
    ]
  }
}
```

## Webhook endpoint examples

<CodeGroup>
  ```javascript Node.js (Express) theme={null}
  const express = require('express');
  const app = express();
  app.use(express.json());

  app.post('/truedy-tools/lookup', (req, res) => {
    // Validate the Truedy shared secret
    const secret = req.headers['x-truedy-secret'];
    if (secret !== process.env.TRUEDY_TOOL_SECRET) {
      return res.status(401).json({ success: false, error: 'Unauthorized' });
    }

    const { phone_number, lookup_type = 'account' } = req.body;

    if (!phone_number) {
      return res.status(400).json({ success: false, error: 'phone_number is required' });
    }

    // Your database or API call here
    const customer = db.findByPhone(phone_number);

    if (!customer) {
      return res.json({
        success: true,
        data: { found: false, message: 'No account found for this number' }
      });
    }

    return res.json({
      success: true,
      data: {
        found: true,
        name: customer.name,
        account_status: customer.status,
        last_order: lookup_type === 'orders' ? customer.lastOrder : undefined
      }
    });
  });

  app.listen(3000);
  ```

  ```python Python (FastAPI) theme={null}
  from fastapi import FastAPI, Header, HTTPException
  from pydantic import BaseModel
  from typing import Optional
  import os

  app = FastAPI()

  class LookupRequest(BaseModel):
      phone_number: str
      lookup_type: Optional[str] = "account"

  @app.post("/truedy-tools/lookup")
  async def lookup_customer(
      payload: LookupRequest,
      x_truedy_secret: Optional[str] = Header(None)
  ):
      # Validate the Truedy shared secret
      if x_truedy_secret != os.environ.get("TRUEDY_TOOL_SECRET"):
          raise HTTPException(status_code=401, detail="Unauthorized")

      # Your database or API call here
      customer = db.find_by_phone(payload.phone_number)

      if not customer:
          return {
              "success": True,
              "data": {"found": False, "message": "No account found for this number"}
          }

      return {
          "success": True,
          "data": {
              "found": True,
              "name": customer["name"],
              "account_status": customer["status"],
              "last_order": customer["last_order"] if payload.lookup_type == "orders" else None
          }
      }
  ```
</CodeGroup>

## Create the tool via API

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.truedy.ai/api/public/v1/tools \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "name": "Look Up Customer",
      "definition": {
        "description": "Look up a customer account by phone number. Call when the caller asks about their account status or orders.",
        "http": {
          "baseUrlPattern": "https://your-server.com/truedy-tools/lookup",
          "httpMethod": "POST"
        },
        "dynamicParameters": [
          {
            "name": "phone_number",
            "location": "PARAMETER_LOCATION_BODY",
            "schema": { "type": "string", "description": "Caller phone number" },
            "required": true
          },
          {
            "name": "lookup_type",
            "location": "PARAMETER_LOCATION_BODY",
            "schema": { "type": "string", "enum": ["account", "orders"] }
          }
        ],
        "staticParameters": [
          {
            "name": "x-truedy-secret",
            "location": "PARAMETER_LOCATION_HEADER",
            "value": "YOUR_SHARED_SECRET"
          }
        ]
      }
    }'
  ```

  ```python Python theme={null}
  import requests

  tool = requests.post(
      "https://api.truedy.ai/api/public/v1/tools",
      headers={"Authorization": "Bearer YOUR_API_KEY"},
      json={
          "name": "Look Up Customer",
          "definition": {
              "description": "Look up a customer account by phone number. Call when the caller asks about their account status or orders.",
              "http": {
                  "baseUrlPattern": "https://your-server.com/truedy-tools/lookup",
                  "httpMethod": "POST",
              },
              "dynamicParameters": [
                  {
                      "name": "phone_number",
                      "location": "PARAMETER_LOCATION_BODY",
                      "schema": {"type": "string", "description": "Caller phone number"},
                      "required": True,
                  },
                  {
                      "name": "lookup_type",
                      "location": "PARAMETER_LOCATION_BODY",
                      "schema": {"type": "string", "enum": ["account", "orders"]},
                  },
              ],
              "staticParameters": [
                  {
                      "name": "x-truedy-secret",
                      "location": "PARAMETER_LOCATION_HEADER",
                      "value": "YOUR_SHARED_SECRET",
                  }
              ],
          },
      },
  ).json()

  tool_id = tool["data"]["id"]
  print(f"Created tool: {tool_id}")
  ```
</CodeGroup>

## Attach to an agent

Once the tool is created, attach it to an agent:

<CodeGroup>
  ```bash cURL theme={null}
  curl -X PATCH https://api.truedy.ai/api/public/v1/agents/{agent_id} \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "tools": ["TOOL_ID"]
    }'
  ```

  ```python Python theme={null}
  import requests

  requests.patch(
      f"https://api.truedy.ai/api/public/v1/agents/{agent_id}",
      headers={"Authorization": "Bearer YOUR_API_KEY"},
      json={"tools": [tool_id]}
  )
  ```
</CodeGroup>

<Note>
  The `tools` array replaces the agent's current tool list. To add a tool without removing existing ones, fetch the agent's current tools first and include all IDs in the array.
</Note>

## Tool response format

Return JSON from your endpoint. The agent reads the entire response body, so structure it to be clear and self-explanatory.

**Recommended response structure:**

```json theme={null}
{
  "success": true,
  "data": {
    "name": "Jane Smith",
    "account_status": "active",
    "outstanding_balance": 0,
    "last_order": "Order #4821 — delivered 3 days ago"
  }
}
```

For errors:

```json theme={null}
{
  "success": false,
  "error": "Customer not found"
}
```

<Tip>
  Use human-readable strings for values the agent will speak aloud. Instead of `"account_status": 2`, use `"account_status": "active"`. The agent reads what you return — make it easy to say.
</Tip>

## Timeout handling

Tools must respond within **5 seconds**. If your backend operation takes longer (e.g. a slow third-party API), use a queued pattern:

1. Acknowledge the request immediately with a pending response
2. Return a message the agent can relay to the caller
3. Trigger the slow operation asynchronously

```json theme={null}
{
  "success": true,
  "data": {
    "status": "pending",
    "message": "Your request is being processed. We will send you a confirmation email within 5 minutes."
  }
}
```

Then instruct the agent in the prompt:

```
If the tool returns a status of "pending", tell the caller exactly what the
message field says and offer to answer any other questions while they wait.
```

## Security: validating Truedy requests

Use a shared secret to verify that requests to your endpoint genuinely come from Truedy.

In your tool definition, add a secret as a static header parameter:

```json theme={null}
{
  "staticParameters": [
    {
      "name": "x-truedy-secret",
      "location": "PARAMETER_LOCATION_HEADER",
      "value": "a-long-random-string-you-generate"
    }
  ]
}
```

In your endpoint, validate the header on every request before processing:

```javascript theme={null}
const secret = req.headers['x-truedy-secret'];
if (!secret || secret !== process.env.TRUEDY_TOOL_SECRET) {
  return res.status(401).json({ success: false, error: 'Unauthorized' });
}
```

<Warning>
  Store the shared secret in an environment variable, never hardcoded in source code. Rotate it immediately if it is ever exposed.
</Warning>

## Prompt engineering for custom tools

The `description` field is the single most important part of your tool definition. It is what the AI model reads to decide **whether and when** to call the tool.

**Poor description (too vague):**

```
Looks up customer data.
```

**Good description (precise and contextual):**

```
Look up a customer's account status and most recent order by their phone number.
Call this tool when the caller mentions their account, asks about an order,
or wants to know their current balance. Only call this once per conversation
unless the caller provides a different phone number.
```

Rules for effective descriptions:

* State **what the tool returns**, not just what it is
* State **when to call it** (the trigger condition)
* State any **constraints** (call once, require a phone number first, etc.)
* Avoid ambiguity — the model takes descriptions literally

## Next steps

<Columns>
  <Card title="Built-in Tools" icon="plug" href="/guides/built-in-tools">
    Use Cal.com, GoHighLevel, or Calendly without writing any code
  </Card>

  <Card title="Tools Overview" icon="wrench" href="/guides/tools-overview">
    Understand the full tool execution model
  </Card>
</Columns>
