---
title: "Een toolintegratie bouwen (API)"
description: "Laat je agent tijdens een gesprek je API's aanroepen — doorzoek een database, maak een ticket aan, zoek een bestelling op."
---

Een **toolintegratie** is een herbruikbaar HTTP-eindpunt dat een agent
tijdens een gesprek kan aanroepen. Je geeft ThunderPhone een JSON-schema-beschrijving
van de tool plus een eindpunt-URL; de agent bepaalt op basis van het gesprek
wanneer deze moet worden aangeroepen, en ThunderPhone voert het uitgaande HTTP-verzoek
vanaf zijn servers uit en retourneert het antwoord aan de agent.

<Note>
  Het dashboard dekt de meeste toolbehoeften zonder deze API: **Connections
  → Apps** verbindt Slack, HubSpot, Salesforce, Google Calendar,
  Google Sheets en Cal.com met enkele OAuth-klikken; **Connections →
  APIs** zet elke HTTP-API om in een agentactie (plak een cURL-opdracht
  en een AI-wizard stelt de tool op, met een ingebouwde Test Request); en
  **Connections → MCP** voegt MCP-servers toe. Zie
  [Connections](/nl/guides/concepts). Deze gids behandelt de onderliggende
  API van de APIs-interface.
</Note>

Deze gids behandelt stap voor stap het bouwen van een tool voor weersinformatie.

## Opbouw van een tool

Twee onderdelen:

1. **Het schema** — een functieomschrijving in OpenAI-stijl
   (`{type: "function", function: {name, description, parameters}}`)
   die het LLM vertelt wat de tool doet en welke argumenten deze accepteert.
2. **Het eindpunt** — de URL die de servers van ThunderPhone aanroepen wanneer het
   LLM besluit de tool te gebruiken. Het verzoek is een JSON POST met de door het
   LLM gekozen argumenten als hoofdtekst.

## 1. Kies een editor

<CardGroup cols={2}>
  <Card title="Dashboard" icon="window-maximize">
    Open **Connections → APIs**, maak de API-verbinding of bewerk deze,
    schakel de parametereditor naar **JSON** en voeg daar het schema toe.
  </Card>
  <Card title="Integrations API" icon="plug">
    Maak de specificatie met `POST /v1/integrations`, of werk deze bij met
    `PATCH /v1/integrations/{id}`.
  </Card>
</CardGroup>

Beide routes maken een opgeslagen integratie. Koppel die integratie aan de
agent nadat je deze hebt opgeslagen. De Agents API heeft geen beschrijfbaar inline
veld `tools`. Deze gids gebruikt de route via de Integrations API.

## 2. De integratie maken

```bash
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" }
    ]
  }'
```

Sla de geretourneerde `id` (een UUID) op. Headerwaarden zijn alleen schrijfbaar: antwoorden vermelden alleen headernamen, en een latere `PATCH` kan een header zonder `value` verzenden om de opgeslagen waarde te behouden.

<Tip>
  Besteed echt aandacht aan de `description` van de tool en van elke
  parameter. De LLM gebruikt deze tekenreeksen tijdens runtime om te bepalen of
  en hoe de tool moet worden aangeroepen. Vage beschrijvingen → vage toolaanroepen.
</Tip>

### Declareer `format: "email"` voor adresparameters

Een parameter die een e-mailadres ontvangt, moet dit in het schema
aangeven:

```json
"email": { "type": "string", "format": "email", "description": "The caller's email address" }
```

`format` is meer dan een hint. Voor een opgelost e-mailschema verwijdert
ThunderPhone vóór het aanroepen van je endpoint witruimte aan het begin en einde van de waarde, zet het
domein om naar kleine letters, zet de losse Engelse woorden `at`, `dot`,
`underscore`, `dash` en `hyphen` om naar hun tekens, en verwijdert witruimte
direct rond `@`, `.`, `_` en `-`. De woorden hebben dezelfde betekenis,
ongeacht of het transcript al een letterlijk `@` bevat:
`"john dot smith at gmail dot com"` wordt
`john.smith@gmail.com`.

Andere interne witruimte wordt afgewezen in plaats van stilzwijgend samengevoegd.
Gesproken scheidingswoorden zijn alleen Engels; niet-Engelse of niet-herkende
vormen met spaties falen gesloten. Geldige geïnternationaliseerde domeinen en SMTPUTF8-lokale delen
worden geaccepteerd. Punycode-invoer blijft punycode en Unicode-domeininvoer blijft Unicode
na normalisatie door de parser, zodat je API de door de beller opgegeven
gebruikelijke representatie ontvangt. Als de uiteindelijke waarde ongeldig is, wordt de tool
**niet aangeroepen**. De agent ontvangt
`invalid_email_argument`, met de instructie
om de spelling met de beller te bevestigen en het letterlijke adres opnieuw te verzenden.

Een weggelaten optioneel e-mailadres blijft ongewijzigd. `null`, een lege tekenreeks of een
tekenreeks met alleen witruimte blijft eveneens ongewijzigd wanneer de eigenschap optioneel
of nullable is; dezelfde waarden worden afgewezen voor een verplicht, niet-nullable
e-mailadres.

Lokale schemaverwijzingen zoals `#/$defs/email` en
`#/definitions/email`, plus `anyOf`, `oneOf` en `allOf`, worden geïnspecteerd
met limieten voor cycli en diepte. Een niet-lokale of niet-oplosbare `$ref` is een
bekende handhavingslimiet en wordt ongewijzigd doorgegeven, net als een aanroep
waarvan de toolsnapshot geen bruikbaar schema heeft. Houd e-mailschema's lokaal wanneer
je wilt dat de controle wordt toegepast.

