Developer-API

HeyAnnika Developer-API

Verbinden Sie HeyAnnika mit n8n, Zapier, Claude oder eigenen Tools — fünf Read-Endpoints, fünf Write-Endpoints, vier Outgoing-Webhook-Events, alles mit Bearer-Token-Auth und JSON-Responses.

Quickstart

In drei Schritten zum ersten Call

  1. 1

    Token erstellen

    Öffnen Sie als Inhaber Ihres Accounts Einstellungen → Integrationen → API-Tokens und legen Sie einen Token mit den benötigten Scopes an. Der Plaintext-Token beginnt mit ha_live_ und wird Ihnen nur ein einziges Mal angezeigt — kopieren Sie ihn sofort in Ihren Passwort-Manager.

  2. 2

    Ersten Call absetzen

    Setzen Sie den Token als Authorization: Bearer …-Header und rufen Sie einen beliebigen Read- oder Write-Endpoint:

    curl -X GET 'https://heyannika.de/api/v1/statistiken?zeitraum=7' \
      -H 'Authorization: Bearer ha_live_…'
  3. 3

    Response auswerten

    Sie erhalten ein JSON-Objekt zurück. Die Header X-RateLimit-Limit und X-RateLimit-Remaining zeigen Ihnen das verbliebene Kontingent in der laufenden Minute:

    {
      "zeitraum": 7,
      "anrufeGesamt": 41,
      "hotLeadsGesamt": 8,
      "durchschnittsdauer": 128,
      "anrufeProTag": [
        { "datum": "2026-05-04", "anzahl": 6 },
        { "datum": "2026-05-05", "anzahl": 5 }
      ]
    }
Authentifizierung

Bearer-Token im Authorization-Header

Jeder API-Call benötigt einen gültigen Token. Ohne Header oder mit ungültigem Token antwortet die API mit 401.

Header-Format

Authorization: Bearer ha_live_4f8a92…

Verfügbare Scopes

Beim Erstellen eines Tokens wählen Sie eine Teilmenge der unten gelisteten Scopes. Read-Scopes erlauben GET-Calls, Write-Scopes erlauben zusätzlich POST und PATCH auf den entsprechenden Ressourcen.

  • anrufe:read — Anrufe lesen
  • kontakte:read — Kontakte lesen
  • objekte:read — Objekte lesen
  • statistiken:read — KPIs lesen
  • kontakte:write — Kontakte anlegen + ändern
  • objekte:write — Objekte anlegen + ändern
  • outbound:write — Outbound-Anrufe planen

Token widerrufen

Verloren oder versehentlich geleakt? Öffnen Sie Einstellungen → Integrationen und klicken Sie neben dem Token auf Widerrufen. Der Token wird sofort ungültig — alle laufenden Integrationen erhalten ab dann 401. Erstellen Sie anschließend einen neuen Token und tauschen Sie ihn in Ihrem Tool aus.

Endpoints

Fünf Read-Endpoints, fünf Write-Endpoints, vier Outgoing-Webhook-Events

Alle Endpoints liefern JSON, alle Listen-Endpoints unterstützen Cursor-Pagination via cursor + limit. Schreib-Endpoints akzeptieren JSON-Bodies und antworten mit 201 (POST) oder 200 (PATCH).

GET

/api/v1/anrufe

anrufe:read

Listet Anrufe Ihres Teams (alle Mitglieder), neueste zuerst. Cursor-Pagination über die Anruf-ID — stabil auch bei kontinuierlichem Polling, da neu eintreffende Anrufe nicht zu Skips führen.

Query-Parameter

NameTypBeschreibungDefault
limitinteger1 bis 20050
cursorstringAnruf-ID, ab der weiterpaginiert wird
sinceISO-8601createdAt >= since
scoreintegerMindest-Lead-Score (1–5)

Response (gekürzt)

{
  "data": [
    {
      "id": "clz1abcdef0123",
      "anruferNummer": "+491701234567",
      "anruferName": "Familie Schmidt",
      "zusammenfassung": "Sucht 3-Zimmer-Wohnung in Eppendorf, max. 350 k€.",
      "leadScore": 4,
      "dauer": 142,
      "anrufTyp": "inbound",
      "createdAt": "2026-05-08T14:32:01.000Z",
      "kontaktId": "clz1ktnxyz9876"
    }
  ],
  "nextCursor": "clz1abcdef0123"
}

cURL-Beispiel

