Naar de inhoud

Voor ontwikkelaars

Koppel Telofia aan de systemen die je al gebruikt

Haal gesprekken, transcripties, afspraken en contacten op via een REST API, ontvang ondertekende webhooks zodra er iets gebeurt, laat de assistent tijdens het gesprek live gegevens in je systeem opzoeken en zet met één regel code een spraakassistent op je website.

De REST API en webhooks zitten in elk abonnement, vanaf Line; hogere abonnementen hebben hogere limieten. Tijdens de gratis proefperiode heb je testtoegang: 1 API-sleutel, 20 verzoeken per minuut.

Basis-URL van de API
https://telofia.com/api/v1
curl · Sleutel testen
curl https://telofia.com/api/v1/me \
  -H "Authorization: Bearer tf_live_…"
200 OK
{
  "object": "account",
  "id": "1f2e3d4c-5b6a-4789-8a7b-6c5d4e3f2a1b",
  "name": "Smile Dental",
  "timezone": "Europe/Warsaw",
  "locale": "en",
  "country": "PL",
  "currency": "PLN",
  "status": "active",
  "plan": {
    "id": "team",
    "billing_cycle": "month",
    "trial_ends_at": null,
    "period_start": "2026-10-01T00:00:00Z",
    "period_end": "2026-11-01T00:00:00Z",
    "recording_history_days": 365
  },
  "minutes": {
    "included": 3000,
    "extra": 100,
    "total": 3100,
    "used": 412.5,
    "remaining": 2687.5
  },
  "api_key_id": "2a3b4c5d-6e7f-4a8b-9c0d-1e2f3a4b5c6d",
  "scopes": [
    "read:calls",
    "read:appointments",
    "read:contacts",
    "read:tasks"
  ],
  "limits": {
    "plan": "team",
    "requests_per_minute": 180,
    "requests_per_day": 50000,
    "max_api_keys": 10,
    "max_webhook_endpoints": 10
  }
}

Snel starten

Je eerste verzoek in twee minuten

  1. 1

    Maak een API-sleutel aan

    Ga in het dashboard naar Ontwikkelaars → API-sleutels, geef de sleutel een naam en kies de rechten. De sleutel (tf_live_…) wordt maar één keer getoond, dus bewaar hem meteen in je secretsmanager.

  2. 2

    Test de verbinding

    Roep GET /me aan. Dat werkt met elke geldige sleutel en geeft je account, de rechten van de sleutel en de limieten van je abonnement terug.

  3. 3

    Lees gegevens of abonneer je op events

    Vraag lijsten met gesprekken, afspraken en contacten op, of voeg onder Ontwikkelaars → Webhooks een endpoint toe om events direct te ontvangen.

curl · Sleutel testen
curl https://telofia.com/api/v1/me \
  -H "Authorization: Bearer tf_live_…"
JavaScript: alle gesprekken met een boeking, pagina voor pagina
const API = "https://telofia.com/api/v1";
const headers = { Authorization: `Bearer ${process.env.TELOFIA_API_KEY}` };

// All calls booked since 1 October, page by page
let cursor = null;
while (true) {
  const url = new URL(`${API}/calls`);
  url.searchParams.set("outcome", "booked");
  url.searchParams.set("since", "2026-10-01T00:00:00Z");
  url.searchParams.set("limit", "100");
  if (cursor) url.searchParams.set("cursor", cursor);

  const res = await fetch(url, { headers });
  if (res.status === 429) {
    // rate limited: wait for the number of seconds in Retry-After, then retry the same page
    await new Promise((r) => setTimeout(r, Number(res.headers.get("Retry-After") ?? 1) * 1000));
    continue;
  }
  if (!res.ok) throw new Error((await res.json()).error.message);

  const page = await res.json();
  for (const call of page.data) console.log(call.started_at, call.from, call.summary);
  if (!page.has_more) break;
  cursor = page.next_cursor;
}

REST API v1

Gesprekken, afspraken en contacten via HTTPS

JSON erin, JSON eruit. Elk verzoek wordt geauthenticeerd met een API-sleutel en valt binnen de limieten van je abonnement. Verzoeken zie je in het dashboard (methode, pad, status en duur — nooit de inhoud van verzoeken of antwoorden).

Authenticatie

Maak een sleutel aan bij API-sleutels en stuur hem mee als Bearer-token. Sleutels beginnen met tf_live_ en horen bij één account.

Authorization: Bearer tf_live_…

Paginering

Lijst-endpoints geven tot limit items terug (1–100, standaard 25) in een vaste volgorde. Is has_more true, geef dan next_cursor ongewijzigd mee als cursor (met dezelfde filters) voor de volgende pagina. Een ongeldige cursor geeft 400 invalid_request.

Fouten

Fouten gebruiken standaard HTTP-statuscodes en een JSON-body met een type en een leesbare foutmelding.

{ "error": { "type": "…", "message": "…" } }

