---
title: "Bygg en verktøyintegrasjon (API)"
description: "La stemmeagenten din kalle API-ene dine midt i en samtale – søk i en database, opprett en sak, slå opp en bestilling."
---

En **verktøyintegrasjon** er et gjenbrukbart HTTP-endepunkt som en agent kan
kalle under en samtale. Du gir ThunderPhone en JSON-skjemabeskrivelse
av verktøyet sammen med en endepunkt-URL; agenten avgjør når den skal kalle det
basert på samtalen, og ThunderPhone utfører den utgående HTTP-
forespørselen fra sine servere og returnerer svaret til agenten.

<Note>
  Dashbordet dekker de fleste verktøybehov uten dette API-et: **Tilkoblinger
  → Apper** kobler til Slack, HubSpot, Salesforce, Google Calendar,
  Google Sheets og Cal.com med noen få OAuth-klikk; **Tilkoblinger →
  API-er** gjør et hvilket som helst HTTP-API om til en agenthandling (lim inn en cURL-kommando
  og en AI-veiviser utformer verktøyet, med en innebygd Test forespørsel); og
  **Tilkoblinger → MCP** legger til MCP-servere. Se
  [Tilkoblinger](/nb/guides/concepts). Denne veiledningen handler om det rå
  API-et bak API-grensesnittet.
</Note>

Denne veiledningen går gjennom hvordan du bygger et verktøy for værslag opp fra start til slutt.

## Oppbygningen av et verktøy

To deler:

1. **Skjemaet** — en funksjonsdefinisjon i OpenAI-stil
   (`{type: "function", function: {name, description, parameters}}`)
   som forteller LLM-en hva verktøyet gjør og hvilke argumenter det tar.
2. **Endepunktet** — URL-en som ThunderPhone-serverne kaller når
   LLM-en bestemmer seg for å bruke verktøyet. Forespørselen er en JSON POST med
   argumentene LLM-en har valgt som forespørselskropp.

## 1. Velg en redigerer

<CardGroup cols={2}>
  <Card title="Dashbord" icon="window-maximize">
    Åpne **Tilkoblinger → API-er**, opprett eller rediger API-tilkoblingen,
    bytt parameterredigereren til **JSON**, og legg til formatet der.
  </Card>
  <Card title="Integrasjons-API" icon="plug">
    Opprett spesifikasjonen med `POST /v1/integrations`, eller oppdater den med
    `PATCH /v1/integrations/{id}`.
  </Card>
</CardGroup>

Begge alternativene oppretter en lagret integrasjon. Knytt integrasjonen til
agenten etter at du har lagret den. Agents API har ikke et skrivbart innebygd `tools`
-felt. Denne veiledningen bruker integrasjons-API-veien.

## 2. Opprett integrasjonen

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

Lagre den returnerte `id`-en (en UUID). Headerværdier er bare skrivbare: svar viser bare headernavn, og en senere `PATCH` kan sende en header uten en `value` for å beholde den lagrede verdien.

<Tip>
  Legg reell innsats i `description` for verktøyet og hver
  parameter. LLM-en bruker disse strengene under kjøring for å avgjøre om
  og hvordan verktøyet skal kalles. Vage beskrivelser → vage verktøykall.
</Tip>

### Angi `format: "email"` for adresseparametere

En parameter som mottar en e-postadresse, bør angi dette i
skjemaet sitt:

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

`format` er mer enn et hint. For et løst e-postskjema, før
endepunktet ditt kalles, trimmer ThunderPhone verdien, gjør domenet
om til små bokstaver, konverterer de frittstående engelske ordene `at`, `dot`,
`underscore`, `dash` og `hyphen` til tegnene sine, og fjerner mellomrom
rett rundt `@`, `.`, `_` og `-`. Ordene har samme betydning
uansett om transkripsjonen allerede inneholder en bokstavelig `@`:
`"john dot smith at gmail dot com"` blir til
`john.smith@gmail.com`.

Andre interne mellomrom avvises i stedet for å bli slått sammen i stillhet.
Talte skilletegnord er kun på engelsk; ikke-engelske eller ugjenkjente
mellomromsdelte former avvises. Gyldige internasjonaliserte domener og SMTPUTF8-
lokaldeler godtas. Punycode-inndata forblir punycode, og Unicode-domeneinndata
forblir Unicode etter parsernormalisering, slik at API-et ditt mottar den
vanlige representasjonen som innringeren oppga. Hvis den endelige
verdien er ugyldig, blir verktøyet **ikke kalt**. Stemmeagenten mottar
`invalid_email_argument`, som ber den
bekrefte stavemåten med innringeren og sende den bokstavelige adressen på nytt.

En utelatt valgfri e-postadresse forblir urørt. `null`, en tom streng eller en
streng som bare inneholder mellomrom, forblir også urørt når egenskapen er valgfri
eller kan være null; de samme verdiene avvises for en obligatorisk e-postadresse
som ikke kan være null.

Lokale skjemareferanser som `#/$defs/email` og
`#/definitions/email`, samt `anyOf`, `oneOf` og `allOf`, inspiseres
med grenser for sykluser og dybde. En ikke-lokal eller uløselig `$ref` er en
kjent begrensning i håndhevingen og sendes videre uendret, det samme gjelder et kall
der verktøyets øyeblikksbilde ikke har noe brukbart skjema. Hold e-postskjemaer lokale når
du trenger at kontrollen skal gjelde.

