---
title: "Bygg en verktygsintegration (API)"
description: "Låt din röstagent anropa dina API:er mitt i ett samtal – sök i en databas, skapa ett ärende eller slå upp en beställning."
---

En **verktygsintegration** är en återanvändbar HTTP-slutpunkt som en röstagent kan
anropa under ett samtal. Du ger ThunderPhone en JSON-schemabeskrivning
av verktyget tillsammans med en slutpunkts-URL; röstagenten avgör när den ska anropa det
utifrån samtalet, och ThunderPhone skickar den utgående HTTP-begäran
från sina servrar och returnerar svaret till röstagenten.

<Note>
  Instrumentpanelen täcker de flesta verktygsbehov utan detta API: **Anslutningar
  → Appar** ansluter Slack, HubSpot, Salesforce, Google Kalender,
  Google Kalkylark och Cal.com med några OAuth-klick; **Anslutningar →
  API:er** gör valfritt HTTP-API till en röstagentåtgärd (klistra in ett cURL-kommando
  så skapar en AI-guide ett utkast till verktyget, med en inbyggd Testa begäran); och
  **Anslutningar → MCP** lägger till MCP-servrar. Se
  [Anslutningar](/sv/guides/concepts). Den här guiden beskriver det underliggande
  API:t för API-ytan.
</Note>

Den här guiden visar hur du bygger ett verktyg för väderuppslagning från början till slut.

## Verktygets uppbyggnad

Två delar:

1. **Schemat** — en OpenAI-liknande funktionsdefinition
   (`{type: "function", function: {name, description, parameters}}`)
   som talar om för LLM:en vad verktyget gör och vilka argument det tar.
2. **Slutpunkten** — URL:en som ThunderPhones servrar anropar när
   LLM:en beslutar att använda verktyget. Begäran är en JSON-POST med
   de argument som LLM:en har valt som brödtext.

## 1. Välj redigeringsmetod

<CardGroup cols={2}>
  <Card title="Instrumentpanel" icon="window-maximize">
    Öppna **Anslutningar → API:er**, skapa eller redigera API-anslutningen,
    växla parameterredigeraren till **JSON** och lägg till formatet där.
  </Card>
  <Card title="Integrations-API" icon="plug">
    Skapa specifikationen med `POST /v1/integrations`, eller uppdatera den med
    `PATCH /v1/integrations/{id}`.
  </Card>
</CardGroup>

Båda vägarna skapar en sparad integration. Koppla integrationen till
röstagenten efter att du har sparat den. Agents API har inget skrivbart inbyggt fält för `tools`.
Den här guiden använder integrations-API-vägen.

## 2. Skapa integrationen

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

Spara det returnerade `id`-värdet (ett UUID). Headervärden är endast skrivbara: svar listar endast headernamn, och en senare `PATCH` kan skicka en header utan ett `value` för att behålla det lagrade värdet.

<Tip>
  Lägg ordentligt med arbete på `description` för verktyget och varje
  parameter. LLM:en använder dessa strängar vid körning för att avgöra om
  och hur verktyget ska anropas. Vaga beskrivningar → vaga verktygsanrop.
</Tip>

### Ange `format: "email"` för adressparametrar

En parameter som tar emot en e-postadress bör ange det i sitt
schema:

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

`format` är mer än en ledtråd. För ett tolkat e-postschema, innan din
slutpunkt anropas, trimmar ThunderPhone värdet, gör domänen till
gemener, omvandlar de fristående engelska orden `at`, `dot`, `underscore`,
`dash` och `hyphen` till sina tecken och tar bort blanksteg direkt runt
`@`, `.`, `_` och `-`. Orden har samma betydelse oavsett om transkriptet
redan innehåller ett bokstavligt `@`:
`"john dot smith at gmail dot com"` blir
`john.smith@gmail.com`.

Alla andra interna blanksteg avvisas i stället för att tyst slås ihop.
Talade avgränsarord stöds endast på engelska; andra språk eller okända
former med blanksteg avvisas. Giltiga internationaliserade domäner och
SMTPUTF8-lokala delar godtas. Punycode-indata förblir punycode och
Unicode-domänindata förblir Unicode efter parsernormalisering, så ditt API
tar emot den konventionella representation som uppringaren angav. Om det
slutliga värdet är ogiltigt anropas verktyget **inte**. Agenten får
`invalid_email_argument`, som instruerar den att
bekräfta stavningen med uppringaren och skicka om den bokstavliga adressen.

En utelämnad valfri e-postadress lämnas orörd. `null`, en tom sträng eller
en sträng som endast innehåller blanksteg lämnas också orörd när egenskapen
är valfri eller kan vara null; samma värden avvisas för en obligatorisk
e-postadress som inte kan vara null.

Lokala schemareferenser som `#/$defs/email` och
`#/definitions/email`, samt `anyOf`, `oneOf` och `allOf`, granskas
med cykel- och djupgränser. En icke-lokal eller olösbar `$ref` är en känd
begränsning i valideringen och skickas vidare oförändrad, liksom ett anrop
vars verktygsögonblicksbild saknar ett användbart schema. Håll
e-postscheman lokala när du behöver att spärren tillämpas.

