Telofy
Telofy is an AI phone receptionist for businesses in Germany, from sole traders to mid-sized companies. It answers calls on the existing phone number whenever nobody can pick up, clarifies the caller's request, books, reschedules or cancels appointments via cal.com (which also connects Google and Outlook calendars), takes orders and forwards urgent calls to a phone number based on rules. After every call, a ready-to-act summary appears in the portal and the business is notified by e-mail, Telegram or push. 30 voices, more than 20 languages, conversation templates for 62 professions, open API, signed webhooks and an MCP server. Callers are told they are speaking with an AI, no audio is stored and data is processed in the EU. Plans start at 79 EUR per month with annual billing.
Dokümantasyon
Telofy für Entwickler.
Schnittstelle, Webhooks und MCP: Anrufe, Anliegen und Termine holen, Ereignisse sicher empfangen, KI-Assistenten anbinden. Basis-Adresse https://api.telofy.app.
Überblick
Die Telofy-Schnittstelle richtet sich an Server, nicht an Browser. Sie ist für den Zugriff von Ihrer Software, Ihrer Agentur oder Ihrem KI-Assistenten gedacht. Browser-Zugriff (CORS) ist nicht vorgesehen.
| Basis-Adresse | https://api.telofy.app |
| Format | JSON, UTF-8 |
| Anmeldung | Authorization: Bearer tlfy_… |
| Lesen | Anrufe, Anliegen, Termine |
| Schreiben | Ausgehenden Anruf auslösen (ab Business) |
| Ratenlimit | 60 Anfragen pro Minute und Schlüssel |
| Ereignisse | Webhooks, signiert |
| KI-Assistenten | MCP, Streamable HTTP unter /mcp |
Eine OpenAPI-Beschreibung gibt es derzeit nicht. Diese Seite ist die Referenz. Sie gibt es auch als reinen Markdown-Text unter /entwickler.md.
Authentifizierung
Jede Anfrage trägt einen API-Schlüssel im Kopf-Feld Authorization.
Authorization: Bearer tlfy_IhrSchluessel
- Einen Schlüssel erstellen Sie im Portal unter Integrationen > API-Schlüssel (Knopf „Schlüssel erstellen“). Er beginnt mit
tlfy_und wird nur einmal vollständig angezeigt. Bewahren Sie ihn wie ein Passwort auf. - Es sind bis zu drei aktive Schlüssel möglich. Ein widerrufener Schlüssel funktioniert nicht mehr.
- Der Betrieb, auf den sich eine Anfrage bezieht, ergibt sich allein aus dem Schlüssel. Eine Angabe in der Anfrage kann ihn nicht ändern.
- Es gibt keine Berechtigungsstufen. Derselbe Schlüssel liest und kann, ab dem Tarif Business, Anrufe auslösen. Geben Sie ihn deshalb nur an Systeme, denen Sie beides zutrauen.
- Fehlt der Schlüssel, ist er falsch oder widerrufen, antwortet Telofy immer gleich:
{ "error": "unauthorized" }
Endpunkte
| Methode | Pfad | Zweck |
|---|---|---|
| GET | /api/v1/calls | Anrufe, neueste zuerst |
| GET | /api/v1/outcomes | Anliegen aus Anrufen: Lead, Ticket, Termin, Auskunft, Bestellung, Sonstiges |
| GET | /api/v1/appointments | Nur Anliegen vom Typ Termin |
| POST | /api/v1/outbound-calls | Ausgehenden Anruf auslösen (ab Business) |
| POST | /mcp | MCP für KI-Assistenten |
Anrufe abrufen
GET /api/v1/calls
curl -H "Authorization: Bearer tlfy_IhrSchluessel" "https://api.telofy.app/api/v1/calls?limit=2"
{
"page": 1,
"limit": 2,
"calls": [
{
"id": "call_9f2a1b",
"startedAt": "2026-07-14T10:15:00.000Z",
"endedAt": "2026-07-14T10:19:30.000Z",
"direction": "inbound",
"status": "completed",
"callerNumber": "+4930555099",
"durationSeconds": 270,
"summary": "Anrufer möchte einen Termin für nächsten Dienstag.",
"outcomeType": "appointment",
"employeeName": "Anna"
},
{
"id": "call_3c7d2e",
"startedAt": "2026-07-14T09:00:00.000Z",
"endedAt": "2026-07-14T09:04:12.000Z",
"direction": "inbound",
"status": "completed",
"callerNumber": "+4930555010",
"durationSeconds": 252,
"summary": "Anrufer fragt nach Öffnungszeiten am Samstag.",
"outcomeType": "lead",
"employeeName": "Anna"
}
]
}
| Feld | Bedeutung |
|---|---|
id | Kennung des Anrufs |
startedAt, endedAt | Beginn und Ende, ISO 8601 |
direction | inbound oder outbound |
status | Status des Anrufs, zum Beispiel completed |
callerNumber | Rufnummer des Anrufers |
durationSeconds | Dauer in Sekunden |
summary | Zusammenfassung des Gesprächs |
outcomeType | Art des Anliegens, falls eines entstand |
employeeName | Name des Mitarbeiters, der den Anruf geführt hat |
Das Gesprächsprotokoll ist nicht Teil der Antwort. Es steht nur im Portal und nur, wenn Sie es dort eingeschaltet haben.
Anliegen abrufen
GET /api/v1/outcomes
curl -H "Authorization: Bearer tlfy_IhrSchluessel" "https://api.telofy.app/api/v1/outcomes?limit=2"
{
"page": 1,
"limit": 2,
"outcomes": [
{
"id": "outcome_7b1a4c",
"type": "appointment",
"title": "Termin am Dienstag, 10:00 Uhr",
"structuredData": {
"requestedTime": "2026-07-21T10:00:00.000Z",
"contactName": "Frau Beispiel"
},
"createdAt": "2026-07-14T10:19:35.000Z",
"callId": "call_9f2a1b",
"callerNumber": "+4930555099",
"callStartedAt": "2026-07-14T10:15:00.000Z",
"callSummary": "Anrufer möchte einen Termin für nächsten Dienstag.",
"handlingStatus": "open"
},
{
"id": "outcome_2e9f31",
"type": "lead",
"title": "Interesse an Beratungstermin",
"structuredData": {
"interesse": "Erstberatung"
},
"createdAt": "2026-07-14T09:04:20.000Z",
"callId": "call_3c7d2e",
"callerNumber": "+4930555010",
"callStartedAt": "2026-07-14T09:00:00.000Z",
"callSummary": "Anrufer fragt nach Öffnungszeiten am Samstag.",
"handlingStatus": "open"
}
]
}
| Feld | Bedeutung |
|---|---|
type | lead, ticket, appointment, info, other, order oder emergency |
title | Kurzer Titel des Anliegens |
structuredData | Erfasste Angaben. Die Felder hängen von der Art des Anliegens ab |
callId | Verweis auf den Anruf |
handlingStatus | Bearbeitungsstand im Portal: open (offen), in Arbeit oder erledigt |
Termine abrufen
GET /api/v1/appointments
Wie /api/v1/outcomes, aber auf Anliegen vom Typ Termin beschränkt. Die Liste heißt in der Antwort appointments.
curl -H "Authorization: Bearer tlfy_IhrSchluessel" "https://api.telofy.app/api/v1/appointments"
{
"page": 1,
"limit": 50,
"appointments": [
{
"id": "outcome_7b1a4c",
"type": "appointment",
"title": "Termin am Dienstag, 10:00 Uhr",
"structuredData": {
"requestedTime": "2026-07-21T10:00:00.000Z",
"contactName": "Frau Beispiel"
},
"createdAt": "2026-07-14T10:19:35.000Z",
"callId": "call_9f2a1b",
"callerNumber": "+4930555099",
"callStartedAt": "2026-07-14T10:15:00.000Z",
"callSummary": "Anrufer möchte einen Termin für nächsten Dienstag.",
"handlingStatus": "open"
}
]
}
Paginierung
Alle Listen liefern die neuesten Einträge zuerst.
pagebeginnt bei 1 (Vorgabe 1).limitist 50 (Vorgabe) und höchstens 100.- Die Antwort enthält
pageundlimit, aber keinen Gesamtzähler und keinen Cursor. Blättern Sie weiter, bis eine Seite weniger Einträge alslimitliefert. - Filterparameter gibt es nicht. Es werden nur
pageundlimitgelesen.
curl -H "Authorization: Bearer tlfy_IhrSchluessel" "https://api.telofy.app/api/v1/calls?page=2&limit=25"
Ratenlimit
Pro Schlüssel sind 60 Anfragen pro Minute erlaubt, zusammengezählt über alle Endpunkte und alle MCP-Aufrufe. Gezählt wird erst nach erfolgreicher Anmeldung. Wird das Limit überschritten, antwortet Telofy bis zum Ende der Minute mit Status 429:
{ "error": "rate_limited" }
Warten Sie nach einer 429-Antwort kurz, bevor Ihr Programm es erneut versucht.
Fehlercodes
Fehler kommen als JSON mit dem Feld error, zum Beispiel { "error": "unauthorized" }.
| Status | error | Bedeutung |
|---|---|---|
| 400 | invalid_request, purpose_required, invalid_number, employee_id_required | Die Anfrage ist unvollständig oder fehlerhaft |
| 401 | unauthorized | Schlüssel fehlt, ist falsch oder widerrufen |
| 403 | plan_not_eligible | Die Funktion gibt es im aktuellen Tarif nicht |
| 404 | not_found, employee_not_found | Pfad unbekannt, oder die employeeId passt zu keinem Mitarbeiter |
| 405 | method_not_allowed | Falsche Methode. Der Kopf Allow nennt die erlaubte |
| 409 | duplicate_external_reference | Die externalReference wurde schon verwendet |
| 429 | rate_limited | Mehr als 60 Anfragen in einer Minute |
| 500 | internal_error | Fehler bei Telofy |
| 502 | provider_request_failed | Der Anruf konnte technisch nicht gestartet werden |
Ausgehender Anruf
POST /api/v1/outbound-calls. Verfügbar ab dem Tarif Business. In anderen Tarifen antwortet Telofy mit 403 plan_not_eligible.
| Feld | Pflicht | Bedeutung |
|---|---|---|
toNumber | ja | Zielnummer im internationalen Format, zum Beispiel +4930123456 |
purpose | ja | Freitext: worum es im Anruf geht |
employeeId | nein | Nur nötig, wenn mehr als ein Mitarbeiter eine Nummer hat |
externalReference | nein | Ihre eigene Kennung, zum Beispiel eine Auftragsnummer. Ein zweiter Aufruf mit demselben Wert wird mit 409 abgelehnt |
Der Mitarbeiter, der anruft, braucht eine zugewiesene Rufnummer.
curl -X POST -H "Authorization: Bearer tlfy_IhrSchluessel" -H "Content-Type: application/json" \
-d '{"toNumber": "+4930111111", "purpose": "Ihr Auto ist fertig zur Abholung."}' \
"https://api.telofy.app/api/v1/outbound-calls"
{ "callId": "call_9f2a1b" }
Die Antwort (Status 202) bestätigt nur, dass der Anruf gestartet wurde. Was besprochen wurde, erfahren Sie asynchron über den Webhook call.completed, sobald das Gespräch beendet und ausgewertet ist. Der MCP-Server löst keine Anrufe aus.
Webhook-Ereignisse
Webhooks legen Sie im Portal unter Integrationen > Webhooks an (Knopf „Webhook anlegen“). Sie tragen eine https-Adresse ein, wählen die Ereignisse und optional einen einzelnen Mitarbeiter. Das Signatur-Geheimnis (whsec_…) erscheint nur einmal in der Antwort.
Telofy sendet die Ereignisse nach der Anrufauswertung, nicht im Moment des Auflegens, und stellt sie asynchron zu. Planen Sie also mit einigen Minuten Abstand zum Ende des Gesprächs und nicht mit „in Echtzeit“.
| Ereignis | Wann |
|---|---|
call.completed | Nach jedem ausgewerteten Anruf |
outcome.created | Wenn der Anruf ein Anliegen vom Typ lead, ticket, appointment oder order ergab |
appointment.created | Zusätzlich zu outcome.created, wenn das Anliegen ein Termin war |
Jede Nachricht ist ein POST mit Content-Type: application/json und dem Kopf X-Telofy-Signature. Der Rumpf hat die Form { "event": …, "data": … }. Ein Gesprächsprotokoll enthält er nie, nur den Link callUrl ins Portal. Weitere Köpfe oder eine eigene Ereignis-Kennung gibt es nicht. Entdoppeln Sie über callId und outcomeId im Rumpf.
call.completed
{
"event": "call.completed",
"data": {
"callId": "call_9f2a1b",
"completedAt": "2026-07-14T09:04:12.000Z",
"durationSeconds": 252,
"employeeName": "Anna",
"summary": "Anrufer fragt nach Öffnungszeiten am Samstag.",
"outcomeType": "lead",
"callUrl": "https://telofy.app/calls/call_9f2a1b"
}
}
outcome.created
{
"event": "outcome.created",
"data": {
"outcomeId": "outcome_2e9f31",
"callId": "call_3c7d2e",
"createdAt": "2026-07-14T09:04:20.000Z",
"outcomeType": "lead",
"summary": "Interesse an Beratungstermin",
"callUrl": "https://telofy.app/calls/call_3c7d2e"
}
}
appointment.created
{
"event": "appointment.created",
"data": {
"outcomeId": "outcome_7b1a4c",
"callId": "call_9f2a1b",
"createdAt": "2026-07-14T10:19:35.000Z",
"requestedTime": "2026-07-21T10:00:00.000Z",
"contactName": "Frau Beispiel",
"contactPhone": "+4930555099",
"callUrl": "https://telofy.app/calls/call_9f2a1b"
}
}
Zustellung
- Als Erfolg gilt jeder Status 2xx. Antworten Sie innerhalb von 10 Sekunden.
- Bei Misserfolg gibt es bis zu fünf Versuche insgesamt, mit Wartezeiten von 1, 5, 30 und 120 Minuten dazwischen. Danach gilt die Zustellung als gescheitert.
- Weiterleitungen (Redirects) werden nicht verfolgt. Die Adresse muss direkt antworten.
- Nur
https://, ohne Zugangsdaten in der Adresse und ohne private oder interne Hostnamen wielocalhostoder.local. Telofy prüft das beim Versand erneut. - Die Reihenfolge ist nicht garantiert. Ein Wiederholungsversuch trägt denselben Rumpf, aber einen neuen Zeitstempel in der Signatur.
- Bis zu fünf Webhooks je Betrieb. Das Zustellprotokoll zeigt die letzten 50 Zustellungen je Webhook und wird nach 30 Tagen gelöscht.
- Der Knopf „Test-Ereignis senden“ im Portal schickt ein
call.completedmit"test": trueauf der obersten Ebene,callIdtestundoutcomeTypetest. Höchstens einmal pro Minute und Webhook. Ein Wiederholen vergangener Ereignisse gibt es nicht.
Signatur prüfen
Jede Nachricht trägt den Kopf
X-Telofy-Signature: t=1752482652,v1=<64 Hexzeichen>
t ist die Unix-Zeit in Sekunden des Versandversuchs. v1 ist der HMAC-SHA256 als Hexwert über den Text <t>.<Rohkörper>, gebildet mit dem Geheimnis Ihres Webhooks (whsec_…).
So prüfen Sie:
- Lesen Sie den Anfragetext unverändert als Text. Wenn Ihr Framework ihn schon in ein Objekt umgewandelt hat, stimmt die Prüfsumme nicht mehr. In Express nutzen Sie dafür
express.raw({ type: "application/json" }), in Flaskrequest.get_data(). - Trennen Sie
tundv1aus dem Kopf. - Bilden Sie den HMAC-SHA256 über
t, einen Punkt und den Rohkörper. - Vergleichen Sie mit
v1in konstanter Zeit. - Lehnen Sie Nachrichten ab, deren Zeitstempel mehr als fünf Minuten von Ihrer Uhr abweicht.
Node.js
import { createHmac, timingSafeEqual } from "node:crypto";
// rohKoerper: der unveränderte Anfragetext als String, nicht das geparste Objekt.
export function istTelofySignaturGueltig(
geheimnis,
rohKoerper,
kopfzeile,
jetzt = Math.floor(Date.now() / 1000)
) {
const teile = {};
for (const stueck of kopfzeile.split(",")) {
const i = stueck.indexOf("=");
if (i > 0) teile[stueck.slice(0, i).trim()] = stueck.slice(i + 1).trim();
}
const t = Number(teile.t);
if (!Number.isFinite(t) || !teile.v1) return false;
if (Math.abs(jetzt - t) > 300) return false; // höchstens fünf Minuten alt
const erwartet = createHmac("sha256", geheimnis)
.update(\`${teile.t}.${rohKoerper}\`, "utf8")
.digest("hex");
const a = Buffer.from(erwartet);
const b = Buffer.from(teile.v1);
return a.length === b.length && timingSafeEqual(a, b);
}
Python
import hashlib
import hmac
import time
def telofy_signatur_gueltig(geheimnis, roh_koerper, kopfzeile, jetzt=None):
"""roh_koerper: der unveränderte Anfragetext als bytes."""
teile = {}
for stueck in kopfzeile.split(","):
schluessel, _, wert = stueck.partition("=")
teile[schluessel.strip()] = wert.strip()
t_text = teile.get("t")
v1 = teile.get("v1")
if not t_text or not v1:
return False
try:
t = int(t_text)
except ValueError:
return False
jetzt = time.time() if jetzt is None else jetzt
if abs(jetzt - t) > 300: # höchstens fünf Minuten alt
return False
erwartet = hmac.new(
geheimnis.encode("utf-8"),
t_text.encode("utf-8") + b"." + roh_koerper,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(erwartet.encode("utf-8"), v1.encode("utf-8"))
MCP für KI-Assistenten
Der MCP-Server liegt unter https://api.telofy.app/mcp (Streamable HTTP, ohne Sitzungszustand, Antwort als JSON). Er nutzt denselben API-Schlüssel und dasselbe Ratenlimit wie die Schnittstelle.
| Werkzeug | Zweck | Parameter |
|---|---|---|
list_calls | Anrufe, neueste zuerst | page, limit (optional) |
list_outcomes | Anliegen aus Anrufen | page, limit (optional) |
list_appointments | Termine | page, limit (optional) |
Alle drei Werkzeuge lesen nur. Der MCP-Server löst keine Anrufe aus und ändert nichts.
Eine übliche Konfiguration für Assistenten, die MCP-Server über eine Adresse einbinden:
{
"mcpServers": {
"telofy": {
"url": "https://api.telofy.app/mcp",
"headers": {
"Authorization": "Bearer tlfy_IhrSchluessel"
}
}
}
}
Wo Sie das eintragen, unterscheidet sich je Assistent. Schauen Sie in dessen Anleitung nach der Stelle für entfernte MCP-Server. Ein Fehler beim Werkzeugaufruf, etwa durch einen ungültigen Schlüssel, kommt als Werkzeugfehler zurück und nicht als HTTP-Fehler.
Eigene Werkzeuge
Mit eigenen Werkzeugen ruft Telofy während eines Gesprächs eine https-Adresse Ihres Systems auf und liest die Antwort vor. Das ist die umgekehrte Richtung: Sie liefern Telofy Daten, statt welche abzuholen.
Anlegen im Portal unter Integrationen > Eigene Werkzeuge (Knopf „Werkzeug anlegen“). Sie legen fest:
- Funktionsname: Nur der Assistent sieht ihn, ohne Leerzeichen, zum Beispiel
lookup_shipping_status. - Beschreibung: Sie entscheidet, ob Telofy das Werkzeug im richtigen Moment aufruft. Schreiben Sie, wann es genutzt werden soll.
- Parameter-Schema (JSON): Welche Angaben Telofy aus dem Gespräch mitschickt.
- Ziel-Adresse: https, öffentlich erreichbar.
- Auth-Header-Name und Geheimnis (optional): Telofy sendet sie bei jedem Aufruf mit. Das Geheimnis wird verschlüsselt gespeichert.
Beispiel für ein Parameter-Schema:
{
"type": "OBJECT",
"properties": {
"order_number": { "type": "STRING", "description": "Bestellnummer" }
},
"required": ["order_number"]
}
Telofy sendet einen POST mit den Angaben unter arguments:
POST /versandstatus HTTP/1.1
Host: ihr-system.de
Content-Type: application/json
{ "arguments": { "order_number": "A-1042" } }
Ihr System antwortet mit Status 2xx und genau diesem Aufbau:
{ "result": "Die Bestellung A-1042 wurde heute mit DHL verschickt und kommt voraussichtlich morgen an." }
Grenzen:
- Bis zu 10 Werkzeuge je Betrieb.
- 4 Sekunden Zeit je Aufruf. Die Antwort darf höchstens 4.000 Byte groß sein, der Text in
resultwird auf 800 Zeichen gekürzt. - Weiterleitungen werden nicht verfolgt. Private und interne Adressen werden abgelehnt.
- Schlägt ein Aufruf fehl, sagt Telofy dem Anrufer ehrlich, dass es das gerade nicht nachsehen kann, und bietet an, das Anliegen aufzunehmen. Das Gespräch bricht nicht ab.
- Reservierte Namen lassen sich nicht vergeben.
Was die Schnittstelle nicht kann
- Sie schreibt keine Termine, Kontakte, Katalogpositionen oder Wissenseinträge. Die Pflege dieser Daten läuft über das Portal und den Import. Schreibend ist nur der ausgehende Anruf.
- Es gibt keine Filter, keinen Gesamtzähler und keine Berechtigungsstufen je Schlüssel.
- MCP liest nur.
- Webhooks haben keine Ereignis-Kennung im Kopf, keine Reihenfolgegarantie und keine Wiederholung vergangener Ereignisse auf Knopfdruck.
- Es gibt kein Programmierpaket (SDK) und keine OpenAPI-Datei.
- Eine eigene App für Zapier, Make oder n8n gibt es nicht. Die Anbindung läuft über deren Webhook-Bausteine, siehe Integrationen.
Fragen von Entwicklern.
Wie lautet die Basis-Adresse der Schnittstelle?
https://api.telofy.app. Die Anfragen gehen an Pfade wie /api/v1/calls und /mcp. Jede Anfrage braucht den Kopf Authorization: Bearer mit Ihrem Schlüssel.
Wie bekomme ich einen API-Schlüssel?
Im Portal unter Integrationen > API-Schlüssel erstellen Sie bis zu drei Schlüssel. Er wird nur einmal vollständig angezeigt und beginnt mit tlfy_.
Kann ich per Schnittstelle einen Anruf auslösen?
Ja, ab dem Tarif Business mit POST /api/v1/outbound-calls. Das Ergebnis des Gesprächs kommt anschließend über den Webhook call.completed.
Wie prüfe ich, dass ein Webhook von Telofy kommt?
Über den Kopf X-Telofy-Signature. Er enthält die Zeitangabe t und den HMAC-SHA256 v1 über t, einen Punkt und den Rohkörper, gebildet mit dem Geheimnis Ihres Webhooks. Beispiele für Node.js und Python stehen auf dieser Seite.
Sind Webhooks in Echtzeit?
Nein. Telofy sendet sie nach der Anrufauswertung und stellt sie asynchron zu. Bei Misserfolg gibt es bis zu fünf Versuche.