Parameters zonder een afgedwongen e-mailindeling worden precies doorgegeven
zoals het model ze heeft geproduceerd.

Ondersteunde indelingen zijn `date-time`, `time`, `date`, `duration`,
`email`, `hostname`, `ipv4`, `ipv6` en `uuid`; alleen `email` wordt
vandaag genormaliseerd en afgedwongen.

## 3. Test het endpoint in de sandbox

Voordat je de integratie aan een agent koppelt, stuur je een ondertekend verzoek
vanaf de servers van ThunderPhone om de connectiviteit te bevestigen:

```bash
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
{
  "ok": true,
  "status": 200,
  "elapsed_ms": 187,
  "response_headers": { "content-type": "application/json" },
  "response_preview": "{\"temperature_f\": 64, ...}"
}
```

Deze test versterkt ook de SSRF-beveiligingen van ThunderPhone — verzoeken naar
localhost of privé-IP-bereiken retourneren `400 code=url_not_allowed`.

## 4. Koppel de integratie aan een spraakagent

Koppel via `integration_ids` wanneer je een spraakagent maakt of bijwerkt:

```bash
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-..."]
  }'
```

Je kunt meerdere integraties aan één spraakagent koppelen. De prompt van de spraakagent kan
ernaar verwijzen op naam — "gebruik `get_weather` wanneer de beller vraagt
naar de weersomstandigheden" — of kan ze impliciet ontdekken via de
schemabeschrijvingen.

## 5. Implementeer het eindpunt

Wanneer de spraakagent de tool aanroept, stuurt ThunderPhone een ondertekende POST naar
je `endpoint_url`:

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

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

Je server antwoordt met JSON dat wordt teruggegeven aan de LLM:

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

De LLM verwerkt dat antwoord en spreekt een begrijpelijke samenvatting uit voor de
beller.

<Warning>
  De handtekening wordt berekend over de onbewerkte aanvraagbody met dezelfde
  `secret` als je webhookeindpunt. **Verifieer deze** — tooleindpunten
  zijn bereikbaar via internet en hebben dezelfde risico's op spoofing als
  webhooks. Zie
  [Webhookhandtekeningen verifiëren](/nl/guides/verify-webhook-signatures).
</Warning>

## 6. Test de volledige keten

Start een [microfoonsessie](/api-reference/mic-sessions) met de spraakagent
en stel de vraag die je tool afhandelt ("Wat is het weer in
94110?"). Het transcript van de oproep toont de volledige cyclus:

```json
{
  "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." }
  ]
}
```

Je kunt dit ophalen via
[`GET /v1/calls/{call_id}/transcript`](/api-reference/calls#get-transcript);
de onbewerkte eventstream (met timing per invoer en audio-offsets) staat op
[`GET /v1/calls/{call_id}/history`](/api-reference/calls#get-history).

In overzichten van oproepen en analyses rapporteert ThunderPhone testoproepen nu afzonderlijk van echt verkeer. Browser Talk-tests en simulaties kunnen een echt telefoonnummer hergebruiken en lijken mogelijk inkomend, maar tellen niet mee voor productietotalen van oproepen, beoordelingen of probleemmetingen.

## Veelvoorkomende valkuilen

<AccordionGroup>
  <Accordion title="Spraakagent roept de tool nooit aan">
    De LLM beslist op basis van de beschrijving van de tool. Als de vraag van de beller
    niet overeenkomt met de beschrijving, roept het model
    de tool niet aan. Maak de beschrijving specifieker (voeg veelvoorkomende synoniemen en
    formuleringen toe) of vermeld dit expliciet in de prompt van de spraakagent ("Wanneer de
    beller naar het weer vraagt, gebruik `get_weather`.").
  </Accordion>

  <Accordion title="Tool retourneert te veel gegevens">
    Antwoorden groter dan 6 kB worden afgekapt in het transcriptvoorbeeld. Retourneer
    alleen de velden die de LLM nodig heeft — niet je volledige rij.
  </Accordion>

  <Accordion title="Time-outs">
    Tooleindpunten hebben standaard een time-out van 10 seconden. Als je meer tijd nodig hebt,
    verwerk het dan asynchroon: retourneer `{"status": "pending", "request_id": "..."}`
    en toon het resultaat via een afzonderlijke toolaanroep.
  </Accordion>

  <Accordion title="Versiebeheer">
    Elke `PATCH` van een integratie maakt een nieuwe revisie. Bekijk
    [`GET /v1/integrations/{id}/versions`](/api-reference/integrations#version-history)
    om te zien wie wat heeft gewijzigd. Als je het schema van een tool breekt, kun je
    handmatig terugdraaien door een oudere momentopname opnieuw te PATCH-en.
  </Accordion>
</AccordionGroup>

---

## Volgende stappen

<CardGroup cols={2}>
  <Card title="Naslagwerk integraties" icon="plug" href="/api-reference/integrations">
    CRUD, overdracht, versiegeschiedenis.
  </Card>
  <Card title="Specificatie voor functietools" icon="screwdriver-wrench" href="/nl/tools/overview">
    Volledige JSON-schemagrammatica en het contract voor ondertekende endpoints.
  </Card>
  <Card title="Handtekeningen verifiëren" icon="shield-check" href="/nl/guides/verify-webhook-signatures">
    Pas het webhook-handtekeningpatroon toe op tool-endpoints.
  </Card>
  <Card title="Transcriptie + geschiedenis-API" icon="phone" href="/api-reference/calls">
    Bekijk de volledige heen-en-terugreis van een toolaanroep.
  </Card>
</CardGroup>