Endpoints

  • GET/meRecht: elke sleutel

    Huidig account ophalen

    Geeft het account terug waar de API-sleutel bij hoort. Handig om de verbinding te testen.

  • GET/assistantsRecht: elke sleutel

    Assistenten ophalen

    Je assistenten met taal, status en toegewezen telefoonnummers. Gebruik de id als assistant_id om gesprekken te filteren.

  • GET/assistants/{id}Recht: elke sleutel

    Een assistent ophalen

    Eén assistent met taal, status en telefoonnummers. Met de scope read:assistants (of write:assistants) ook de prompt: instructions, greeting, company_profile en updated_at.

  • PATCH/assistants/{id}Recht: write:assistants

    De prompt van de assistent wijzigen

    Wijzigt de instructies, begroeting of het bedrijfsprofiel. Weggelaten velden blijven ongewijzigd. Dezelfde limieten als in het dashboard: instructies tot 20.000 tekens, bedrijfsprofiel tot 10.000, begroeting 5–600 tekens en die moet zeggen dat de beller met een AI praat (anders 400 met code ai_disclosure). Elke wijziging komt in de versiegeschiedenis van de assistent (bron API) en geldt vanaf het volgende gesprek. De eerlijkheidsregels van het platform (AI-melding, opnamemelding) gelden altijd.

  • GET/phone-numbersRecht: elke sleutel

    Telefoonnummers ophalen

    De telefoonnummers van je account met land, type, status en de assistent die ze beantwoordt.

  • GET/callsRecht: read:calls

    Gesprekken weergeven

    Gesprekken met de nieuwste eerst, zonder transcripties. Demogesprekken worden nooit meegenomen.

  • GET/calls/{id}Recht: read:calls

    Een gesprek ophalen

    Eén gesprek met de volledige transcriptie en de afspraken die tijdens het gesprek zijn geboekt.

  • GET/calls/{id}/recordingRecht: read:calls

    Gespreksopname ophalen

    Ondertekende links (1 uur geldig) om de MP3-opname af te spelen en te downloaden (stereo: beller links, assistent rechts). Een opname die ouder is dan de opnamegeschiedenis van je abonnement geeft 403 plan_required met recording_history_days; een gesprek zonder opname geeft 404.

  • GET/appointmentsRecht: read:appointments

    Afspraken weergeven

    Aankomende afspraken op starttijd (standaard vanaf nu).

  • GET/appointments/{id}Recht: read:appointments

    Een afspraak ophalen

    Eén afspraak van je account.

  • POST/appointments/{id}/cancelRecht: write:appointments

    Een afspraak annuleren

    Annuleert een komende afspraak, verwijdert die uit de gekoppelde Google--agenda en stuurt de webhook appointment.canceled. calendar_sync laat zien of de agenda is bijgewerkt.

  • POST/appointments/{id}/rescheduleRecht: write:appointments

    Een afspraak verzetten

    Verzet een komende afspraak naar een nieuw tijdstip. Antwoordt met 409 als het tijdstip overlapt met een andere afspraak of bezette tijd in de gekoppelde agenda. Zonder ends_at blijft de duur gelijk. Stuurt de webhook appointment.rescheduled.

  • GET/contactsRecht: read:contacts

    Contacten weergeven

    Je bellers, met de nieuwste eerst. De assistent onthoudt ze over gesprekken heen.

  • GET/contacts/{id}Recht: read:contacts

    Contact ophalen

    Eén contact met naam, e-mail, tags, notities en de herkomst van naam en e-mail (call, system of manual).

  • POST/contactsRecht: write:contacts

    Contact aanmaken

    Voegt een contact toe, bijvoorbeeld vanuit je CRM, zodat de assistent de naam van de beller kent. Naam en e-mail die via de API zijn opgeslagen, zijn gemarkeerd als manual en worden nooit door de assistent overschreven. Geeft 409 met contact_id als het nummer al bestaat. Verstuurt de webhook contact.created.

  • PATCH/contacts/{id}Recht: write:contacts

    Contact wijzigen

    Wijzigt naam, e-mail, notities of blokkering. Weggelaten velden blijven ongewijzigd en null maakt een veld leeg. Een gewijzigde naam of e-mail is gemarkeerd als manual, dus de assistent overschrijft die niet. Het telefoonnummer kan niet worden gewijzigd.

  • GET/tasksRecht: read:tasks

    Taken ophalen

    Terugbelverzoeken, berichten en andere taken uit gesprekken, nieuwste eerst.

  • GET/tasks/{id}Recht: read:tasks

    Taak ophalen

    Eén taak van je account.

  • PATCH/tasks/{id}Recht: write:tasks

    Taak wijzigen

    Zet de status van de taak op open, done of dismissed, bijvoorbeeld nadat je team de klant heeft teruggebeld.

  • POST/calls/outboundRecht: write:calls

    Een uitgaand gesprek aanmaken

    Je assistent belt één persoon vanaf je bedrijfsnummer met het doel dat jij opgeeft, net als een stap in een onboardingreeks. Het gesprek gaat nu uit of op call_at, altijd binnen de beltijden (ma–za, 9:00–20:00 in de tijdzone van je account; daarbuiten schuift het op naar het eerstvolgende toegestane moment en laat call_at zien wanneer). Neemt niemand op, dan probeert de assistent het tot 3 keer, met 2 uur ertussen. Geeft 202 terug met het gesprek in status scheduled. Fouten: 400 consent_missing, invalid_phone, number_not_callable, international of invalid_call_at; 409 opted_out (de persoon wil niet gebeld worden), already_scheduled (met existing_id), assistant_not_live, over_quota of no_outbound_number; 429 daily_outbound_limit met limit (standaard 200 gesprekken per 24 uur). Het resultaat komt binnen via de webhooks call.completed en outbound_call.finished met je external_id.

  • GET/calls/outbound/{id}Recht: write:calls

    Een uitgaand gesprek ophalen

    Status van een ingepland uitgaand gesprek: scheduled, dialing, done (met call_id van het gesprek), failed (last_error, bijv. no_answer na alle pogingen) of canceled. Werkt ook met de callback_id uit webhooks voor onboarding- en terugbelgesprekken.

  • DELETE/calls/outbound/{id}Recht: write:calls

    Een uitgaand gesprek annuleren

    Annuleert een via de API aangemaakt uitgaand gesprek dat nog ingepland staat. Veilig om te herhalen (al geannuleerd geeft 200). Geeft 409 not_cancelable als er al gebeld wordt of het gesprek is afgerond, of bij onboardinggesprekken (verwijder de persoon dan met POST /api/outreach/unenroll).

  • GET/messagesRecht: read:messages

    Sms’en weergeven

    Sms’en die je account heeft verstuurd en antwoorden van klanten, nieuwste eerst. phone is het nummer van de klant (ontvanger of afzender van een antwoord). generated_by: template, ai of manual; charged_from: plan of credits.

  • POST/messagesRecht: write:messages

    Een sms versturen

    Stuurt een sms naar een van je contacten of bellers: je eigen tekst, of een sjabloon van je account met vars (date, time, name, service, link, amount). Telt mee voor het sms-tegoed van je abonnement en extra sms’en. Fouten: 400 invalid_request (consent_missing, invalid_phone, too_long, missing_vars); 409 conflict (opted_out — de klant antwoordde STOP, over_quota, recipient_not_allowed — maak eerst het contact aan, blocked_destination); 429 rate_limited.