curl -X GET 'https://heyannika.de/api/v1/anrufe?limit=20&score=3' \
  -H 'Authorization: Bearer ha_live_…'
GET

/api/v1/anrufe/{id}

anrufe:read

Liefert einen einzelnen Anruf mit Detaildaten — Transkript und nächste Schritte inklusive. Das Transkript wird nur zurückgegeben, wenn der Anrufer der Speicherung eingewilligt hat (DSGVO/§7 UWG).

Query-Parameter

Keine Query-Parameter — der Endpoint liefert immer denselben Snapshot.

Response (gekürzt)

{
  "id": "clz1abcdef0123",
  "anruferNummer": "+491701234567",
  "anruferName": "Familie Schmidt",
  "dauer": 142,
  "anrufTyp": "inbound",
  "leadScore": 4,
  "zusammenfassung": "Sucht 3-Zimmer-Wohnung in Eppendorf …",
  "naechsteSchritte": "Exposé senden, Termin Mo 10:00 bestätigen.",
  "transkript": "Annika: Guten Tag, hier spricht …",
  "createdAt": "2026-05-08T14:32:01.000Z",
  "kontakt": {
    "id": "clz1ktnxyz9876",
    "name": "Familie Schmidt",
    "telefon": "+491701234567",
    "email": "schmidt@example.de"
  }
}

cURL-Beispiel

curl -X GET 'https://heyannika.de/api/v1/anrufe/clz1abcdef0123' \
  -H 'Authorization: Bearer ha_live_…'
GET

/api/v1/kontakte

kontakte:read

Listet alle Kontakte Ihres Teams. Cursor-Pagination, gelöschte Kontakte (Soft-Delete) sind ausgeschlossen.

Query-Parameter

NameTypBeschreibungDefault
limitinteger1 bis 20050
cursorstringKontakt-ID, ab der weiterpaginiert wird

Response (gekürzt)

{
  "data": [
    {
      "id": "clz1ktnxyz9876",
      "name": "Familie Schmidt",
      "telefon": "+491701234567",
      "email": "schmidt@example.de",
      "leadScore": 4,
      "consentErteilt": true,
      "createdAt": "2026-05-08T14:32:01.000Z"
    }
  ],
  "nextCursor": null
}

cURL-Beispiel

curl -X GET 'https://heyannika.de/api/v1/kontakte?limit=50' \
  -H 'Authorization: Bearer ha_live_…'
GET

/api/v1/objekte

objekte:read

Listet die Objekte Ihres Teams. Optional gefiltert nach Status. preis und groesse werden als Number geliefert (kein Decimal-String).

Query-Parameter

NameTypBeschreibungDefault
limitinteger1 bis 20050
cursorstringObjekt-ID, ab der weiterpaginiert wird
statusenumaktiv | verkauft | reserviert | deaktiviert

Response (gekürzt)

{
  "data": [
    {
      "id": "clz1obj4567abc",
      "titel": "3-Zi-ETW Eppendorf, Balkon, EBK",
      "adresse": "Eppendorfer Landstr. 12, 20249 Hamburg",
      "preis": 349000,
      "zimmer": 3,
      "groesse": 78.5,
      "status": "aktiv",
      "exposeUrl": "https://…/expose/clz1obj4567abc.pdf"
    }
  ],
  "nextCursor": null
}

cURL-Beispiel

curl -X GET 'https://heyannika.de/api/v1/objekte?status=aktiv' \
  -H 'Authorization: Bearer ha_live_…'
GET

/api/v1/statistiken

statistiken:read

Aggregierte KPIs über einen Zeitraum: Gesamtanrufe, Hot-Leads (Score ≥ 4), Durchschnittsdauer und eine Tagesreihe. Tagesgrenzen werden in Europe/Berlin gerechnet, damit DE-Statistiken auch nahe Mitternacht sauber sind.

Query-Parameter

NameTypBeschreibungDefault
zeitraumenum7 | 30 | 90 (Tage)30

Response (gekürzt)

{
  "zeitraum": 30,
  "anrufeGesamt": 248,
  "hotLeadsGesamt": 41,
  "durchschnittsdauer": 134,
  "anrufeProTag": [
    { "datum": "2026-04-09", "anzahl": 6 },
    { "datum": "2026-04-10", "anzahl": 9 }
  ]
}

cURL-Beispiel

curl -X GET 'https://heyannika.de/api/v1/statistiken?zeitraum=7' \
  -H 'Authorization: Bearer ha_live_…'
POST

/api/v1/kontakte