Parametere uten et håndhevet e-postformat sendes videre nøyaktig
slik modellen produserte dem.

Støttede formater er `date-time`, `time`, `date`, `duration`,
`email`, `hostname`, `ipv4`, `ipv6` og `uuid`; bare `email`
normaliseres og håndheves i dag.

## 3. Sandbox-test endepunktet

Før du kobler integrasjonen til en stemmeagent, sender du en signert forespørsel
fra ThunderPhone-serverne for å bekrefte tilkoblingen:

```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, ...}"
}
```

Denne testen styrker også ThunderPhones SSRF-beskyttelse — forespørsler til
localhost eller private IP-områder returnerer `400 code=url_not_allowed`.

## 4. Knytt integrasjonen til en agent

Knytt den til via `integration_ids` når du oppretter eller oppdaterer en agent:

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

Du kan knytte mange integrasjoner til én agent. Agentens prompt kan
referere til dem ved navn — «bruk `get_weather` når innringeren spør
om forhold» — eller den kan oppdage dem implisitt fra
skjemabeskrivelsene.

## 5. Implementer endepunktet

Når agenten bruker verktøyet, sender ThunderPhone en signert POST til
din `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"}
```

Serveren din svarer med JSON som sendes tilbake til LLM-en:

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

LLM-en tar inn svaret og gir innringeren en menneskelig oppsummering.

<Warning>
  Signaturen beregnes over den rå forespørselskroppen ved hjelp av den samme
  `secret` som webhook-endepunktet ditt. **Verifiser den** — verktøyendepunkter
  er eksponert mot internett og har de samme risikoene for forfalskning som
  webhooks. Se
  [Verifiser webhook-signaturer](/nb/guides/verify-webhook-signatures).
</Warning>

## 6. Test flyten

Kjør en [mikrofonøkt](/api-reference/mic-sessions) mot agenten
og still spørsmålet verktøyet ditt håndterer («Hvordan er været i
94110?»). Samtalens transkripsjon viser hele forespørselsrunden:

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

Du kan hente dette via
[`GET /v1/calls/{call_id}/transcript`](/api-reference/calls#get-transcript);
den rå hendelsesstrømmen (med tidsangivelse per oppføring og lydforskyvninger) finnes på
[`GET /v1/calls/{call_id}/history`](/api-reference/calls#get-history).

I samtaleoversikter og analyseflater rapporterer ThunderPhone nå testsamtaler separat fra reell trafikk. Tester og simuleringer i Browser Talk kan gjenbruke et ekte telefonnummer og kan se ut som innkommende samtaler, men de teller ikke med i produksjonens samtaletotaler, vurderinger eller problemstatistikk.

## Vanlige fallgruver

<AccordionGroup>
  <Accordion title="Agenten bruker aldri verktøyet">
    LLM-en avgjør basert på verktøyets beskrivelse. Hvis innringerens
    spørsmål ikke samsvarer med beskrivelsen, bruker modellen ikke
    verktøyet. Gjør beskrivelsen mer presis (legg til vanlige synonymer og
    formuleringer), eller nevn det eksplisitt i agentens prompt («Når
    innringeren spør om vær, bruk `get_weather`.»).
  </Accordion>

  <Accordion title="Verktøyet returnerer for mye data">
    Svar over 6 kB blir avkortet i forhåndsvisningen av transkripsjonen. Returner
    bare feltene LLM-en trenger — ikke hele raden.
  </Accordion>

  <Accordion title="Tidsavbrudd">
    Verktøyendepunkter har en standard tidsavbruddsgrense på 10 sekunder. Hvis du trenger lengre tid,
    håndter det asynkront: returner `{"status": "pending", "request_id": "..."}`
    og vis resultatet via et separat verktøykall.
  </Accordion>

  <Accordion title="Versjonering">
    Hver `PATCH` av en integrasjon oppretter en ny revisjon. Se på
    [`GET /v1/integrations/{id}/versions`](/api-reference/integrations#version-history)
    for å se hvem som endret hva. Hvis du ødelegger et verktøys skjema, kan du
    rulle tilbake manuelt ved å PATCH-e et eldre øyeblikksbilde tilbake.
  </Accordion>
</AccordionGroup>

---

## Neste trinn

<CardGroup cols={2}>
  <Card title="Integrasjonsreferanse" icon="plug" href="/api-reference/integrations">
    CRUD, overføring, versjonshistorikk.
  </Card>
  <Card title="Spesifikasjon for funksjonsverktøy" icon="screwdriver-wrench" href="/nb/tools/overview">
    Fullstendig JSON-schemagrammatikk og kontrakten for signerte endepunkter.
  </Card>
  <Card title="Verifiser signaturer" icon="shield-check" href="/nb/guides/verify-webhook-signatures">
    Bruk mønsteret for webhook-signaturer på verktøyendepunkter.
  </Card>
  <Card title="API for transkripsjon + historikk" icon="phone" href="/api-reference/calls">
    Undersøk hele tur-retur-flyten for et verktøykall.
  </Card>
</CardGroup>
