Links

Verknüpfungen zwischen beliebigen CRM-Objekten — Customer ↔ Vertrag, Vertrag ↔ Dokument, Schaden ↔ Aktivität etc. Verfügbar parent-scoped unter Customer, Produktpartner und Benutzer. Liefert die Cross-Reference-Layer für Workflows, die mehrere Ressourcen-Typen miteinander koppeln.

Zuletzt geprüft: 14. Mai 2026

Übersicht

Ein Link ist eine generische Verknüpfung zwischen zwei CRM-Objekten — definiert über ein (owner_section, owner_id) ↔ (foreign_section, foreign_id)-Paar. Beispiele:

  • Vertrag → Dokument (Police gehört zu Vertrag)
  • Schaden → Aktivität (Telefonat zum Schaden)
  • Customer → Customer (Familien-Beziehung über die Relations-API modelliert; Links sind die generische Variante)

Im Gegensatz zur typisierten Relations-API (mit Beziehungs-Typen wie „Ehepartner") sind Links untyped — nur die Sections und IDs zählen. Das macht sie flexibel, aber semantisch leicht.

Section-Werte sind die Resource-Familien-Namen aus der Start — customers, contracts, damages, documents etc.

Scopes

Links teilen sich die Scope-Familie mit ihrem Parent — sie sind ein Cross-Reference-Layer am jeweiligen Parent-Modell, kein eigenes Geschäftsobjekt:

EndpointScope
GET /v3/customers/<kid>/linksv3:customers:read
POST /v3/customers/<kid>/linksv3:customers:write
DELETE /v3/customers/<kid>/links/<id>v3:customers:write
GET /v3/products/<kid>/linksv3:products:read
POST /v3/products/<kid>/linksv3:products:write
DELETE /v3/products/<kid>/links/<id>v3:products:write
GET /v3/users/<kid>/linksv3:users:read
POST /v3/users/<kid>/linksv3:users:write
DELETE /v3/users/<kid>/links/<id>v3:users:write

Auth-Mechanik und Fehler siehe Authentifizierung. Vollständige Scope-Referenz mit Token-Beispielen: Scopes.

Schema

json
{
  "id": 55001,
  "kid": 12345,                       // Customer, an dem der Link hängt
  "owner_section": "contracts",
  "owner_id": 90001,                  // Vertrag-ID
  "foreign_section": "documents",
  "foreign_id": 33001,                // Dokument-ID
  "insert_datum": "2024-06-15"
}

kid-Bindung: Jeder Link hängt logisch an seinem Parent (kid = Customer-, Product- oder User-ID). Der kid steht im Pfad (/v3/{customers|products|users}/<kid>/links) — sowohl für List, POST als auch DELETE. POST/PATCH-Bodies mit kid-Feld werden mit 400 body_kid_forbidden abgelehnt.

Lateral-Access-Schutz (Mai 2026): Der Server verifiziert bei jedem DELETE zusätzlich, dass der adressierte Link wirklich zum URL-Parent gehört. Bei einem Mismatch kommt 404 not_found. Das gilt sowohl für die parent-scoped Variante (DELETE /v3/customers/<kid>/links/<linkId> muss zum Customer-kid passen) als auch für die nested-resource-Variante (DELETE /v3/<section>/<sectionId>/links/<linkId> muss owner_section/owner_id matchen). Damit kann ein Caller mit Tenant-Scope einen Link nicht über einen fremden Parent löschen — selbst wenn er die Link-ID kennt.

Endpoints

Die Endpoints sind 1:1 identisch unter allen drei Parents — die Beispiele unten zeigen customers, dieselben Aufrufe funktionieren mit products und users als Parent.

GET/v3/customers/<kid>/links Im Playground testen ↗
GET/v3/products/<kid>/links Im Playground testen ↗
GET/v3/users/<kid>/links Im Playground testen ↗

Liefert alle Links, deren Owner der durch den Pfad referenzierte Parent ist — also owner_section = customers|products|users und owner_id = <kid>. Anders als andere List-Endpoints kein Pagination-Envelope; die Response ist ein einfaches { total, items }-Objekt.

Wichtig — Owner ist Pfad-fest: owner_section und owner_id werden aus dem URL-Pfad abgeleitet und sind nicht als Query-Parameter setzbar. Um Links eines anderen Owner-Records (z. B. eines Vertrags, eines Schadens) abzufragen, nutze die nested-resource-Variante des jeweiligen Records — z. B. GET /v3/contracts/<id>/links für die Links eines Vertrags. Diese Route trägt ihren eigenen Scope (v3:contracts:read) und ihre eigene Ownership-Pruefung.

Path-Parameter: <kid> (Customer-, Product- oder User-ID — gleichzeitig Owner-ID des Links).

Query-Parameter:

NameTypPflichtBeschreibung
foreign_sectionstringneinFilter: nur Links auf diese Foreign-Section (z. B. documents, contracts)

Fehler:

  • query_owner_section_forbidden (400) — owner_section als Query angegeben (Owner ist Pfad-fest)
  • query_owner_id_forbidden (400) — owner_id als Query angegeben (Owner ist Pfad-fest)
bash
# Alle Links dieses Customers
curl "https://www.api.i-planner.app/v3/customers/12345/links" \
  -H "Authorization: Bearer $IPLANNER_API_TOKEN"

# Nur die Dokumente, die direkt an diesem Customer haengen
curl "https://www.api.i-planner.app/v3/customers/12345/links?foreign_section=documents" \
  -H "Authorization: Bearer $IPLANNER_API_TOKEN"
POST/v3/customers/<kid>/links Im Playground testen ↗
POST/v3/products/<kid>/links Im Playground testen ↗
POST/v3/users/<kid>/links Im Playground testen ↗

Legt eine neue Verknüpfung an, deren Owner der durch den Pfad referenzierte Parent ist (owner_section = customers|products|users, owner_id = <kid>). Der Body liefert ausschliesslich die Foreign-Seite.

Owner ist Pfad-fest: owner_section und owner_id werden nicht im Body akzeptiert. Um einen Link mit einem anderen Owner-Record anzulegen (z. B. Vertrag → Dokument), nutze die nested-resource-POST-Variante des jeweiligen Records — z. B. POST /v3/contracts/<id>/links mit Body { foreign_section, foreign_id }.

Path-Parameter: <kid> (Customer-, Product- oder User-ID — der Owner des neuen Links).

Body (alle Felder Pflicht — kid, owner_section, owner_id sind verboten, da Pfad-fest):

FeldTypBeschreibung
foreign_sectionstringResource-Familie des Foreign-Objekts (z. B. documents)
foreign_idint > 0ID des Foreign-Objekts — siehe Hinweis unten zur Wahl der ID

Fehler:

  • empty_body (400) — Body fehlt oder ist kein Objekt
  • body_owner_section_forbidden (400) — owner_section im Body (Owner ist Pfad-fest)
  • body_owner_id_forbidden (400) — owner_id im Body (Owner ist Pfad-fest)
  • body_kid_forbidden (400) — kid im Body (kommt aus Pfad)
  • missing_foreign_section (400) — foreign_section fehlt
  • invalid_foreign_id (400) — foreign_id fehlt, ist 0, negativ oder kein Integer
  • relation_exists (409) — Link existiert bereits

Response: 201 mit { success: true, ids: [<owner→foreign>, <foreign→owner>] }. Links werden serverseitig bidirektional angelegt — pro Aufruf entstehen zwei crm_relations-Rows, deren IDs in der Reihenfolge owner→foreign, foreign→owner zurückkommen. Zum Löschen reicht einer der beiden IDs; der gegenläufige Eintrag wird automatisch mit entfernt.

bash
curl -X POST https://www.api.i-planner.app/v3/customers/12345/links \
  -H "Authorization: Bearer $IPLANNER_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "foreign_section": "documents",
    "foreign_id": 33001
  }'
DELETE/v3/customers/<kid>/links/<id> Im Playground testen ↗
DELETE/v3/products/<kid>/links/<id> Im Playground testen ↗
DELETE/v3/users/<kid>/links/<id> Im Playground testen ↗

Path-Parameter: <kid> (Customer-, Product- oder User-ID), <id> (Link-Row-ID).

Löscht eine Verknüpfung. Hard-Delete — kein Soft-Delete-Flag, der Link ist nach erfolgreichem Aufruf weg. Die verlinkten Owner-/Foreign-Objekte bleiben unangetastet.

Path-Parameter: id (integer, positiv).

Response: 200 mit { success: true }. 404 endpoint_not_found bei nicht-numerischer ID, 404 not_found bei nicht existierender ID, 400 invalid_id bei 0/negativ. Volle Error-Tabelle in Fehler.

bash
curl -X DELETE https://www.api.i-planner.app/v3/customers/12345/links/55001 \
  -H "Authorization: Bearer $IPLANNER_API_TOKEN"