Rechten

Elke sleutel heeft de scopes die bij het aanmaken zijn gekozen. Scopes kun je later niet wijzigen — maak een nieuwe sleutel aan als je meer nodig hebt. Mist de sleutel de vereiste scope, dan antwoorden endpoints met 403 insufficient_scope. GET /me, /assistants en /phone-numbers werken met elke geldige sleutel.

  • read:callsGesprekken, samenvattingen en transcripties lezen
  • read:appointmentsAfspraken lezen
  • write:appointmentsAfspraken annuleren en verzetten
  • read:contactsContacten lezen
  • write:contactsContacten aanmaken en wijzigen
  • read:tasksTaken en terugbelverzoeken lezen
  • write:tasksStatus van taken wijzigen
  • write:callsUitgaande gesprekken starten, opvragen en annuleren
  • read:messagesSms’en en antwoorden van klanten lezen
  • write:messagesSms’en naar je contacten sturen
  • read:assistantsDe prompt van de assistent lezen (instructies, begroeting, bedrijfsprofiel)
  • write:assistantsDe prompt van de assistent wijzigen

Fouten

  • 400invalid_request — een parameter ontbreekt of is ongeldig
  • 401unauthorized — API-sleutel ontbreekt, is ongeldig of is ingetrokken
  • 403insufficient_scope — de sleutel mist de scope van het endpoint; plan_required — het account heeft geen actief abonnement of de opname is ouder dan de opnamegeschiedenis van het abonnement
  • 404not_found — het object bestaat niet in je account
  • 409conflict — het tijdstip is al bezet, de afspraak kan niet worden gewijzigd, er bestaat al een contact met dit telefoonnummer of een uitgaand gesprek kan niet worden gevoerd (code: opted_out, already_scheduled, assistant_not_live, over_quota, no_outbound_number, not_cancelable)
  • 429rate_limited — te veel verzoeken; probeer opnieuw na de tijd in de Retry-After-header
  • 500server_error — er ging aan onze kant iets mis
  • 503service_unavailable — tijdelijk probleem; probeer opnieuw na de tijd in de Retry-After-header

De volledige API-referentie met parameters en voorbeeldantwoorden staat in het dashboard onder Ontwikkelaars → API-referentie.

Webhooks

Events in realtime naar je server

Voeg in het dashboard een https-endpoint toe, kies de events en wij sturen een ondertekende JSON-envelop (POST) zodra er iets gebeurt. “Test versturen” levert een voorbeeld van je eerste event, handig om velden te koppelen in Zapier of Make.

Events

  • call.completedGesprek voltooid

    Samenvatting, resultaat, sentiment, transcriptie, contact en boekingen; bij uitgaande gesprekken ook de reeks of het API-verzoek (callback_id, external_id, outreach).

  • call.startedGesprek gestart

    Er is een gesprek aangenomen (inkomend, uitgaand of via het web).

  • appointment.bookedAfspraak geboekt

    De assistent heeft een afspraak geboekt.

  • appointment.canceledAfspraak geannuleerd

    Een afspraak is telefonisch of via de API geannuleerd.

  • appointment.rescheduledAfspraak verzet

    Een afspraak is naar een nieuw tijdstip verzet (inclusief het vorige tijdstip).

  • task.createdTerugbelverzoek / taak aangemaakt

    Een beller vroeg om teruggebeld te worden of liet een taak achter.

  • contact.createdNieuw contact

    Iemand die voor het eerst belde, is als contact opgeslagen.

  • outbound_call.finishedUitgaand gesprek afgerond

    Een ingepland uitgaand gesprek (API, onboardingreeks of terugbelverzoek) is afgerond of mislukt — ook als niemand opnam na alle pogingen.

