API-Integrations-Leitfaden

Dieser Leitfaden ergänzt die maschinenlesbare OpenAPI-Spezifikation. Alles, was sich sauber als OpenAPI-Operation beschreiben lässt, steht dort — diese Seite deckt nur die Punkte ab, die OpenAPI strukturell nicht ausdrücken kann (Custom-Field-Semantik, Marshmallow-Validierungsverhalten, Entdeckungs-Flows).

1. OpenAPI-Spec & Swagger-UI

  • Swagger-UI: https://<host>/api/docs
  • Roh-Spec (JSON): https://<host>/api/openapi.json

Beide Endpunkte sind mit Basic Auth geschützt. Server-seitig setzen wir die Credentials per Environment:

IK_OPENAPI_USER=<benutzer>
IK_OPENAPI_PASSWORD=<passwort>

Bitte beim Kunden separate Credentials anfragen — nicht den Admin-Login wiederverwenden.

2. API-Authentifizierung (separat von der Doku)

Für die eigentlichen API-Calls wird ein Bearer-Token verwendet, das im UI unter Einstellungen → Schnittstellen-Zugang erzeugt wird. Details siehe doc/INTEGRATIONS_N8N.md.

Authorization: Bearer <token>
X-Organisation-Id: <organisation_uuid>

3. Strikte Feldvalidierung (Marshmallow)

Alle Schreib-Endpoints (POST/PUT) lehnen die gesamte Anfrage mit 400 "Unknown field" ab, sobald ein Feld geschickt wird, das das Marshmallow-Schema nicht kennt. Es gibt keine partielle Speicherung. Konsequenzen:

  • Camelcase-Feldnamen funktionieren nicht (Schema ist snake_case). Beispiele für
    häufige Stolpersteine:
  • Activity → Kontakt-Verknüpfung: contact_ids, nicht contactIds oder contacts.
  • Kontakt-Status: status_id (Integer), nicht status als String.
  • Tags und Aktivitäten sind im ContactSchema dump-only — über PUT /api/contacts/{id}
    lassen sie sich nicht setzen, auch nicht versehentlich.

Die korrekten Schreibwege:

Use CaseEndpointBody
Kontakt-Status setzenPUT /api/contacts/{id}/status{"status_id": 7} (oder "" zum Entfernen)
Tags an Kontakt anhängenPUT /api/tags/contact/{contact_id}{"tag_ids": ["<uuid>", ...]} (ersetzt den Satz)
Neuen Tag anlegenPOST /api/{orga_id}/tags{"name": "Investor", "color": "#1F8AE0"}
Aktivität + KontaktPOST /api/activities/activities{..., "contact_ids": ["<uuid>", ...]}
Kontakte einer Aktivität ändernPUT /api/activities/activities/{id}{"contact_ids": ["<uuid>", ...]}
Aktivität löschenDELETE /api/activities/activities/{id}

Hinweis zur URL: Activity-Endpoints liegen historisch unter /api/activities/activities/... (Blueprint-Prefix /api/activities + Inner-Route /activities/...). Bitte exakt diese URL verwenden — /api/activities/{id} ohne den zweiten Pfadteil existiert nicht.

Alle vollständigen Bodys/Responses finden sich in der OpenAPI-Spec.

4. Custom Fields

Custom Fields haben pro Organisation eine Definition (ID + Name + Typ) und pro Ziel-Datensatz beliebige Values (key=Definition-ID).

4.1 Definitions entdecken

GET /api/custom-fields/definitions?model=contact&organisation_id=<uuid>

Antwort enthält pro Definition id, name, field_type, config_json. Die id ist das, was du in den folgenden Endpoints verwendest.

4.2 Values eines Kontakts lesen (klassisch)

GET /api/custom-fields/values?model=contact&target_id=<contact_uuid>

{"values": {"<def_id>": <wert>}}

4.3 Values inline mit dem Kontakt lesen (seit Juni 2026)

?include=custom_fields hängt eine custom_fields-Map direkt an jede Kontakt-Response und vermeidet einen zweiten Roundtrip — auch für Listen (in einer Bulk-Query).

GET /api/contacts?organisation_id=<uuid>&page=1&per_page=50&include=custom_fields
GET /api/contacts/<id>?include=custom_fields
{
  "id": "3b1f...",
  "first_name": "Anna",
  ...
  "custom_fields": {
    "8a2c-...-place_id-def-id": "ChIJxxx",
    "9b4d-...-typ-def-id": "Investor"
  }
}

Ohne den include-Parameter ist das Feld null (rückwärtskompatibel).

4.4 Filter auf Custom-Field-Werte (z. B. Dubletten-Check)

?cf_<definition_id>=<wert> filtert die Liste exakt auf den Wert. Vergleichslogik je Typ:

field_typeVergleich
text, richtextILIKE %wert% (case-insensitive substring)
dropdownexakt =
numberexakter Float-Vergleich (Komma wird zu Punkt)
booleantrue/1/jafalse/...
dateISO-String YYYY-MM-DD
relation (single)exakte ID
relation (multiple)enthält die ID im JSON-Array

Beispiel — Dubletten-Erkennung über eine place_id:

GET /api/contacts?organisation_id=<org>&cf_<place_id_def_id>=ChIJxxx

Es gibt aktuell keinen Alias auf den menschlichen Feldnamen (z. B. ?place_id=...). Bitte die Definition-ID einmalig auflösen und cachen — sie ändert sich nicht.

4.5 Values schreiben

PUT /api/custom-fields/values
{
  "model": "contact",
  "target_id": "<contact_uuid>",
  "values": {
    "<def_id>": "ChIJxxx",
    "<andere_def_id>": "Investor"
  }
}

Ein leerer/Null-Wert löscht den jeweiligen Eintrag.

5. Migration / Aufräumen

Verwaiste Aktivitäten entfernen

Aktivitäten ohne Kontaktbezug lassen sich über DELETE /api/activities/activities/{id} entfernen. Falls in der Produktionsumgebung 405 zurückkommt, läuft dort ein älterer Code-Stand — bitte einmal melden, dann deployen wir neu.

6. Was OpenAPI nicht beschreibt (Zusammenfassung)

Diese Seite existiert, weil:

  • die ?cf_<id>= Query-Param-Konvention dynamisch ist (Parameter-Name hängt von
    Org-spezifischen Definition-IDs ab) und in OpenAPI nicht statisch dokumentierbar,
  • die Marshmallow-unknown=RAISE-Semantik eine Konvention, kein Schema-Feature ist,
  • der Entdeckungs-Flow Definitions → Filter/Read/Write mehrere Endpoints
    zusammenkettet und Beispiele braucht.

Alles weitere (Schemas, Pfade, Antwortcodes) bitte aus der Swagger-UI ziehen.