> ## 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.

# Funktionsverktyg

> Låt dina AI-agenter anropa externa API:er under samtal

Funktionsverktyg gör att dina AI-agenter kan anropa externa API:er under telefonsamtal. Använd dem för att slå upp kunddata, kontrollera tillgänglighet, boka tider eller utföra valfri åtgärd som din backend stöder.

## Så fungerar det

1. Du definierar verktyg med ett schema (vilka argument verktyget accepterar)
2. Du anger en `endpoint`-konfiguration (var ThunderPhone anropar ditt API) — eller utelämnar den för att ta emot verktygsanrop på organisationens webhook
3. Under ett samtal avgör AI:n när ett verktyg ska användas baserat på konversationen
4. ThunderPhone anropar din endpoint med verktygsargumenten
5. Ditt API-svar skickas tillbaka till AI:n för att fortsätta konversationen

<Note>
  Funktionsverktyg är alternativet där du använder ditt eget API. ThunderPhone
  innehåller även plattformshanterade verktyg som inte kräver någon endpoint:
  [appanslutningar](/sv/guides/connect-apps) (HubSpot, Salesforce, Slack,
  Google Calendar, Google Sheets, Cal.com),
  [API-anslutningar](/sv/guides/api-connections) och
  [MCP-servrar](/sv/guides/mcp-servers).
</Note>

***

## Verktygsschema

Varje verktyg följer den här strukturen:

```json theme={null}
{
  "type": "function",
  "function": {
    "name": "search_appointments",
    "description": "Find available appointment slots for a given date",
    "parameters": {
      "type": "object",
      "properties": {
        "date": {
          "type": "string",
          "description": "Date in YYYY-MM-DD format"
        },
        "service": {
          "type": "string",
          "description": "Type of service (e.g., 'consultation', 'follow-up')"
        }
      },
      "required": ["date"]
    }
  },
  "endpoint": {
    "url": "https://api.example.com/appointments/search",
    "method": "POST",
    "headers": {
      "X-Api-Key": "your-api-key"
    }
  }
}
```

### Funktionsdefinition

| Fält          | Typ    | Krävs | Beskrivning                                   |
| ------------- | ------ | ----- | --------------------------------------------- |
| `name`        | string | Ja    | Unik identifierare för verktyget              |
| `description` | string | Ja    | Förklarar för AI:n när verktyget ska användas |
| `parameters`  | object | Ja    | JSON Schema för verktygsargument              |

### Endpoint-konfiguration

| Fält      | Typ    | Krävs | Beskrivning                     |
| --------- | ------ | ----- | ------------------------------- |
| `url`     | string | Ja    | URL:en till din API-endpoint    |
| `method`  | string | Nej   | HTTP-metod (standard: `POST`)   |
| `headers` | object | Nej   | Anpassade headers att inkludera |

<Note>
  `endpoint`-konfigurationen skickas **inte** till AI-modellen — den används endast av ThunderPhone för att utföra verktygsanropet.
</Note>

***

## Två anropsvägar

Vilken begäran din server tar emot beror på om verktyget har en
`endpoint`:

|                      | Verktyg **med** `endpoint`                                                      | Verktyg **utan** `endpoint`                                                                 |
| -------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| Vart begäran skickas | Direkt till `endpoint.url`                                                      | Organisationens [äldre webhook-URL](/api-reference/organizations#legacy-single-url-webhook) |
| Brödtext             | **Endast verktygsargument**                                                     | `telephony.tool` / `web.tool`-omslag                                                        |
| Headers              | Dina `endpoint.headers` + `X-ThunderPhone-Call-ID` + `X-ThunderPhone-Signature` | `Content-Type` + `X-ThunderPhone-Signature`                                                 |
| Signeringsnyckel     | Organisationens webhook-hemlighet                                               | Organisationens webhook-hemlighet                                                           |

Båda vägarna är **blockerande** — AI:n väntar mitt i en mening på
resultatet — med en timeout på **20 s**. Håll hanterare snabba. Det går
bra att kombinera: i ett samtal vars organisation har en webhook-URL
anropas verktyg med en `endpoint` direkt, medan övriga använder
webhooken som reserv.

## Direkta endpointanrop

När AI:n anropar ett verktyg som har en `endpoint` skickar ThunderPhone
en begäran till din URL:

### Begärandehuvuden

```http theme={null}
POST /appointments/search HTTP/1.1
Host: api.example.com
Content-Type: application/json
X-ThunderPhone-Signature: abc123...
X-ThunderPhone-Call-ID: 987654321
X-Api-Key: your-api-key
```

Anpassade huvuden från din `endpoint.headers` inkluderas alltid
ordagrant, plus två ThunderPhone-namnområdesindelade huvuden:

* `X-ThunderPhone-Signature` — HMAC-SHA256 av de exakta bytevärdena i
  begärandetexten, med din **organisations webhook-hemlighet** som nyckel
* `X-ThunderPhone-Call-ID` — ID:t för det aktuella samtalet

`Content-Type: application/json` anges om inte din `endpoint.headers`
åsidosätter det — en anpassad `Content-Type` gäller.

<Warning>
  Signaturen använder webhook-hemligheten på organisationsnivå från
  [`GET /v1/webhook`](/api-reference/organizations#legacy-single-url-webhook)
  som nyckel. Om din organisation aldrig har konfigurerat den äldre
  webhooken finns ingen hemlighet och verktygsanrop innehåller **endast**
  `X-ThunderPhone-Call-ID` — en hanterare som avbryter vid en saknad
  signatur skulle avvisa dem. Konfigurera antingen den äldre webhooken
  för att få en hemlighet, eller lägg din egen delade hemlighet i
  `endpoint.headers`.
</Warning>

### Begärandetext

För `POST` / `PUT` / `PATCH` innehåller texten **endast**
verktygsargumenten (utan omslag), serialiserade kanoniskt (sorterade
nycklar, kompakta avgränsare):

```json theme={null}
{"date":"2025-01-02","service":"consultation"}
```

För `GET` / `DELETE` skickas argumenten som **frågeparametrar**
och texten är tom — signaturen beräknas då över den tomma
bytesträngen. Se
[Verifiera webhook-signaturer](/sv/guides/verify-webhook-signatures).

### Svar

Returnera ett JSON-svar med verktygsresultatet:

```json theme={null}
{
  "available_slots": ["9:00 AM", "2:00 PM", "4:30 PM"],
  "timezone": "America/Los_Angeles"
}
```

Svaret formateras och skickas till AI:n för att fortsätta
konversationen. Icke-JSON-svar omsluts som `{"data": "<text>"}`;
tidsgränser och anslutningsfel rapporteras till AI:n som fel, så att
röstagenten kan be om ursäkt och gå vidare i stället för att fastna.

## Utskick i webhook-läge

Verktyg **utan** en `endpoint` skickas till organisationens äldre
webhook-URL som en signerad `telephony.tool`-begäran (telefonsamtal)
eller `web.tool`-begäran (webbsamtal). Till skillnad från
[granskningsnotiserna](/sv/webhooks/events) som levereras till
webhook-endpoints efter körning **är** denna begäran själva körningen —
ditt HTTP-svar är verktygsresultatet.

```json theme={null}
{
  "type": "telephony.tool",
  "data": {
    "call_id": 987654321,
    "tool_name": "search_appointments",
    "arguments": { "date": "2026-04-21" },
    "from_number": "+14155550199",
    "to_number": "+15551234567"
  }
}
```

`web.tool` innehåller `origin_domain` i stället för `from_number` /
`to_number`. Svara med verktygsresultatet som JSON — samma
svarskontrakt som för direkta endpointanrop. Begäran signeras med
organisationens webhook-hemlighet över den råa texten, precis som alla
andra webhooks.

<Note>
  Prenumererande [webhook-endpoints](/sv/webhooks/endpoints) får dessutom
  en icke-blockerande `telephony.tool` / `web.tool`-**notis efter**
  varje verktygskörning (oavsett vilken väg som körde den), inklusive
  verktygets svar — användbart för granskningsloggar. Se
  [händelsekatalogen](/sv/webhooks/events).
</Note>

***

## Signaturverifiering

Direkta verktygsanrop signeras på samma sätt som webhooks:

* HMAC-SHA256 över de exakta byte i begärandetexten (den kanoniska JSON-strukturen —
  sorterade nycklar, inga extra blanksteg)
* Nycklad med organisationens webhook-hemlighet
* `GET`- / `DELETE`-verktyg signerar den tomma bytesträngen

<CodeGroup>
  ```python Python theme={null}
  import hmac
  import hashlib

  def verify_tool_call(body: bytes, signature: str, secret: str) -> bool:
      expected = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
      return hmac.compare_digest(expected, signature)

  @app.post("/appointments/search")
  async def search_appointments(request: Request):
      body = await request.body()
      signature = request.headers.get("X-ThunderPhone-Signature", "")

      if not verify_tool_call(body, signature, WEBHOOK_SECRET):
          raise HTTPException(status_code=401)

      data = json.loads(body)
      date = data["date"]

      # Look up availability
      slots = await get_available_slots(date)

      return {"available_slots": slots}
  ```

  ```javascript Node.js theme={null}
  app.post('/appointments/search', express.raw({type: 'application/json'}), (req, res) => {
    const signature = req.headers['x-thunderphone-signature'] || '';
    const expected = crypto
      .createHmac('sha256', WEBHOOK_SECRET)
      .update(req.body)
      .digest('hex');

    if (!signature ||
        signature.length !== expected.length ||
        !crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) {
      return res.status(401).send('Invalid signature');
    }

    const { date, service } = JSON.parse(req.body);

    // Look up availability
    const slots = getAvailableSlots(date, service);

    res.json({ available_slots: slots });
  });
  ```
</CodeGroup>

Fullständiga exempel — inklusive fallet med tom begärandetext och informationen om när ingen hemlighet används —
finns i [Verifiera webhook-signaturer](/sv/guides/verify-webhook-signatures).

***

## Exempel: Komplett bokningsflöde

Här är en uppsättning verktyg för ett komplett system för tidsbokning:

```json theme={null}
{
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "search_appointments",
        "description": "Find available appointment slots",
        "parameters": {
          "type": "object",
          "properties": {
            "date": { "type": "string", "description": "YYYY-MM-DD" },
            "service": { "type": "string" }
          },
          "required": ["date"]
        }
      },
      "endpoint": {
        "url": "https://api.example.com/appointments/search",
        "method": "POST",
        "headers": { "X-Api-Key": "key" }
      }
    },
    {
      "type": "function",
      "function": {
        "name": "book_appointment",
        "description": "Book an appointment at a specific time",
        "parameters": {
          "type": "object",
          "properties": {
            "date": { "type": "string", "description": "YYYY-MM-DD" },
            "time": { "type": "string", "description": "HH:MM format" },
            "customer_name": { "type": "string" },
            "customer_phone": { "type": "string" }
          },
          "required": ["date", "time", "customer_name"]
        }
      },
      "endpoint": {
        "url": "https://api.example.com/appointments/book",
        "method": "POST",
        "headers": { "X-Api-Key": "key" }
      }
    },
    {
      "type": "function",
      "function": {
        "name": "cancel_appointment",
        "description": "Cancel an existing appointment",
        "parameters": {
          "type": "object",
          "properties": {
            "confirmation_number": { "type": "string" }
          },
          "required": ["confirmation_number"]
        }
      },
      "endpoint": {
        "url": "https://api.example.com/appointments/cancel",
        "method": "POST",
        "headers": { "X-Api-Key": "key" }
      }
    }
  ]
}
```

***

## Bästa praxis

<AccordionGroup>
  <Accordion title="Skriv tydliga beskrivningar">
    Fältet `description` hjälper AI:n att förstå **när** verktyget ska användas. Var specifik med vad det gör och när det är lämpligt.
  </Accordion>

  <Accordion title="Hantera fel på ett smidigt sätt">
    Returnera felmeddelanden som AI:n kan förstå: `{"error": "No slots available for that date"}` i stället för generella 500-fel.
  </Accordion>

  <Accordion title="Håll svaren kortfattade">
    Returnera endast det AI:n behöver för att fortsätta samtalet. Stora nyttolaster förlänger svarstiderna.
  </Accordion>

  <Accordion title="Använd obligatoriska fält med eftertanke">
    Markera fält som `required` endast när det verkligen behövs. AI:n ber användaren om obligatorisk information innan verktyget anropas.
  </Accordion>
</AccordionGroup>

***

## Relaterat

<CardGroup cols={2}>
  <Card title="Appanslutningar" icon="plug" href="/sv/guides/connect-apps">
    Plattformshanterade verktyg för HubSpot, Salesforce, Slack, Google
    Calendar, Google Sheets och Cal.com — ingen endpoint krävs.
  </Card>

  <Card title="MCP-servrar" icon="server" href="/sv/guides/mcp-servers">
    Anslut en MCP-server och låt agenten anropa dess verktyg.
  </Card>

  <Card title="API-anslutningar" icon="code" href="/sv/guides/api-connections">
    Återanvändbara REST-integrationer som du kan ansluta till agenter.
  </Card>

  <Card title="Verifiera webhook-signaturer" icon="shield-check" href="/sv/guides/verify-webhook-signatures">
    En verifieringshjälpare för webhooks och verktygsanrop.
  </Card>
</CardGroup>