kontakte:write

Legt einen neuen Kontakt im Team an. Body als JSON. Triggert den Outgoing-Webhook kontakt.created, falls Sie eine aktive Webhook-Subscription für dieses Event haben.

Query-Parameter

NameTypBeschreibungDefault
namestringPflicht — 1 bis 200 Zeichen
telefonstringPflicht — 5 bis 30 Zeichen, internationales Format empfohlen
emailstringOptional — gültige E-Mail-Adresse
leadScoreintegerOptional — 0 bis 50
consentErteiltbooleanOptional — DSGVO-Einwilligungfalse
notizstringOptional — freie Notiz
kontaktTypenumkaufinteressent | mietinteressent | verkaufsinteressent | vermieter | sonstig

Response (gekürzt)

{
  "id": "clz1ktnxyz9876",
  "name": "Familie Schmidt",
  "telefon": "+491701234567",
  "email": "schmidt@example.de",
  "leadScore": 4,
  "createdAt": "2026-05-10T14:32:01.000Z"
}

cURL-Beispiel

curl -X POST 'https://heyannika.de/api/v1/kontakte' \
  -H 'Authorization: Bearer ha_live_…' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "Familie Schmidt",
    "telefon": "+491701234567",
    "email": "schmidt@example.de",
    "leadScore": 4,
    "consentErteilt": true,
    "kontaktTyp": "kaufinteressent"
  }'
PATCH

/api/v1/kontakte/{id}

kontakte:write

Aktualisiert einen vorhandenen Kontakt teilweise. Senden Sie nur die Felder, die geändert werden sollen — alles andere bleibt erhalten. Body-Felder identisch zu POST, alle optional.

Query-Parameter

NameTypBeschreibungDefault
namestringOptional — 1 bis 200 Zeichen
telefonstringOptional — 5 bis 30 Zeichen
emailstringOptional — gültige E-Mail-Adresse
leadScoreintegerOptional — 0 bis 5
consentErteiltbooleanOptional — DSGVO-Einwilligung
notizstringOptional — freie Notiz
kontaktTypenumkaufinteressent | mietinteressent | verkaufsinteressent | vermieter | sonstig

Response (gekürzt)

{
  "id": "clz1ktnxyz9876",
  "name": "Familie Schmidt",
  "telefon": "+491701234567",
  "email": "schmidt-neu@example.de",
  "leadScore": 5,
  "createdAt": "2026-05-10T14:32:01.000Z"
}

cURL-Beispiel

curl -X PATCH 'https://heyannika.de/api/v1/kontakte/clz1ktnxyz9876' \
  -H 'Authorization: Bearer ha_live_…' \
  -H 'Content-Type: application/json' \
  -d '{
    "email": "schmidt-neu@example.de",
    "leadScore": 5
  }'
POST

/api/v1/objekte

objekte:write

Legt ein neues Objekt im Team an. Adresse wird aus den Einzelfeldern strasse + hausnummer + plz + ort + land zusammengesetzt.

Query-Parameter

NameTypBeschreibungDefault
titelstringPflicht — Kurzbeschreibung des Objekts
strassestringPflicht
hausnummerstringPflicht
plzstringPflicht
ortstringPflicht
landstringOptionalDeutschland
preisnumberOptional — in Euro
zimmernumberOptional — Anzahl Zimmer
groessenumberOptional — Wohnfläche in m²
beschreibungstringOptional — freier Beschreibungstext
statusenumaktiv | verkauft | reserviert | deaktiviertaktiv

Response (gekürzt)

{
  "id": "clz1obj4567abc",
  "titel": "3-Zi-ETW Eppendorf, Balkon, EBK",
  "adresse": "Eppendorfer Landstr. 12, 20249 Hamburg",
  "preis": 349000,
  "zimmer": 3,
  "groesse": 78.5,
  "status": "aktiv",
  "exposeUrl": null
}

cURL-Beispiel

curl -X POST 'https://heyannika.de/api/v1/objekte' \
  -H 'Authorization: Bearer ha_live_…' \
  -H 'Content-Type: application/json' \
  -d '{
    "titel": "3-Zi-ETW Eppendorf, Balkon, EBK",
    "strasse": "Eppendorfer Landstr.",
    "hausnummer": "12",
    "plz": "20249",
    "ort": "Hamburg",
    "preis": 349000,
    "zimmer": 3,
    "groesse": 78.5,
    "status": "aktiv"
  }'
PATCH

