Verträge
Endpoints für die Verwaltung von Verträgen — Full-CRUD top-level über `/v3/contracts/{id}` plus parent-scoped Listing/Create unter `/v3/customers/{kid}/contracts`. Sub-Ressourcen (Personen, Tarife) und Annotations hängen über die Vertrag-`id` top-level.
Zuletzt geprüft: 14. Mai 2026
Übersicht
Ein Contract ist ein Vertrag eines Customers — Versicherungspolice, Finanzierungsvertrag, Sparvertrag o. ä. Vertragsdaten umfassen Stammdaten (Vertragsnummer, Gesellschaft, Sparte, Tarif, Beitrag), versicherte Personen, Tarif-Optionen und beliebige angehängte Comments/Documents/Followers/Tags.
Der Contract hat eine global eindeutige id — alle CRUD-Operationen laufen daher top-level über /v3/contracts/{id}, ohne Detour über den Customer:
- Top-Level Full-CRUD —
/v3/contracts(List, Create) und/v3/contracts/{id}(Read, Update, Delete). Eignet sich für Reports, Dashboards, Provisions-Auswertungen, Workflow-Knoten und überall dort, wo du die Contract-idschon kennst. - Parent-scoped Listing + Create —
GET /v3/customers/{kid}/contracts(Verträge eines Kunden) undPOST /v3/customers/{kid}/contracts(Vertrag unter einem Kunden anlegen). Path-kidist Source-of-Truth; Body darf keinkid-Feld tragen (sonst400 body_kid_forbidden).
Beide Patterns nutzen denselben v3:contracts:*-Scope — der Scope hängt an den Daten, nicht am URL-Pfad.
Scopes
Alle Vertrags-Endpoints — unabhängig vom URL-Pfad — laufen über die Scope-Familie v3:contracts:*:
v3:contracts:read— alleGET-Endpoints (Liste, Einzelabruf, Persons-Liste, Tariffs-Liste).v3:contracts:write—POST,PATCH,DELETEauf Vertrag, Persons und Tariffs.- Sparten-Stammdaten liegen jetzt unter
/v3/system/spartenmit Scopev3:system:read— nicht mehr unter/v3/contracts/sparten.
| Endpoint | Scope |
|---|---|
GET /v3/contracts | v3:contracts:read |
POST /v3/contracts | v3:contracts:write |
GET /v3/contracts/<id> | v3:contracts:read |
PATCH /v3/contracts/<id> | v3:contracts:write |
DELETE /v3/contracts/<id> | v3:contracts:write |
GET /v3/customers/<kid>/contracts | v3:contracts:read |
POST /v3/customers/<kid>/contracts | v3:contracts:write |
GET /v3/contracts/<id>/persons | v3:contracts:read |
POST /v3/contracts/<id>/persons | v3:contracts:write |
DELETE /v3/contracts/<id>/persons/<personId> | v3:contracts:write |
GET /v3/contracts/<id>/tariffs | v3:contracts:read |
POST /v3/contracts/<id>/tariffs | v3:contracts:write |
DELETE /v3/contracts/<id>/tariffs/<tariffId> | v3:contracts:write |
Wichtig: v3:customers:read|write reicht NICHT für Vertrags-Zugriff — auch wenn der URL-Pfad /v3/customers/... enthält. Der Scope hängt an den Daten, nicht am Pfad. Wer Customers verwalten will und Verträge lesen will, braucht beide Scopes — getrennt und unabhängig.
Auth-Mechanik und Fehler siehe Authentifizierung. Vollständige Scope-Referenz mit Token-Beispielen: Scopes.
Pagination & Limits
Standard-Pagination (page, limit) wie im Customer-Endpoint. Default-Limit 50, Hard-Cap 5000, Envelope { page, page_size, total, items }. Details und Beispiel-Loop in der Customers-Pagination.
Batch-Create: POST /v3/customers/<kid>/contracts akzeptiert Single-Item oder Array (max. 100 Items). Verhalten identisch zum Customer-POST (per-item Transaktion, gescheiterte Items in failed[], kein „alle-oder-keiner") — siehe Customers > Kunden anlegen.
Schema
{
"id": 90001,
"kid": 12345,
"sparte": 164,
"vertragsnummer": "V-2024-00471",
// Weitere Vertragsfelder richten sich nach der Sparte und der Organisation.
// Sub-Ressourcen sind nur mit den jeweiligen with_*-Parametern enthalten.
"persons": [
{
"id": 87001,
"kid": 12345,
"vid": 90001,
"pid": 42,
"person_art": 2,
"person_name": "Testperson"
}
],
"tariffs": [
{
"id": 88001,
"kid": 12345,
"vid": 90001,
"name": "PrivatPlus",
"tarif_id": -1
}
]
}
Im Einzelabruf mit with_schema=true enthält select_fields.sparten alle aktuell verfügbaren Sparten der Organisation; eine ältere, inzwischen deaktivierte Sparte bleibt zusätzlich enthalten, wenn der Vertrag sie verwendet. Mit eingebundenen Personen oder Tarifen liegt die Liste unter select_fields.contracts.sparten. Für eine eigenständige, cachebare Sparten-Liste gibt es GET /v3/system/sparten. Bei paginierten Vertragslisten ohne division werden dagegen nur Sparten der aktuellen Ergebnisseite aufgeführt.
Dynamische bestandsführende Firma (gd_abr_firma)
gd_abr_firma erwartet eine numerische Firmen-ID, keinen Firmennamen:
0steht für den aktuellen Hauptfirmennamen aus Organisation → Einstellungen → Firma → Firmenname.- Positive Werte verweisen auf eine zusätzliche Firma aus den Abrechnungseinstellungen.
Die gültigen Werte stehen bei ?with_schema=true unter select_fields.abrFirma; mit eingebundenen Personen oder Tarifen unter select_fields.contracts.abrFirma. Diese Liste wird dynamisch aus den aktuellen Organisations- und Abrechnungseinstellungen erzeugt; im Formulareditor angelegte statische Optionen sind für dieses Feld nicht maßgeblich.
Die Liste gilt organisationsweit, nicht nur für eine einzelne Sparte. Der Hauptfirmenname wird unter Organisation → Einstellungen → Firma gepflegt; zusätzliche Abrechnungsfirmen werden in den Abrechnungseinstellungen verwaltet. Änderungen der Anzeigenamen verändern bestehende Verträge nicht: Gespeichert bleibt die numerische ID, nur deren aktuelle Bezeichnung wird bei der Anzeige neu aufgelöst.
{
"fields": {
"gd_abr_firma": {
"label": "Bestandsführende Firma",
"data_type": "number",
"select_fields_name": "abrFirma"
}
},
"select_fields": {
"abrFirma": [
{ "value": 0, "display": "Hauptfirma GmbH" },
{ "value": 3, "display": "Weitere Firma GmbH" }
]
}
}
Endpoints
Verträge org-weit abfragen
Liefert eine paginierte Liste aller Verträge der Organisation, quer durch alle Customers. Ideal für Reports und Analytics. Read-only, eigener Scope v3:contracts:read.
Query-Parameter
page— Seitenindex (Default1).limit— Items pro Seite (Default50, Max5000).kid— optional: Filter auf einen einzelnen Customer.with_persons— versicherte Personen inline (Defaultfalse, opt-in).with_tariffs— Tarif-Optionen inline (Defaultfalse, opt-in).with_schema— Schema im Response (Defaultfalse, opt-in). Ohne eingebundene Unterressourcen stehenfieldsundselect_fieldsdirekt unter der Antwort. Mitwith_personsund/oderwith_tariffsliegen sie untercontracts,personsund/odertariffs– auch wenn ein eingebundenes Array leer ist.division— Sparte-ID aus Sparten-Liste. Filtert sowohl dieitems[]als auch das (mitwith_schema=1gelieferte)fields/select_fieldsauf genau diese Sparte. Ideal für Form-Builder, die das Create-Form für eine konkrete Sparte rendern wollen — auch wenn noch kein Vertrag der Sparte existiert (= leere Liste, volles Sparten-Schema).with_archived— auch archivierte Verträge listen (Defaultfalse).flat— Sub-Arrays in flache Keys ausrollen (Defaultfalse).
Request
curl 'https://www.api.i-planner.app/v3/contracts?page=1&limit=50&with_archived=true' \
-H "Authorization: Bearer $IPLANNER_API_TOKEN"
Response
// OK — Pagination-Envelope + items[]. Jedes Item ist ein vollständiges
// Contract-Objekt. persons[] und tariffs[] erscheinen nur bei den with_*-Parametern.
{
"page": 1,
"page_size": 50,
"total": 4217,
"items": [
{
"id": 90001,
"kid": 12345,
"vertragsnummer": "V-2024-00471",
"sparte": 164
}
]
}
Einzelnen Vertrag abrufen
Liefert einen einzelnen Vertrag über seine globally-unique id (alle Sub-Ressourcen inline). Read-only, Scope v3:contracts:read. Query-Parameter with_persons, with_tariffs, with_schema, flat wie bei der Liste.
Request
curl 'https://www.api.i-planner.app/v3/contracts/90001' \
-H "Authorization: Bearer $IPLANNER_API_TOKEN"
Response — analog zum Customer-Get: 200 mit vollem Objekt, 400 invalid_id, 404 endpoint_not_found (nicht-numerisch), 404 not_found (Vertrag existiert nicht). Volle Error-Tabelle in Errors.
Vertrag top-level anlegen
Legt einen Vertrag unter einem bestehenden, nicht gelöschten Customer an. Der Customer-kid ist im Body erforderlich; Produkt-, Benutzer-, gelöschte oder unbekannte Kontakte werden nicht als Parent akzeptiert. Alternativ setzt POST /v3/customers/{kid}/contracts den kid über den Pfad.
persons werden weiterhin über ihren eigenen Endpoint angelegt. Tarife können
dagegen direkt als tariffs-Array mitgesendet werden. Vertrag und Tarife werden
atomar gespeichert: Schlägt ein Tarif fehl, wird auch der Vertrag nicht angelegt.
Request
curl -X POST 'https://www.api.i-planner.app/v3/contracts' \
-H "Authorization: Bearer $IPLANNER_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"kid": 12345,
"nummer": "H-2026-00012",
"gesellschaft": "HUK24",
"sparte": "haftpflicht",
"beitrag": 7.90,
"intervall": "monatlich",
"beginn": "2026-06-01",
"tariffs": [
{ "name": "PrivatPlus", "tarif_id": -1 },
{ "name": "Zahn Premium", "tarif_id": 42 }
]
}'
Response — Erfolg 201 { success, id, kid, tariffs }; ein fehlender oder ungültiger kid ergibt 400, ein nicht vorhandener, gelöschter oder falsch typisierter Customer 404 customer_not_found. tariffs enthält die tatsächlich angelegten Tarifzeilen einschließlich ihrer neuen IDs.
Vertrag am Kunden anlegen
Legt einen Vertrag unter einem konkreten Customer an. Der Customer-kid steht im Pfad — kid im Body ist verboten und führt zu 400 body_kid_forbidden.
Diese parent-scoped Variante ist die typische Wahl in UI-Workflows („Vertrag an diesen Kunden anhängen"). Wer die kid bereits im Programm-Kontext hat, kann sie stattdessen an den Top-Level-POST senden.
Request
curl -X POST 'https://www.api.i-planner.app/v3/customers/12345/contracts' \
-H "Authorization: Bearer $IPLANNER_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"nummer": "H-2026-00012",
"gesellschaft": "HUK24",
"sparte": "haftpflicht",
"beitrag": 7.90,
"intervall": "monatlich",
"beginn": "2026-06-01",
"tariffs": [
{ "name": "PrivatPlus", "tarif_id": -1 }
]
}'
Response
{
"success": true,
"id": 90003,
"kid": 12345,
"tariffs": [
{ "id": 88001, "vid": 90003, "kid": 12345, "name": "PrivatPlus", "tarif_id": -1 }
]
}
Vertrag aktualisieren
Aktualisiert die im Body gesendeten Felder über die globally-unique id — kein Customer-Detour nötig. PATCH-Semantik (Felder nicht im Body bleiben unverändert). Sub-Ressourcen-Arrays werden hier nicht akzeptiert. Tarifänderungen laufen über die Tarif-Unterressource, damit vorhandene Mehrfach-Tarife nicht versehentlich ersetzt werden.
Request
curl -X PATCH 'https://www.api.i-planner.app/v3/contracts/90001' \
-H "Authorization: Bearer $IPLANNER_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "beitrag": 92.50, "status": "aktiv" }'
Response
{ "success": true, "updated": 2 }
Vertrag löschen
Soft-Delete via del=1 — der Vertrag bleibt audit-fähig in der Datenbank, ist aus Listen-Responses ausgeblendet. Wiederholte DELETEs antworten mit 404 not_found. Top-level über die globally-unique id.
Request
curl -X DELETE 'https://www.api.i-planner.app/v3/contracts/90001' \
-H "Authorization: Bearer $IPLANNER_API_TOKEN"
Response
{ "success": true, "deleted": true, "soft_deleted": true, "id": 90001 }
Helper-Endpoints
Sparten-Liste
Globale Liste aller Sparten-Werte deiner Organisation. Keine kid-Bindung, keine Pagination — der Endpoint liefert das Tenant-Sparten-Dictionary als kompaktes Array. Cache-friendly auf Client-Seite. Scope: v3:system:read.
Request
curl 'https://www.api.i-planner.app/v3/system/sparten' \
-H "Authorization: Bearer $IPLANNER_API_TOKEN"
Response
{
"items": [
{ "value": "krankenversicherung", "display": "Krankenversicherung" },
{ "value": "lebensversicherung", "display": "Lebensversicherung" },
{ "value": "haftpflicht", "display": "Haftpflicht" },
{ "value": "kfz", "display": "KFZ" },
{ "value": "rechtsschutz", "display": "Rechtsschutz" }
]
}
Unterressourcen
Persons
Versicherte Personen oder Vertragsbeteiligte an einem Vertrag (Versicherungsnehmer, Versicherte, Begünstigte).
DB-Tabelle: crm_vertraege_person. Verfügbare Spalten: id, kid, vid, pid, art, person_art, name, person_name, beschreibung, ranking. Unbekannte Body-Felder werden still verworfen.
{
"id": 87001,
"vid": 90001, // Vertrag (aus Pfad)
"kid": 12345, // Parent-Customer (aus Pfad)
"pid": 67890, // optional: kid einer anderen Person, falls verlinkt
"art": 1, // Rollen-Kennung (smallint)
"person_art": 1,
"name": "Leier", // Freitext-Name
"person_name": "Helmut Leier",
"beschreibung": "Hauptversicherter"
}
Path-Parameter — <id> (Contract-Row-ID, globally unique), <personId> (Person-Row-ID). Query-Parameter (List) — page, limit, with_schema.
Beispiel — Versicherte Person hinzufügen:
curl -X POST 'https://www.api.i-planner.app/v3/contracts/90001/persons' \
-H "Authorization: Bearer $IPLANNER_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"art": 1,
"person_name": "Anna Leier",
"beschreibung": "Versicherte Person"
}'
Tariffs
Tarif-Optionen / Bausteine eines Vertrags (z. B. bei einer Krankenversicherung: stationär, ambulant, Zahn separat).
DB-Tabelle: crm_vertraege_tarife. Schreibbar sind name (Pflicht, maximal 255 Zeichen) und optional tarif_id (-1 oder eine positive Katalog-ID). id, kid, vid und ranking werden vom Server verwaltet; unbekannte Body-Felder werden mit 400 validation_failed abgelehnt.
{
"id": 88001,
"kid": 12345, // Parent-Customer (aus Pfad)
"vid": 90001, // Vertrag (aus Pfad)
"tarif_id": -1, // Referenz auf System-Tarif (-1 = freier Eintrag ohne Katalog-Bezug)
"name": "Stationär PrivatPlus"
}
Path-Parameter — <id> (Contract-Row-ID, globally unique), <tariffId> (Tarif-Row-ID). Query-Parameter (List) — page, limit, with_schema.
Beispiel — Tarif-Baustein hinzufügen:
curl -X POST 'https://www.api.i-planner.app/v3/contracts/90001/tariffs' \
-H "Authorization: Bearer $IPLANNER_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Zahn Premium",
"tarif_id": -1
}'
Anhänge am Vertrag
Comments, Documents, Followers und Tags hängen direkt an der Vertrag-id — top-level, ohne Customer-Detour:
/v3/contracts/<id>/<resource>[/<rid>]
<resource> ist comments, documents, followers oder tags. Felder, Methods und Response-Bodies entsprechen den Customer-Level-Pendants — der einzige Unterschied: kein kid-Body-Feld, kein kid-Query-Param. Beides ergibt sich aus dem Pfad.
curl -X POST 'https://www.api.i-planner.app/v3/contracts/90001/comments' \
-H "Authorization: Bearer $IPLANNER_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "text": "Beitragserhöhung zum 1. Juli 2026 angekündigt." }'