Parametrar utan ett framtvingat e-postformat skickas vidare exakt så som
modellen producerade dem.

Format som stöds är `date-time`, `time`, `date`, `duration`,
`email`, `hostname`, `ipv4`, `ipv6` och `uuid`; endast `email`
normaliseras och framtvingas i dag.

## 3. Testa slutpunkten i sandboxmiljön

Innan du kopplar integrationen till en agent skickar du en signerad begäran
från ThunderPhones servrar för att bekräfta anslutningen:

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

Det här testet förstärker också ThunderPhones SSRF-skydd — begäranden till
localhost eller privata IP-intervall returnerar `400 code=url_not_allowed`.

## 4. Koppla integrationen till en agent

Koppla via `integration_ids` när du skapar eller uppdaterar 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 koppla många integrationer till en agent. Agentens instruktion kan
referera till dem med namn — ”använd `get_weather` när den som ringer frågar
om väderförhållanden” — eller så kan den upptäcka dem implicit utifrån
schemabeskrivningarna.

## 5. Implementera slutpunkten

När agenten anropar verktyget skickar ThunderPhone en signerad POST-begäran till
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"}
```

Din server svarar med JSON som skickas tillbaka till LLM:en:

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

LLM:en tar emot svaret och säger en sammanfattning på naturligt språk till
den som ringer.

<Warning>
  Signaturen beräknas över den råa begärandetexten med samma
  `secret` som din webhook-slutpunkt. **Verifiera den** — verktygsslutpunkter
  är exponerade mot internet och omfattas av samma risker för förfalskning som
  webhooks. Se
  [Verifiera webhook-signaturer](/sv/guides/verify-webhook-signatures).
</Warning>

## 6. Testa flödet

Kör en [mikrofonsession](/api-reference/mic-sessions) mot agenten
och ställ frågan som verktyget hanterar (”Hur är vädret i
94110?”). Samtalets transkription visar hela tur- och returflödet:

```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 hämta detta via
[`GET /v1/calls/{call_id}/transcript`](/api-reference/calls#get-transcript);
den råa händelseströmmen (med tidsangivelser och ljudförskjutningar per post) finns på
[`GET /v1/calls/{call_id}/history`](/api-reference/calls#get-history).

I samtalslistor och analysvyer rapporterar ThunderPhone nu testsamtal separat från verklig trafik. Browser Talk-tester och simuleringar kan återanvända ett riktigt telefonnummer och kan se ut som inkommande samtal, men de räknas inte in i produktionssamtalens totaler, betyg eller incidentmått.

## Vanliga fallgropar

<AccordionGroup>
  <Accordion title="Agenten anropar aldrig verktyget">
    LLM:en avgör utifrån verktygets beskrivning. Om den som ringer ställer en
    fråga som inte matchar beskrivningen anropar modellen inte
    verktyget. Förtydliga beskrivningen (lägg till vanliga synonymer och
    formuleringar) eller nämn det uttryckligen i agentens instruktion (”När den
    som ringer frågar om vädret, använd `get_weather`.”).
  </Accordion>

  <Accordion title="Verktyget returnerar för mycket data">
    Svar över 6 kB trunkeras i förhandsvisningen av transkriptionen. Returnera
    bara de fält som LLM:en behöver — inte hela raden.
  </Accordion>

  <Accordion title="Tidsgränser">
    Verktygsslutpunkter har en standardtidsgräns på 10 sekunder. Om du behöver längre tid
    hanterar du det asynkront: returnera `{"status": "pending", "request_id": "..."}`
    och visa resultatet via ett separat verktygsanrop.
  </Accordion>

  <Accordion title="Versionshantering">
    Varje `PATCH` av en integration skapar en ny revision. Kontrollera
    [`GET /v1/integrations/{id}/versions`](/api-reference/integrations#version-history)
    för att se vem som ändrade vad. Om du förstör ett verktygs schema kan du
    återställa manuellt genom att PATCH:a tillbaka en äldre ögonblicksbild.
  </Accordion>
</AccordionGroup>

---

## Nästa steg

<CardGroup cols={2}>
  <Card title="Referens för integrationer" icon="plug" href="/api-reference/integrations">
    CRUD, överföring, versionshistorik.
  </Card>
  <Card title="Specifikation för funktionsverktyg" icon="screwdriver-wrench" href="/sv/tools/overview">
    Fullständig JSON-schemagrammatik och kontraktet för signerade slutpunkter.
  </Card>
  <Card title="Verifiera signaturer" icon="shield-check" href="/sv/guides/verify-webhook-signatures">
    Tillämpa mönstret för webhook-signaturer på verktygsslutpunkter.
  </Card>
  <Card title="API för transkript och historik" icon="phone" href="/api-reference/calls">
    Granska hela tur-och-retur-flödet för ett verktygsanrop.
  </Card>
</CardGroup>
