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, nichtcontactIdsodercontacts. - Kontakt-Status:
status_id(Integer), nichtstatusals String. - Tags und Aktivitäten sind im
ContactSchemadump-only — überPUT /api/contacts/{id}
lassen sie sich nicht setzen, auch nicht versehentlich.
Die korrekten Schreibwege:
| Use Case | Endpoint | Body |
|---|---|---|
| Kontakt-Status setzen | PUT /api/contacts/{id}/status | {"status_id": 7} (oder "" zum Entfernen) |
| Tags an Kontakt anhängen | PUT /api/tags/contact/{contact_id} | {"tag_ids": ["<uuid>", ...]} (ersetzt den Satz) |
| Neuen Tag anlegen | POST /api/{orga_id}/tags | {"name": "Investor", "color": "#1F8AE0"} |
| Aktivität + Kontakt | POST /api/activities/activities | {..., "contact_ids": ["<uuid>", ...]} |
| Kontakte einer Aktivität ändern | PUT /api/activities/activities/{id} | {"contact_ids": ["<uuid>", ...]} |
| Aktivität löschen | DELETE /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_type | Vergleich |
|---|---|
text, richtext | ILIKE %wert% (case-insensitive substring) |
dropdown | exakt = |
number | exakter Float-Vergleich (Komma wird zu Punkt) |
boolean | true/1/ja ↔ false/... |
date | ISO-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/Writemehrere Endpoints
zusammenkettet und Beispiele braucht.
Alles weitere (Schemas, Pfade, Antwortcodes) bitte aus der Swagger-UI ziehen.