Headers bij elke levering

  • Telofia-SignatureHandtekening: t=<unix-tijd>,v1=<HMAC-SHA256 hex>
  • Telofia-EventType event, bijv. appointment.booked
  • Telofia-DeliveryUnieke leverings-ID
  • User-AgentTelofia-Webhooks/1.0 · Identificeert onze webhookafzender

Levering en nieuwe pogingen

  • Antwoord binnen 10 seconden met een willekeurige 2xx-status. Doorverwijzingen worden niet gevolgd.
  • Mislukte leveringen proberen we opnieuw na 1 min, 5 min, 30 min, 2 u, 6 u en 12 u (7 pogingen, ongeveer 21 uur).
  • Hetzelfde event kan vaker dan één keer binnenkomen. Gebruik de id van het event om dubbele te negeren.
  • Alleen openbare https-adressen zijn toegestaan. Na 50 mislukte pogingen op rij wordt een endpoint gepauzeerd en toont het dashboard waarom.
  • Het dashboard houdt een leveringslog bij met statuscodes en antwoorden en laat je een levering handmatig opnieuw versturen.
Voorbeeldlevering: call.completed
{
  "id": "evt_3f9a1c7e5b2d4f6a8c0e1b3d5f7a9c1e",
  "type": "call.completed",
  "created_at": "2026-10-01T09:17:45Z",
  "account_id": "1f2e3d4c-5b6a-4789-8a7b-6c5d4e3f2a1b",
  "data": {
    "id": "5b1f0c6e-2a8d-4f3e-9d51-7c0a4e2b9f10",
    "object": "call",
    "assistant_id": "0e6f4c8a-3b2d-4a1e-8f7c-5d9b2a1c3e4f",
    "phone_number_id": "4d3c2b1a-0f9e-4d8c-b7a6-958473625140",
    "contact_id": "9a8b7c6d-5e4f-4a3b-2c1d-0e9f8a7b6c5d",
    "direction": "inbound",
    "status": "completed",
    "from": "+48601234567",
    "to": "+48221234567",
    "started_at": "2026-10-01T09:14:03Z",
    "ended_at": "2026-10-01T09:17:41Z",
    "duration_sec": 218,
    "summary": "Anna Kowalska booked a first consultation for Friday 10:00.",
    "outcome": "booked",
    "sentiment": "positive",
    "tags": [
      "new-customer"
    ],
    "has_recording": true,
    "end_reason": "caller_hangup",
    "callback_id": null,
    "external_id": null,
    "outreach": null,
    "caller_spoke": true,
    "caller_words": 6,
    "transcript": [
      {
        "role": "caller",
        "text": "I'd like to book a consultation this week.",
        "at": 4.1
      }
    ],
    "contact": {
      "id": "9a8b7c6d-5e4f-4a3b-2c1d-0e9f8a7b6c5d",
      "object": "contact",
      "phone": "+48601234567",
      "name": "Anna Kowalska",
      "name_source": "call",
      "email": "anna.kowalska@example.com",
      "email_source": "call",
      "notes": null,
      "tags": [
        "customer"
      ],
      "blocked": false,
      "calls_count": 3,
      "last_call_at": "2026-10-01T09:14:03Z",
      "created_at": "2026-08-12T15:02:11Z"
    },
    "appointments": [
      {
        "id": "c3d2e1f0-a9b8-4c7d-8e6f-5a4b3c2d1e0f",
        "object": "appointment",
        "call_id": "5b1f0c6e-2a8d-4f3e-9d51-7c0a4e2b9f10",
        "contact_id": "9a8b7c6d-5e4f-4a3b-2c1d-0e9f8a7b6c5d",
        "customer_name": "Anna Kowalska",
        "customer_phone": "+48601234567",
        "customer_email": "anna.kowalska@example.com",
        "service": "First consultation",
        "starts_at": "2026-10-03T08:00:00Z",
        "ends_at": "2026-10-03T08:30:00Z",
        "status": "booked",
        "external_id": "google:7h3k9s2l1m0n",
        "created_at": "2026-10-01T09:16:20Z"
      }
    ]
  }
}

Handtekeningen verifiëren. Elke levering bevat een header Telofia-Signature in de vorm t=timestamp,v1=signature. Bereken de HMAC-SHA256 van “timestamp.raw_body” met je ondertekeningssleutel en vergelijk die met v1. Weiger timestamps die ouder zijn dan 5 minuten.

Node.js · Handtekening controleren
import crypto from "node:crypto";

// Express: app.post("/webhook", express.raw({ type: "application/json" }), handler)
export function verify(rawBody, signatureHeader, secret) {
  // Telofia-Signature: t=1727774265,v1=5f2c…
  const parts = Object.fromEntries(signatureHeader.split(",").map((p) => p.split("=")));
  const expected = crypto.createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
  const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
  const given = Buffer.from(parts.v1 ?? "");
  // timingSafeEqual needs buffers of the same length
  return fresh && given.length === expected.length && crypto.timingSafeEqual(Buffer.from(expected), given);
}
Python · Handtekening controleren
import hmac, hashlib, time