/api/v1/objekte/{id}

objekte:write

Aktualisiert ein vorhandenes Objekt teilweise. Senden Sie nur die Felder, die geändert werden sollen. Alle Felder identisch zu POST, alle optional.

Query-Parameter

NameTypBeschreibungDefault
titelstringOptional
strassestringOptional
hausnummerstringOptional
plzstringOptional
ortstringOptional
landstringOptional
preisnumberOptional — in Euro
zimmernumberOptional
groessenumberOptional — Wohnfläche in m²
beschreibungstringOptional
statusenumaktiv | verkauft | reserviert | deaktiviert

Response (gekürzt)

{
  "id": "clz1obj4567abc",
  "titel": "3-Zi-ETW Eppendorf, Balkon, EBK",
  "adresse": "Eppendorfer Landstr. 12, 20249 Hamburg",
  "preis": 339000,
  "zimmer": 3,
  "groesse": 78.5,
  "status": "reserviert",
  "exposeUrl": null
}

cURL-Beispiel

curl -X PATCH 'https://heyannika.de/api/v1/objekte/clz1obj4567abc' \
  -H 'Authorization: Bearer ha_live_…' \
  -H 'Content-Type: application/json' \
  -d '{
    "preis": 339000,
    "status": "reserviert"
  }'
POST

/api/v1/outbound/rufe

outbound:write

Plant einen ausgehenden Anruf. Setzt nur den DB-Record. Der existierende Outbound-Cron pickt geplante Calls auf und initiiert sie. Voraussetzungen: Kontakt gehört zum Team, consentErteilt=true, kein DNC-Eintrag — sonst 403 mit Begründung.

Query-Parameter

NameTypBeschreibungDefault
kontaktIdstringPflicht — ID des Zielkontakts
objektTitelstringOptional — Objekt-Titel als Gesprächskontext
scenariostringOptional — Gesprächs-Szenario-ID

Response (gekürzt)

{
  "outboundCallId": "clz1out7788xy",
  "scheduledFor": "2026-05-10T15:00:00.000Z"
}

cURL-Beispiel

curl -X POST 'https://heyannika.de/api/v1/outbound/rufe' \
  -H 'Authorization: Bearer ha_live_…' \
  -H 'Content-Type: application/json' \
  -d '{
    "kontaktId": "clz1ktnxyz9876",
    "objektTitel": "3-Zi-ETW Eppendorf",
    "scenario": "expose-followup"
  }'
Outgoing Webhooks

Push-Events an Ihre URL — HMAC-signiert

HeyAnnika sendet bei vier Events einen JSON-POST an Ihre Empfänger-URL. Jede Anfrage trägt eine HMAC-SHA256-Signatur, damit Sie die Echtheit verifizieren können.

Verfügbare Events

Pro Webhook-Subscription wählen Sie eine Teilmenge der folgenden Events. Sie erhalten ausschließlich Events Ihres eigenen Teams.

  • anruf.createdbei jedem neuen Inbound-Anruf
  • anruf.hot_leadwenn der Lead-Score eines Anrufs ≥ 4 erreicht
  • termin.bookedwenn Annika einen Termin vereinbart
  • kontakt.createdwenn ein neuer Kontakt angelegt wird (auch via API)

Setup

Öffnen Sie Einstellungen → Integrationen → Webhooks Neuer Webhook anlegen → URL und gewünschte Events wählen. Das Secret wird Ihnen nur einmalig angezeigt — kopieren Sie es sofort in Ihren Passwort-Manager. Es wird zur HMAC-Signierung jeder Webhook-Auslieferung verwendet und lässt sich nachträglich nicht mehr abrufen.

Payload-Format

Alle Events folgen demselben Envelope: event, teamId, createdAt und ein event-spezifisches payload.

{
  "event": "anruf.created",
  "teamId": "ckxxxxxx",
  "createdAt": "2026-05-10T12:34:56.789Z",
  "payload": {
    "anrufId": "ckxxxxxx",
    "anruferName": "Max Mustermann",
    "anruferNummer": "+4940...",
    "leadScore": 4,
    "anrufTyp": "kauf",
    "zusammenfassung": "...",
    "kontaktId": "ckxxxxxx",
    "createdAt": "2026-05-10T12:34:56.000Z"
  }
}

Request-Header

HeaderWert
X-HeyAnnika-EventEvent-Name (z. B. anruf.created)
X-HeyAnnika-Signaturesha256=<hex> — HMAC-SHA256 des rohen Bodies mit Ihrem Secret
User-AgentHeyAnnika-Webhook/1.0
Content-Typeapplication/json

