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

# Funktiotyökalut

> Anna AI-agenttiesi kutsua ulkoisia API-rajapintoja keskustelujen aikana

Funktiotyökalujen avulla AI-agenttisi voivat kutsua ulkoisia API-rajapintoja puheluiden aikana. Käytä niitä asiakastietojen hakemiseen, saatavuuden tarkistamiseen, tapaamisten varaamiseen tai mihin tahansa toimintoon, jota taustajärjestelmäsi tukee.

## Näin se toimii

1. Määrität työkalut skeemalla (mitä argumentteja työkalu hyväksyy)
2. Määrität `endpoint`-kokoonpanon (mihin ThunderPhone kutsuu API-rajapintaasi) — tai jätät sen pois vastaanottaaksesi työkalukutsut organisaatiosi webhookissa
3. Puhelun aikana AI päättää keskustelun perusteella, milloin työkalua käytetään
4. ThunderPhone kutsuu päätepistettäsi työkalun argumenteilla
5. API-vastauksesi välitetään takaisin AI:lle keskustelun jatkamiseksi

<Note>
  Funktiotyökalut ovat tapa käyttää omaa API-rajapintaasi. ThunderPhone tarjoaa myös
  alustahallinnoituja työkaluja, jotka eivät tarvitse päätepistettä:
  [sovellusintegraatiot](/fi/guides/connect-apps) (HubSpot, Salesforce, Slack,
  Google Calendar, Google Sheets, Cal.com),
  [API-integraatiot](/fi/guides/api-connections) ja
  [MCP-palvelimet](/fi/guides/mcp-servers).
</Note>

***

## Työkalun skeema

Jokainen työkalu noudattaa tätä rakennetta:

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

### Funktion määritelmä

| Kenttä        | Tyyppi | Pakollinen | Kuvaus                                         |
| ------------- | ------ | ---------- | ---------------------------------------------- |
| `name`        | string | Kyllä      | Työkalun yksilöllinen tunniste                 |
| `description` | string | Kyllä      | Kertoo AI:lle, milloin tätä työkalua käytetään |
| `parameters`  | object | Kyllä      | Työkalun argumenttien JSON-skeema              |

### Päätepisteen kokoonpano

| Kenttä    | Tyyppi | Pakollinen | Kuvaus                               |
| --------- | ------ | ---------- | ------------------------------------ |
| `url`     | string | Kyllä      | API-päätepisteesi URL                |
| `method`  | string | Ei         | HTTP-metodi (oletus: `POST`)         |
| `headers` | object | Ei         | Mukautetut sisällytettävät otsakkeet |

<Note>
  `endpoint`-kokoonpanoa **ei** lähetetä AI-mallille — ThunderPhone käyttää sitä vain työkalukutsun suorittamiseen.
</Note>

***

## Kaksi kutsupolkua

Palvelimesi vastaanottama pyyntö riippuu siitä, onko työkalulla
`endpoint`:

|                         | Työkalu, **jossa on** `endpoint`                                           | Työkalu, **jossa ei ole** `endpoint`                                                                      |
| ----------------------- | -------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| Minne pyyntö lähetetään | Suoraan osoitteeseen `endpoint.url`                                        | Organisaatiosi [vanhaan webhook-URL-osoitteeseen](/api-reference/organizations#legacy-single-url-webhook) |
| Runko                   | **Pelkät työkalun argumentit**                                             | `telephony.tool` / `web.tool` -kirjekuori                                                                 |
| Otsakkeet               | `endpoint.headers` + `X-ThunderPhone-Call-ID` + `X-ThunderPhone-Signature` | `Content-Type` + `X-ThunderPhone-Signature`                                                               |
| Allekirjoitusavain      | Organisaation webhook-salaisuus                                            | Organisaation webhook-salaisuus                                                                           |

Molemmat polut ovat **synkronisia** — AI odottaa tulosta kesken lauseen —
ja niiden aikakatkaisu on **20 s**. Pidä käsittelijät nopeina. Voit käyttää
molempia: puhelussa, jonka organisaatiolla on webhook-URL, työkalut, joilla on
`endpoint`, kutsutaan suoraan ja muut palaavat webhookiin.

## Suorat päätepistekutsut

Kun tekoäly kutsuu työkalua, jolla on `endpoint`, ThunderPhone lähettää
pyynnön URL-osoitteeseesi:

### Pyyntöotsakkeet

```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
```

Mukautetut otsakkeet kohdasta `endpoint.headers` sisällytetään aina
sellaisinaan sekä kaksi ThunderPhone-nimiavaruuteen kuuluvaa otsaketta:

* `X-ThunderPhone-Signature` — HMAC-SHA256 pyynnön tarkasta
  runkodatan tavujonosta käyttäen avaimena **organisaatiosi webhook-salaisuutta**
* `X-ThunderPhone-Call-ID` — Nykyisen puhelun tunnus

`Content-Type: application/json` asetetaan, ellei `endpoint.headers`
ohita sitä — mukautettu `Content-Type` on ensisijainen.

<Warning>
  Allekirjoituksessa käytetään organisaatiotason webhook-salaisuutta,
  joka haetaan osoitteesta
  [`GET /v1/webhook`](/api-reference/organizations#legacy-single-url-webhook).
  Jos organisaatiosi ei ole koskaan määrittänyt vanhaa webhookia,
  salaista avainta ei ole, ja työkalukutsut sisältävät **vain**
  `X-ThunderPhone-Call-ID`-otsakkeen — käsittelijä, joka epäonnistuu
  puuttuvan allekirjoituksen vuoksi, hylkäisi ne.
  Määritä joko vanha webhook saadaksesi salaisen avaimen tai lisää oma
  jaettu salainen avain kohtaan `endpoint.headers`.
</Warning>

### Pyyntödata

Kohdissa `POST` / `PUT` / `PATCH` runko sisältää **vain** työkalun
argumentit (ilman käärettä), kanonisesti sarjoitettuna (avaimet
lajiteltuina, tiiviit erottimet):

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

Kohdissa `GET` / `DELETE` argumentit lähetetään **kyselyparametreina**
ja runko on tyhjä — allekirjoitus lasketaan tällöin tyhjälle
tavujonolle. Katso
[Webhook-allekirjoitusten vahvistaminen](/fi/guides/verify-webhook-signatures).

### Vastaus

Palauta JSON-vastaus, joka sisältää työkalun tuloksen:

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

Vastaus muotoillaan ja annetaan tekoälylle keskustelun jatkamista
varten. Muut kuin JSON-vastaukset kääritään muotoon `{"data": "<text>"}`;
aikakatkaisuista ja yhteysvirheistä ilmoitetaan tekoälylle virheinä,
jotta agentti voi pahoitella ja jatkaa sen sijaan, että se jäisi
odottamaan.

## Webhook-tilan reititys

Työkalut, **joilla ei ole** `endpoint`-määrittelyä, reititetään
organisaatiosi vanhaan webhook-URL-osoitteeseen allekirjoitettuna
`telephony.tool`- (puhelut) tai `web.tool`-pyyntönä
(verkkopuhelut). Toisin kuin [auditointi-ilmoitukset](/fi/webhooks/events),
jotka toimitetaan webhook-päätepisteisiin suorituksen jälkeen, tämä
pyyntö **on** suoritus — HTTP-vastauksesi on työkalun tulos.

```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` sisältää `origin_domain`-kentän kenttien `from_number` /
`to_number` sijaan. Vastaa työkalun tuloksella JSON-muodossa — sama
vastaussopimus kuin suorissa päätepistekutsuissa. Pyyntö allekirjoitetaan
organisaation webhook-salaisuudella raa'an rungon perusteella, kuten
jokainen muukin webhook.

<Note>
  Tilatut [webhook-päätepisteet](/fi/webhooks/endpoints) vastaanottavat
  lisäksi ei-estävän `telephony.tool` / `web.tool` **ilmoituksen
  jokaisen työkalusuorituksen jälkeen** (riippumatta siitä, mitä reittiä
  se suoritettiin), mukaan lukien työkalun vastauksen — hyödyllistä
  auditointijälkiä varten. Katso
  [tapahtumaluettelo](/fi/webhooks/events).
</Note>

***

## Allekirjoituksen vahvistus

Suorat työkalukutsut allekirjoitetaan samalla tavalla kuin webhookit:

* HMAC-SHA256 tarkkojen pyynnön rungon tavujen yli (kanoninen JSON — lajitellut avaimet, ei ylimääräisiä välilyöntejä)
* Avaimena organisaatiosi webhook-salaisuus
* `GET`- / `DELETE`-työkalut allekirjoittavat tyhjän tavumerkkijonon

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

Täydelliset ohjeet — mukaan lukien tyhjän rungon tapaus ja salaisuuden puuttumista koskeva huomautus — ovat kohdassa [Webhook-allekirjoitusten vahvistaminen](/fi/guides/verify-webhook-signatures).

***

## Esimerkki: täydellinen varausprosessi

Tässä on työkalujoukko täydellistä ajanvarausjärjestelmää varten:

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

***

## Parhaat käytännöt

<AccordionGroup>
  <Accordion title="Kirjoita selkeät kuvaukset">
    `description`-kenttä auttaa tekoälyä ymmärtämään, **milloin** työkalua käytetään. Kerro tarkasti, mitä se tekee ja milloin sen käyttö on sopivaa.
  </Accordion>

  <Accordion title="Käsittele virheet hallitusti">
    Palauta virheilmoituksia, jotka tekoäly ymmärtää: `{"error": "No slots available for that date"}` yleisten 500-virheiden sijaan.
  </Accordion>

  <Accordion title="Pidä vastaukset tiiviinä">
    Palauta vain se, mitä tekoäly tarvitsee keskustelun jatkamiseksi. Suuret hyötykuormat hidastavat vastausaikoja.
  </Accordion>

  <Accordion title="Käytä pakollisia kenttiä harkiten">
    Merkitse kentät `required`-kentiksi vain, kun se on todella tarpeen. Tekoäly pyytää käyttäjältä vaaditut tiedot ennen työkalun kutsumista.
  </Accordion>
</AccordionGroup>

***

## Aiheeseen liittyvää

<CardGroup cols={2}>
  <Card title="Sovellusliitännät" icon="plug" href="/fi/guides/connect-apps">
    Alustan hallinnoimat työkalut HubSpotille, Salesforcelle, Slackille, Google
    Calendarille, Google Sheetsille ja Cal.comille — päätepistettä ei tarvita.
  </Card>

  <Card title="MCP-palvelimet" icon="server" href="/fi/guides/mcp-servers">
    Liitä MCP-palvelin ja anna agentin kutsua sen työkaluja.
  </Card>

  <Card title="API-liitännät" icon="code" href="/fi/guides/api-connections">
    Uudelleenkäytettävät REST-integraatiot, jotka voit liittää agentteihin.
  </Card>

  <Card title="Vahvista webhook-allekirjoitukset" icon="shield-check" href="/fi/guides/verify-webhook-signatures">
    Yksi vahvistusapuri webhookeille ja työkalukutsuille.
  </Card>
</CardGroup>