def verify(raw_body: bytes, signature_header: str, secret: str) -> bool:
    parts = dict(p.split("=", 1) for p in signature_header.split(","))
    expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + raw_body, hashlib.sha256).hexdigest()
    fresh = abs(time.time() - int(parts["t"])) < 300
    return fresh and hmac.compare_digest(expected, parts.get("v1", ""))
Voorbeeldontvanger (Express)
import express from "express";
import { verify } from "./verify.js";

const app = express();
const seen = new Set(); // use your database in production

app.post("/telofia/webhook", express.raw({ type: "application/json" }), (req, res) => {
  const raw = req.body.toString("utf8");
  if (!verify(raw, req.get("Telofia-Signature") ?? "", process.env.TELOFIA_WEBHOOK_SECRET)) return res.sendStatus(401);

  const event = JSON.parse(raw);
  if (seen.has(event.id)) return res.sendStatus(200); // retried delivery, already handled
  seen.add(event.id);

  if (event.type === "appointment.booked") {
    const a = event.data;
    console.log("New booking:", a.customer_name, a.service, a.starts_at);
  }
  res.sendStatus(200); // answer with 2xx within 10 seconds
});

app.listen(3000);

Stabiele waarden

Deze waarden in gesprekken (API en call.*-webhooks) en in uitgaande gesprekken (outbound_call.finished, /calls/outbound) zijn een stabiel contract: we kunnen nieuwe waarden toevoegen, maar deze wijzigen of verwijderen we niet. Uitgaande pogingen die niet worden opgenomen, in gesprek zijn of worden geweigerd, leveren nooit een gesprek op, dus daarvoor komt er geen call.completed: na de laatste poging ontvang je outbound_call.finished met last_error no_answer. Een gesprek dat door de voicemail wordt aangenomen, telt als opgenomen (meestal een kort gesprek met outcome other).

Status van het gesprek

in_progress
Het gesprek loopt.
completed
De persoon heeft met de assistent gesproken.
missed
Niemand sprak, of de assistent kon het gesprek niet aannemen (zie end_reason).
failed
Het gesprek kon door een fout niet worden afgehandeld.

Resultaat van het gesprek

booked
Er is een afspraak geboekt.
transferred
Het gesprek is doorverbonden met een medewerker.
message
Er is een bericht, terugbelverzoek, bestelling of opvolging genoteerd.
info
De persoon kreeg informatie; verder was er niets nodig.
spam
Spam, telemarketing of een geblokkeerd nummer.
other
Al het overige, bijv. een heel kort gesprek of voicemail.

Reden van beëindiging (end_reason)

caller_hangup
De andere persoon heeft opgehangen.
caller_hangup_greeting
Uitgaand gesprek: de persoon hing op tijdens of direct na de begroeting zonder iets te zeggen (gesprek tot 45 s). caller_spoke is dan false.
agent_hangup
De assistent beëindigde het gesprek na het afscheid.
transferred
Het gesprek is doorverbonden met een medewerker.
silence
Beëindigd na een lange stilte.
max_duration
De maximale gespreksduur is bereikt.
spam
Beëindigd als spam.
blocked
Het nummer is geblokkeerd in contacten.
busy
Alle lijnen van het account waren bezet.
over_quota
Geen belminuten meer over.
trial_expired
De gratis proefperiode is afgelopen.
inactive
Geen actief abonnement.
assistant_not_live
De assistent is gepauzeerd.
no_assistant
Geen assistent neemt dit nummer op.
ai_budget
Zonder AI afgehandeld vanwege een tijdelijke limiet; het team is gevraagd terug te bellen.
error
Een technische fout heeft het gesprek beëindigd.

Status van het uitgaande gesprek

scheduled
Wacht op het ingestelde tijdstip (of op de volgende poging).
dialing
Wordt nu gebeld.
done
Opgenomen; call_id is het gesprek.
failed
Niet opgenomen na alle pogingen, of kon niet worden uitgevoerd (zie last_error).
canceled
Geannuleerd (API, persoon uit de reeks verwijderd of reeks uitgezet).

last_error van het uitgaande gesprek

no_answer
Niemand nam op (ook bij in gesprek of geweigerd).
failed
De assistent was op het moment van bellen niet beschikbaar (abonnement, minuten of pauze).
no_result
Binnen 15 minuten geen resultaat van het gesprek.
expired
Het gesprek was meer dan 2 uur te laat en is daarom niet gevoerd.
outside_window
Verplaatst naar de volgende beltijden (status blijft scheduled).
dial_error
Het telefoonnetwerk weigerde het gesprek (verstuurd als dial_<reason>).
over_quota
Geen belminuten meer over.
inactive
Geen actief abonnement.
assistant_not_live
De assistent is gepauzeerd.
no_outbound_number
Geen bedrijfsnummer om mee te bellen.
canceled_by_api
Geannuleerd met DELETE /calls/outbound/{id}.
unenrolled
De persoon is uit de reeks verwijderd (POST /api/outreach/unenroll).
opt_out
De persoon wil niet gebeld worden.
replaced
Vervangen door een nieuwer terugbelverzoek naar hetzelfde nummer.

