API - Telefonie / CTI Admin

📕 Für Administratoren und Entwickler.

Die CTI-Endpunkte (Computer-Telephony-Integration) ermöglichen die Anbindung einer externen CTI-Middleware an spiritflow. Typischer Anwendungsfall: Eine Middleware verbindet sich mit der Telefonanlage (z.B. FritzBox, Asterisk) und kommuniziert über diese API mit spiritflow.

Basis-Pfad: /api/v1/cti

DSGVO-Hinweis: Die CTI-Integration verarbeitet Telefonnummern anrufender Personen. Stellen Sie sicher, dass die Speicherung und Verarbeitung dieser Daten mit Ihrer Datenschutzerklaerung und den gesetzlichen Anforderungen vereinbar ist. Telefonnummern werden ausschließlich mandantenweit gespeichert und nicht an Dritte weitergegeben.


Ablauf einer CTI-Integration

Telefonanlage <-> CTI-Middleware <-> spiritflow Public API
  1. Eingehender Anruf: TK-Anlage meldet Anruf an die Middleware. Middleware ruft POST /events/incoming-call auf. spiritflow zeigt dem zugeordneten Benutzer einen Screen-Pop mit Kundeninformationen.
  2. Click-to-Dial: Benutzer klickt eine Telefonnummer in spiritflow an. spiritflow sendet den Webhook cti.dial_requested. Die Middleware empfaengt den Webhook und initiiert den Anruf über die TK-Anlage.
  3. Reverse Lookup: Middleware oder Frontend fragt GET /lookup an. spiritflow ordnet die Nummer einem Kunden zu.

Rufnummernsuche (Reverse Lookup)

GET /api/v1/cti/lookup?phone={nummer}

Scope: cti:read

Sucht anhand einer Telefonnummer nach passenden Kunden und Kontaktpersonen. Die Nummer wird automatisch normalisiert (Sonderzeichen, Landesvorwahl). Die Suche erfolgt per Suffix-Matching über die letzten 7 Ziffern.

Query-Parameter:

ParameterTypPflichtBeschreibung
phonestringJaTelefonnummer (beliebiges Format)

Antwort:

{
  "data": [
    {
      "customerId": 42,
      "customerName": "Muster GmbH",
      "customerNumber": "K-2025-001",
      "matchedField": "phone",
      "matchedNumber": "+49 211 12345678",
      "contactPerson": null
    },
    {
      "customerId": 55,
      "customerName": "Beispiel AG",
      "customerNumber": "K-2025-015",
      "matchedField": "mobile",
      "matchedNumber": "+49 170 9876543",
      "contactPerson": {
        "id": 12,
        "firstName": "Anna",
        "lastName": "Müller",
        "matchedField": "mobile",
        "matchedNumber": "+49 170 9876543"
      }
    }
  ],
  "meta": {
    "requestId": "...",
    "timestamp": "2025-03-01T10:30:00Z"
  }
}

cURL-Beispiel:

curl -X GET "https://app.spiritflow.team/api/v1/cti/lookup?phone=+4921112345678" \
  -H "X-API-Key: sf_live_xxxxx"

Hinweis: Die Suche durchsucht die Felder phone und mobile sowohl bei Kunden als auch bei aktiven Kontaktpersonen. Ergebnisse sind auf den eigenen Mandanten beschränkt. Archivierte Kunden werden nicht gefunden.


Nebenstellen-Zuordnung (Device Mappings)

GET /api/v1/cti/device-mappings

Scope: cti:read

Liefert eine Liste aller Benutzer, die eine CTI-Nebenstelle konfiguriert haben. Die Middleware nutzt diese Information, um eingehende Anrufe dem richtigen Benutzer zuzuordnen.

Antwort:

{
  "data": [
    {
      "userId": 2,
      "username": "florian.cremer",
      "displayName": "Florian Cremer",
      "device": "201"
    },
    {
      "userId": 5,
      "username": "anna.mueller",
      "displayName": "Anna Müller",
      "device": "**610"
    }
  ],
  "meta": {
    "requestId": "...",
    "timestamp": "2025-03-01T10:30:00Z"
  }
}

cURL-Beispiel:

curl -X GET "https://app.spiritflow.team/api/v1/cti/device-mappings" \
  -H "X-API-Key: sf_live_xxxxx"

Hinweis: Administratoren konfigurieren die Nebenstellen der einzelnen Benutzer in spiritflow unter Einstellungen > Benutzerverwaltung > Benutzer-Dialog > Tab Arbeitseinstellungen > Telefonie (CTI).


Eingehenden Anruf melden (Screen-Pop)

POST /api/v1/cti/events/incoming-call

Scope: cti:write

Meldet einen eingehenden Anruf. spiritflow fuehrt automatisch eine Rufnummernsuche durch und zeigt dem zugeordneten Benutzer eine Echtzeit-Benachrichtigung (Screen-Pop) mit Kundeninformationen an.

Request-Body:

FeldTypPflichtBeschreibung
callerNumberstringJaAnrufende Telefonnummer
calledDevicestringJaAngerufene Nebenstelle (z.B. "201", "**610")
callIdstringNeinEindeutige Call-ID der TK-Anlage
timestampstringNeinISO 8601 Zeitstempel des Anrufs

cURL-Beispiel:

curl -X POST "https://app.spiritflow.team/api/v1/cti/events/incoming-call" \
  -H "X-API-Key: sf_live_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "callerNumber": "+49 211 12345678",
    "calledDevice": "201",
    "callId": "call-2025-03-01-001"
  }'

Antwort: HTTP 202 Accepted

{
  "data": {
    "processed": true,
    "targetUserId": 2,
    "customerFound": true,
    "customerId": 42
  },
  "meta": {
    "requestId": "...",
    "timestamp": "2025-03-01T10:30:00Z"
  }
}

Verhalten:

SituationErgebnis
Nebenstelle zugeordnet + Kunde gefundenScreen-Pop mit Kundenname, Klick navigiert zum Kunden
Nebenstelle zugeordnet + Kunde unbekanntScreen-Pop mit Telefonnummer
Nebenstelle nicht zugeordnetprocessed: false, keine Benachrichtigung

Anruf angenommen melden (Call Connected)

POST /api/v1/cti/events/call-connected

Scope: cti:write

Meldet an spiritflow, dass ein Anruf auf einer bestimmten Nebenstelle angenommen wurde. Die Anruf-Benachrichtigungen werden bei allen anderen Benutzern entfernt.

Request-Body:

FeldTypPflichtBeschreibung
callIdstringJaEindeutige Call-ID der TK-Anlage
devicestringJaNebenstelle, die den Anruf angenommen hat
callerNumberstringNeinAnrufende Telefonnummer

cURL-Beispiel:

curl -X POST "https://app.spiritflow.team/api/v1/cti/events/call-connected" \
  -H "X-API-Key: sf_live_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "callId": "call-2025-03-01-001",
    "device": "201"
  }'

Antwort: HTTP 202 Accepted

{
  "data": {
    "processed": true,
    "answeredByUserId": 2,
    "notificationsDismissed": 3
  },
  "meta": {
    "requestId": "...",
    "timestamp": "2025-03-01T10:30:00Z"
  }
}

Anruf-Ende melden (Call Ended)

POST /api/v1/cti/events/call-ended

Scope: cti:write

Meldet das Ende eines Anrufs an spiritflow. Bei nicht angenommenen Anrufen (answered: false) werden die zugehoerigen Ring-Benachrichtigungen entfernt. Bei angenommenen Anrufen wird ein Aktivitaets-Eintrag im Rueckblick angelegt.

Request-Body:

FeldTypPflichtBeschreibung
callIdstringJaEindeutige Call-ID der TK-Anlage
durationnumberNeinDauer des Anrufs in Sekunden (null wenn nicht angenommen)
answeredbooleanNeinOb der Anruf angenommen wurde (Standard: false)
answeredByDevicestringNeinNebenstelle, die den Anruf angenommen hat
callerNumberstringNeinAnrufende Telefonnummer

cURL-Beispiel:

curl -X POST "https://app.spiritflow.team/api/v1/cti/events/call-ended" \
  -H "X-API-Key: sf_live_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "callId": "call-2025-03-01-001",
    "duration": 120,
    "answered": true,
    "answeredByDevice": "201",
    "callerNumber": "+49 211 12345678"
  }'

Antwort: HTTP 202 Accepted

{
  "data": {
    "processed": true,
    "notificationsRemoved": 1,
    "activityCreated": true
  },
  "meta": {
    "requestId": "...",
    "timestamp": "2025-03-01T10:30:00Z"
  }
}

Click-to-Dial (Webhook)

Click-to-Dial wird nicht über die Public API aufgerufen, sondern über einen Webhook-Event. Wenn ein Benutzer in spiritflow auf eine Telefonnummer klickt, wird der Webhook cti.dial_requested an alle konfigurierten Webhook-Endpunkte gesendet.

Webhook-Payload:

{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "event": "cti.dial_requested",
  "created_at": "2025-03-01T10:30:00Z",
  "source": "ui",
  "actor": {
    "id": 2,
    "type": "user",
    "name": "Florian Cremer"
  },
  "data": {
    "targetNumber": "+49 211 12345678",
    "sourceDevice": "201",
    "userId": 2,
    "username": "florian.cremer",
    "customerId": 42,
    "contactPersonId": null
  },
  "changes": {}
}

Die CTI-Middleware empfaengt diesen Webhook und initiiert den Anruf von der Nebenstelle (sourceDevice) zur Zielnummer (targetNumber) über die Telefonanlage.

Voraussetzungen für Click-to-Dial:

  1. Webhook mit Event cti.dial_requested muss eingerichtet sein
  2. Der Benutzer muss eine Nebenstelle konfiguriert haben
  3. Die CTI-Middleware muss den Webhook empfangen und verarbeiten