Signatur verifizieren

Berechnen Sie HMAC-SHA256 über den rohen, unveränderten Request-Body mit Ihrem Webhook-Secret und vergleichen Sie das Ergebnis mit dem Wert aus X-HeyAnnika-Signature — Konstantzeitvergleich verwenden, um Timing-Angriffe zu vermeiden.

Node.js

import { createHmac, timingSafeEqual } from "crypto"

function verifySignature(rawBody, signatureHeader, secret) {
  const expected = createHmac("sha256", secret).update(rawBody).digest("hex")
  const provided = signatureHeader.replace(/^sha256=/, "")
  return timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(provided, "hex"))
}

Python

import hmac, hashlib

def verify_signature(raw_body: bytes, signature_header: str, secret: str) -> bool:
    expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    provided = signature_header.replace("sha256=", "")
    return hmac.compare_digest(expected, provided)

Retry-Strategie & Timeout

Retry: Aktuell kein automatischer Retry. Der Failure-Count pro Webhook wird in der UI angezeigt; nach 10 Fehlversuchen empfehlen wir, den Webhook zu pausieren und die Empfänger-URL zu prüfen. Eine automatische Retry-Logik kommt in einer späteren Phase.

Timeout: 5 Sekunden serverseitig. Antworten Sie schnell mit 200 oder 204 und verarbeiten den Payload asynchron — etwa über eine interne Queue.

Rate Limits

60 Requests pro Minute pro Token

Großzügig genug für n8n-Sync-Polls und interaktive Claude- oder Zapier-Flows. Bei Überschreitung antwortet die API mit 429 Too Many Requests.

X-RateLimit-Limit

Aktuelles Maximum pro Minute (immer 60).

X-RateLimit-Remaining

Verbleibende Requests im aktuellen Minutenfenster.

Retry-After

Sekunden bis zum nächsten erlaubten Request — nur bei 429.

Was tun bei 429? Lesen Sie den Retry-After-Header und warten Sie genau diese Sekunden, bevor Sie erneut anfragen. Tools wie n8n haben dafür einen Retry on Fail-Knoten — bei eigenem Code reicht ein einfaches setTimeout(retry, retryAfter * 1000).
Fehler-Codes

HTTP-Statuscodes & Bedeutung

StatusBedeutungWas tun
401Token fehlt oder ist ungültig.Header Authorization prüfen, ggf. neuen Token erstellen.
403Token gültig, aber der benötigte Scope fehlt.Token mit zusätzlichem Scope neu erstellen — bestehende Tokens lassen sich nicht nachträglich erweitern.
404Die angefragte Ressource existiert nicht oder gehört nicht zu Ihrem Team.ID prüfen — Cross-Team-Zugriffe erscheinen aus Sicherheitsgründen ebenfalls als 404.
429Rate-Limit überschritten (mehr als 60 Requests/Minute).Retry-After-Header abwarten und erneut versuchen.
500Unerwarteter Fehler auf unserer Seite.Kurze Pause, dann Retry. Wenn der Fehler bestehen bleibt: Support kontaktieren.
Coming Soon

In Vorbereitung

Wir bauen die API schrittweise aus. Folgende Bausteine stehen für die nächsten Phasen auf der Roadmap — ohne festes Datum, aber fest geplant.

  • n8n Custom Node

    Native HeyAnnika-Node mit fertigen Triggers + Actions, damit Sie nicht jeden Call manuell als HTTP-Request bauen müssen.

  • MCP-Server für Claude

    Model-Context-Protocol-Server, mit dem Claude direkt auf Ihre HeyAnnika-Daten zugreifen kann — Anrufe zusammenfassen, Folgeaktionen vorschlagen, Briefings schreiben.

  • Retry-Logik für Webhooks

    Automatische Wiederholungsversuche mit Exponential-Backoff bei fehlschlagenden Webhook-Auslieferungen — heute manuell, später automatisch.

  • Sub-Account-Tokens

    Tokens mit Mitarbeiter-Scope statt Team-Scope — feinere Berechtigungen für Sekretariate, Assistenzen oder einzelne Makler:innen im Team.

Bug-Report oder Feature-Wunsch?

Bei Bug-Reports oder Feature-Wünschen schreiben Sie uns direkt — wir antworten in der Regel innerhalb eines Werktags.

info@nexumsystems.de