Live gegevens via MCP

De assistent zoekt tijdens het gesprek dingen op in je systeem

Koppel een externe MCP-server (Model Context Protocol), zoals je webwinkelcatalogus, voorraad, boekings- of ordersysteem. Jij bepaalt precies welke tools en bronnen (alleen-lezen) de assistent mag gebruiken.

Vooraf leren

Voor inhoud die zelden verandert, zoals prijslijsten, productbeschrijvingen of voorwaarden. Gekozen bronnen en alleen-lezen-tools worden elke 1 tot 168 uur of op verzoek in de kennis van de assistent geïmporteerd. Geen vertraging tijdens het gesprek.

Live controleren tijdens het gesprek

Voor dingen die veranderen: voorraad, beschikbaarheid, orderstatus. De assistent roept de tool tijdens het gesprek aan met een kort “een moment, ik kijk het even na”. Antwoordt je server niet binnen ongeveer 2,5 seconden, dan zegt hij dat hij het nu niet kan bevestigen en biedt hij aan dat je team contact opneemt.

Wat je nodig hebt

  • Een MCP-server die via https bereikbaar is (Streamable HTTP, bijv. https://mcp.example.com/mcp; oudere HTTP+SSE-servers worden automatisch herkend).
  • Authenticatie: geen, een Bearer-token of een eigen header zoals X-API-Key. Geheimen worden versleuteld (AES-256-GCM) en nooit naar de browser gestuurd.
  • Alleen-lezen-tools. Tools die als destructief gemarkeerd zijn, worden geblokkeerd; voor tools zonder markering ‘alleen-lezen’ bevestig je dat ze alleen gegevens lezen.
  • Tot 5 bronnen en 50 live-tools per assistent. Live opvragingen moeten binnen ongeveer 2 seconden antwoorden.
  • Optioneel voor de websitewidget: kies bij een tool „Aan het begin van het gesprek” het argument dat het identiteitstoken van de ingelogde bezoeker krijgt (zie Spraakwidget voor je website).

Argumenten worden gecontroleerd tegen het schema van de tool, resultaten worden ingekort en uitsluitend als gegevens behandeld (nooit als instructies), en elke opvraging wordt gelogd zonder gegevens van de beller. Privé- en interne adressen worden geweigerd.

Spraakwidget voor je website

Je assistent op je website, met één regel code

Bezoekers klikken op een knop en praten in de browser met dezelfde assistent die je telefoon opneemt. Webgesprekken gebruiken de minuten van je abonnement, zonder telefoniekosten.

Vanaf Office

  • Ongeveer 14 KB, zonder afhankelijkheden, geïsoleerd in Shadow DOM zodat de stijlen van je site hem niet kunnen breken.
  • Spreekt de taal van je pagina (<html lang>) of de taal die je instelt met data-lang="de".
  • Live ondertiteling; is de microfoon geblokkeerd, dan kan de bezoeker typen en antwoordt de assistent met spraak.
  • Beperk de widget tot je eigen domeinen, dan start hij alleen op die sites.
Insluitcode (je sleutel staat in het dashboard)
<script src="https://telofia.com/widget.js" data-key="tfw_…" async></script>
JavaScript-API
// optional: open the widget from your own button
document.querySelector("#talk-to-us").addEventListener("click", () => window.TelofiaWidget?.open());

window.TelofiaWidget?.close();   // close the panel
window.TelofiaWidget?.destroy(); // remove the widget from the page

Strenge Content Security Policy? Sta script-src https://telofia.com https://cdn.jsdelivr.net, connect-src https://telofia.com wss://*.livekit.cloud https://*.livekit.cloud en media-src blob: toe.

Ingelogde gebruikers (identiteitstoken)

Als bezoekers op je website zijn ingelogd, weet de assistent wie er spreekt zonder naar het e-mailadres te vragen.

  • Je server geeft de ingelogde gebruiker een kortlevend, ondertekend token (bijv. HMAC, 15 minuten geldig). Zet nooit een e-mailadres of gebruikers-ID in de browser: iedereen zou in de console dat van een ander kunnen invullen.
  • Roep TelofiaWidget.identify({ token }) op elk moment aan; een latere aanroep vervangt het token en identify(null) wist het. Voordat widget.js is geladen, stel je window.TelofiaIdentity = { token, expires_at } in: dit wordt gelezen zodra het gesprek begint, en een verlopen token wordt overgeslagen.
  • Kies in het dashboard het argument voor het identiteitstoken bij je MCP-tool „Aan het begin van het gesprek”. Telofia geeft het token ongewijzigd alleen aan dat argument door, slaat het nooit op, toont het nooit en houdt het buiten transcripties, samenvattingen, webhooks en logs; de assistent ziet het nooit.
  • 8 tot 400 tekens: letters, cijfers en . _ ~ + / = - (bijv. base64url). Vernieuw het voordat het verloopt, bijvoorbeeld elke 10 minuten. Je MCP-server controleert het; een ongeldig of verlopen token moet hetzelfde resultaat geven als geen token. Telefoongesprekken en gesprekken zonder token werken zoals voorheen.
Identiteitstoken
// signed-in user: a short-lived, signed token issued by YOUR server (never an e-mail or user ID)
const { token, expires_at } = await fetch("/api/telofia-identity").then((r) => r.json());

window.TelofiaIdentity = { token, expires_at };       // read when the conversation starts, also before widget.js loads
window.TelofiaWidget?.identify({ token, expires_at }); // replaces the token at any time

// refresh before it expires (e.g. every 10 minutes); on sign-out:
window.TelofiaIdentity = null;
window.TelofiaWidget?.identify(null);

Telefonische onboarding

Je nieuwe klanten krijgen automatisch een welkomstgesprek

Meldt iemand zich aan in je systeem, stuur die persoon dan door naar een belreeks. De assistent belt vanaf je bedrijfsnummer, helpt op weg en belt een paar dagen later nog eens. Er wordt gebeld binnen de beltijden van de reeks: standaard van maandag tot zaterdag, van 9:00 tot 20:00 in jouw tijdzone, of op de dagen en tijden die je zelf instelt.

POST/api/outreach/enroll

Authenticeer met de sleutel van de reeks (tlo_…) uit het dashboard, als Bearer-token of in de header X-Telofia-Key. Elke reeks heeft een eigen sleutel.

Body van het verzoek

  • phonestringverplicht
    Telefoonnummer, bij voorkeur in E.164-formaat. Nationale nummers worden gelezen met de landcode van je account.
  • consenttrueverplicht
    Moet true zijn: de persoon heeft toestemming gegeven om gebeld te worden, bijv. in je aanmeldformulier.
  • namestringoptioneel
    Naam, maximaal 120 tekens.
  • emailstringoptioneel
    E-mailadres.
  • contextstringoptioneel
    Wat de assistent over deze persoon moet weten, maximaal 1.000 tekens (bijv. het gekozen abonnement).
  • external_idstring | numberoptioneel
    De ID van de persoon in je systeem (tekst of getal). Dezelfde ID wordt maar één keer ingeschreven.
curl
curl -X POST "https://telofia.com/api/outreach/enroll" \
  -H "Authorization: Bearer tlo_…" \
  -H "Content-Type: application/json" \
  -d '{"phone":"+48601234567","name":"Anna Kowalska","email":"anna@example.com","context":"Signed up today, Start plan","external_id":"user_123","consent":true}'
201 Created
{
  "ok": true,
  "enrollment_id": "8f7e6d5c-4b3a-4c2d-9e1f-0a9b8c7d6e5f",
  "external_id": "user_123",
  "status": "active",
  "next_call_at": "2026-10-09T10:20:00Z",
  "limits": {
    "per_day": 500,
    "remaining_today": 499
  }
}

Antwoorden

  • 201: ingeschreven. enrollment_id, external_id, status, next_call_at (tijdstip van het eerste gesprek), intro_sms_at (wanneer de sms vóór het eerste gesprek verstuurd wordt, of null) en limits { per_day, remaining_today }
  • 200: al ingeschreven (zelfde external_id, of dit nummer loopt al in de reeks), met duplicate: true
  • 400: invalid_request, invalid_phone of consent_required
  • 401: invalid_key
  • 409: sequence_disabled, consent_missing of opted_out (de persoon wil niet gebeld worden)
  • 429: limit, de daglimiet voor inschrijvingen in de reeks is bereikt (500 nieuwe personen per 24 uur; de body bevat limit). rate_limited: meer dan 120 verzoeken per minuut met deze sleutel (Retry-After-header)

Beltijden: stel in het dashboard per reeks de dagen en een tijdvak tussen 7:00 en 21:00 in. Neemt niemand op, dan probeert de assistent het tot 3 keer, met 2 uur ertussen, binnen die tijden.

Sms vóór het gesprek: als dit voor de reeks aanstaat (dashboard), krijgt de persoon een kort bericht van het nummer waarmee de assistent gaat bellen, standaard 20 minuten vóór het eerste gesprek (5–120 min). Zou het eerste gesprek eerder plaatsvinden, dan schuift het op zodat de sms altijd eerst verstuurd wordt, binnen de beltijden. Een mislukte sms (de persoon antwoordde STOP, de sms-bundel is op) houdt het gesprek nooit tegen. Elke sms telt mee voor je sms-bundel en is in het dashboard te zien onder Sms.

Begroeting: in het dashboard stelt u per stap (of als standaard voor de hele reeks) de eerste zin van het gesprek in — een vaste tekst met de variabelen {first_name}, {first_name_vocative} (Poolse vocatief, bijv. Krystianie), {assistant_name}, Telofia (bedrijfsnaam in gesprekken uit de instellingen van de assistent) en {name}, met een aparte variant voor aanmeldingen zonder naam — of een modus waarin de assistent de eerste zin opstelt uit name, context, het doel van de stap en het resultaat van de tools „Aan het begin van het gesprek”. Dat er een AI-assistent belt en dat het gesprek wordt opgenomen, voegen we altijd toe aan de eerste zin als de tekst het niet zegt.

Iemand uit de reeks verwijderen

POST/api/outreach/unenroll

Heeft iemand de gesprekken niet meer nodig (bijvoorbeeld na de eerste verkoop, of na opzegging), stop dan zijn of haar reeks met dezelfde sleutel. Geplande gesprekken worden direct geannuleerd. Een gesprek dat al loopt wordt niet onderbroken, maar daarna volgen geen gesprekken meer.

  • external_id | enrollment_id | phoneverplicht
    Precies één van: external_id (zoals verstuurd bij inschrijving), enrollment_id (uit het antwoord op de inschrijving) of phone.
  • reasonoptioneel
    Optionele reden, maximaal 200 tekens, bewaard in het auditlogboek van je account.
  • 200: gestopt (changed: true) of al afgerond: status stopped, completed of failed (changed: false). Veilig om te herhalen.
  • 400: invalid_request (geen of meer dan één identificatie) of invalid_phone
  • 401: invalid_key
  • 404: not_found, deze inschrijving bestaat niet in deze reeks
  • 429: rate_limited
curl
curl -X POST "https://telofia.com/api/outreach/unenroll" \
  -H "Authorization: Bearer tlo_…" \
  -H "Content-Type: application/json" \
  -d '{"external_id":"user_123","reason":"first_sale"}'
200 OK
{
  "ok": true,
  "enrollment_id": "8f7e6d5c-4b3a-4c2d-9e1f-0a9b8c7d6e5f",
  "external_id": "user_123",
  "status": "stopped",
  "changed": true
}

Gespreksresultaten in webhooks

call.started en call.completed voor gesprekken uit een reeks bevatten callback_id, external_id en outreach { sequence_id, enrollment_id, external_id, step } (allemaal null bij inkomende gesprekken). Onbeantwoorde pogingen leveren geen gesprek op, dus daarvoor komt er geen call.completed: na de laatste poging ontvang je outbound_call.finished met status failed en last_error no_answer. Herhaalpogingen van één stap tellen als één resultaat. call.completed bevat ook caller_spoke en caller_words (hoeveel woorden de persoon zei). Als iemand opnam en tijdens of direct na de begroeting zonder iets te zeggen ophing, is end_reason caller_hangup_greeting.

call.completed voor een gesprek uit een reeks
{
  "id": "5b1f0c6e-2a8d-4f3e-9d51-7c0a4e2b9f10",
  "object": "call",
  "assistant_id": "0e6f4c8a-3b2d-4a1e-8f7c-5d9b2a1c3e4f",
  "phone_number_id": "4d3c2b1a-0f9e-4d8c-b7a6-958473625140",
  "contact_id": "9a8b7c6d-5e4f-4a3b-2c1d-0e9f8a7b6c5d",
  "direction": "outbound",
  "status": "completed",
  "from": "+48732127980",
  "to": "+48601234567",
  "started_at": "2026-10-01T09:14:03Z",
  "ended_at": "2026-10-01T09:17:41Z",
  "duration_sec": 218,
  "summary": "Jan added the company name and tax ID; asked for a call tomorrow at 10:00 about payments.",
  "outcome": "message",
  "sentiment": "positive",
  "tags": [],
  "has_recording": true,
  "end_reason": "agent_hangup",
  "callback_id": "0f1e2d3c-4b5a-4968-8776-655443322110",
  "external_id": "gapli-evt-1842",
  "outreach": {
    "sequence_id": "11111111-1111-4111-8111-111111111111",
    "enrollment_id": "8f7e6d5c-4b3a-4c2d-9e1f-0a9b8c7d6e5f",
    "external_id": "gapli-evt-1842",
    "step": 1
  },
  "caller_spoke": true,
  "caller_words": 112
}

Agenda, CRM en automatisering

Kant-en-klare koppelingen, zonder code

  • Google Agenda

    De assistent controleert de echte beschikbaarheid en boekt, verplaatst en annuleert afspraken in je agenda. Hij leest alleen bezette tijden, nooit titels van afspraken.

  • HubSpot

    Elk gesprek wordt bij het contact vastgelegd, boekingen worden meetings en terugbelverzoeken taken. Optioneel maakt hij contacten en deals aan.

  • Pipedrive

    Gesprekken als activiteiten of notities, boekingen als afspraken, terugbelverzoeken als activiteiten. Optioneel maakt hij personen, deals of leads aan.

  • Zapier, Make en n8n

    Stapsgewijze handleidingen in het dashboard. Ze draaien op webhooks en de API, die in elk abonnement zitten.

Abonnementen en limieten

Welk abonnement je nodig hebt

Limieten per account: verzoeken per minuut (ook per sleutel, glijdend venster) en per dag (UTC). Boven een limiet krijg je 429 rate_limited met een Retry-After-header.

AbonnementVerzoeken / minVerzoeken / dagAPI-sleutelsWebhooks
Gratis proefperiode (testtoegang)201.00011
Line302.00022
Desk6010.00055
Office18050.0001010
Network600200.0002520

Spraakwidget voor je website: vanaf het Office-abonnement.

Hogere limieten nodig? Neem contact op, dan verhogen we ze voor je account.

Early access: hoor bij de eerste bedrijven die de telefoon door AI laten opnemen.

Klaar om te koppelen?

Start de gratis proefperiode, maak een test-API-sleutel aan en verstuur binnen een paar minuten je eerste verzoek. De API zit in elk abonnement, vanaf Line.