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

# Build a tool integration (API)

> Let your agent call your APIs mid-conversation — search a database, create a ticket, look up an order.

A **tool integration** is a reusable HTTP endpoint that an agent can
invoke during a call. You give ThunderPhone a JSON-schema description
of the tool plus an endpoint URL; the agent decides when to call it
based on the conversation, and ThunderPhone makes the outbound HTTP
request from its servers and returns the response to the agent.

<Note>
  The dashboard covers most tool needs without this API: **Connections
  → Apps** connects Slack, HubSpot, Salesforce, Google Calendar,
  Google Sheets, and Cal.com in a few OAuth clicks; **Connections →
  APIs** turns any HTTP API into an agent action (paste a cURL command
  and an AI wizard drafts the tool, with a built-in Test Request); and
  **Connections → MCP** adds MCP servers. See
  [Connections](/guides/concepts#connections). This guide is the raw
  API underneath the APIs surface.
</Note>

This guide walks through building a weather-lookup tool end-to-end.

## Anatomy of a tool

Two pieces:

1. **The schema** — an OpenAI-style function definition
   (`{type: "function", function: {name, description, parameters}}`)
   that tells the LLM what the tool does and what arguments it takes.
2. **The endpoint** — the URL ThunderPhone's servers call when the
   LLM decides to use the tool. The request is JSON POST with the
   LLM's chosen arguments as the body.

## 1. Choose a storage strategy

<CardGroup cols={2}>
  <Card title="Inline on the agent" icon="paperclip">
    Attach a one-off tool to the agent's `tools` array. Simple, but
    not reusable.
  </Card>

  <Card title="Saved integration" icon="plug">
    Store the tool as a reusable [integration](/api-reference/integrations)
    and link it from many agents. Recommended for anything used more
    than once.
  </Card>
</CardGroup>

This guide uses the saved-integration path.

## 2. Create the integration

```bash theme={null}
curl -X POST https://api.thunderphone.com/v1/integrations \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "display_name": "Weather API",
    "spec": {
      "type": "function",
      "function": {
        "name": "get_weather",
        "description": "Return the current weather for a zip code.",
        "parameters": {
          "type": "object",
          "properties": {
            "zip": { "type": "string", "description": "5-digit US ZIP code" }
          },
          "required": ["zip"]
        }
      }
    },
    "endpoint_url":    "https://api.example.com/weather",
    "endpoint_method": "GET",
    "headers": [
      { "key": "X-Api-Key", "value": "your-provider-key" }
    ]
  }'
```

Save the returned `id` (a UUID).

<Tip>
  Spend real effort on the `description` of the tool and of each
  parameter. The LLM uses these strings at runtime to decide whether
  and how to call the tool. Vague descriptions → vague tool calls.
</Tip>

## 3. Sandbox-test the endpoint

Before you link the integration to an agent, fire a signed request
from ThunderPhone's servers to confirm connectivity:

```bash theme={null}
curl -X POST https://api.thunderphone.com/v1/integrations/test-request \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url":    "https://api.example.com/weather?zip=94110",
    "method": "GET",
    "headers": { "X-Api-Key": "your-provider-key" }
  }'
```

```json Response theme={null}
{
  "ok": true,
  "status": 200,
  "elapsed_ms": 187,
  "response_headers": { "content-type": "application/json" },
  "response_preview": "{\"temperature_f\": 64, ...}"
}
```

This test also hardens ThunderPhone's SSRF guards — requests to
localhost or private IP ranges return `400 code=url_not_allowed`.

## 4. Link the integration to an agent

Attach via `integration_ids` when you create or update an agent:

```bash theme={null}
curl -X PATCH https://api.thunderphone.com/v1/agents/12 \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "integration_ids": ["f9b5a1a4-..."]
  }'
```

You can link many integrations to one agent. The agent's prompt can
reference them by name — "use `get_weather` when the caller asks
about conditions" — or it can discover them implicitly from the
schema descriptions.

## 5. Implement the endpoint

When the agent invokes the tool, ThunderPhone sends a signed POST to
your `endpoint_url`:

```
POST /weather HTTP/1.1
Host: api.example.com
X-Api-Key: your-provider-key
X-ThunderPhone-Signature: <HMAC-SHA256 hex>
X-ThunderPhone-Call-ID: 987654321
Content-Type: application/json

{"zip": "94110"}
```

Your server responds with JSON that gets handed back to the LLM:

```json theme={null}
{"temperature_f": 64, "condition": "Partly cloudy", "wind_mph": 8}
```

The LLM ingests that response and speaks a human summary to the
caller.

<Warning>
  The signature is computed over the raw request body using the same
  `secret` as your webhook endpoint. **Verify it** — tool endpoints
  are internet-facing and subject to the same spoofing concerns as
  webhooks. See
  [Verify webhook signatures](/guides/verify-webhook-signatures).
</Warning>

## 6. Test the loop

Run a [mic session](/api-reference/mic-sessions) against the agent
and ask the question your tool handles ("What's the weather in
94110?"). The call's transcript shows the full round trip:

```json theme={null}
{
  "call_id": 987654321,
  "transcripts": [
    { "role": "user",
      "content": "What's the weather in 94110?" },
    { "role": "tool_call",
      "content": "{\"tool_call\": \"get_weather\", \"arguments\": {\"zip\": \"94110\"}}" },
    { "role": "tool_response",
      "content": "{\"tool_name\": \"get_weather\", \"response\": {\"temperature_f\": 64, \"condition\": \"Partly cloudy\"}}" },
    { "role": "agent",
      "content": "It's 64 degrees and partly cloudy." }
  ]
}
```

You can pull this via
[`GET /v1/calls/{call_id}/transcript`](/api-reference/calls#get-transcript);
the raw event stream (with per-entry timing and audio offsets) is at
[`GET /v1/calls/{call_id}/history`](/api-reference/calls#get-history).

## Common gotchas

<AccordionGroup>
  <Accordion title="Agent never calls the tool">
    The LLM decides based on the tool's description. If the caller's
    question doesn't match the description, the model won't invoke
    the tool. Tighten the description (add common synonyms and
    phrasing) or mention it explicitly in the agent prompt ("When the
    caller asks about weather, use `get_weather`.").
  </Accordion>

  <Accordion title="Tool returns too much data">
    Responses over 6 kB are truncated in the transcript preview. Return
    just the fields the LLM needs — not your whole row.
  </Accordion>

  <Accordion title="Timeouts">
    Tool endpoints have a 10-second default timeout. If you need longer,
    handle it asynchronously: return `{"status": "pending", "request_id": "..."}`
    and surface the result via a separate tool call.
  </Accordion>

  <Accordion title="Versioning">
    Every integration `PATCH` creates a new revision. Inspect
    [`GET /v1/integrations/{id}/versions`](/api-reference/integrations#version-history)
    to see who changed what. If you break a tool's schema, you can
    roll back manually by PATCH-ing an older snapshot back in.
  </Accordion>
</AccordionGroup>

***

## Next steps

<CardGroup cols={2}>
  <Card title="Integrations reference" icon="plug" href="/api-reference/integrations">
    CRUD, transfer, version history.
  </Card>

  <Card title="Function Tools spec" icon="screwdriver-wrench" href="/tools/overview">
    Full JSON schema grammar and the signed-endpoint contract.
  </Card>

  <Card title="Verify signatures" icon="shield-check" href="/guides/verify-webhook-signatures">
    Apply the webhook-signature pattern to tool endpoints.
  </Card>

  <Card title="Transcript + history API" icon="phone" href="/api-reference/calls">
    Inspect the full round-trip of a tool call.
  </Card>
</CardGroup>
