AgencyOS: CockpitOS anbinden (Magic Link & Integration API)
Diese Seite richtet sich an Entwickler:innen von AgencyOS (Team-Produkt unter team.cockpit-os.de). Sie beschreibt, was AgencyOS implementieren muss, um eine Vertrauensstellung mit dem CockpitOS-Dashboard herzustellen und Center sowie Inhalte über die REST-API anzusprechen. Dabei gibt es zwei Geltungsbereiche für den API-Key: eine Organisation (klassisch) oder nutzerweit (alle Center gemäß den Cockpit-Rechten der anbindenden Person, organisationenübergreifend inkl. zugewiesener Center ohne Organisation).
Produkt: AgencyOS · Dashboard-Implementierung (Monorepo): u. a. apps/dashboard/src/app/api/agencyos/…, apps/dashboard/src/app/agencyos/connect/page.tsx
Schlüssel ohne Magic Link (im Dashboard): Eingeloggte Nutzer:innen mit passender Berechtigung können unter Einstellungen → Integrationen eine AgencyOS-Integration anlegen bzw. den API-Key rotieren (GET/POST /api/agencyos/integrations, POST …/rotate) — sinnvoll z. B. für lokale Tools (MCP, Skripte). Vollständiger Key wie gewohnt einmalig in der Antwort, nicht dauerhaft in der UI.
Remote-MCP (HTTP, Monorepo): Paket packages/mcp-cockpit-remote stellt dieselben MCP-Tools per Streamable HTTP bereit (für Organisationen mit Claude-„Remote MCP“-URL). Nicht Teil der öffentlichen Dashboard-API; Betrieb mit eigenem Secret COCKPIT_MCP_HTTP_BEARER und COCKPIT_AGENCYOS_API_KEY im Server-Env. Siehe Paket-README.
Ersetzen Sie {DASHBOARD_ORIGIN} durch die öffentliche URL Ihrer Dashboard-Instanz (z. B. https://dashboard.cockpit-os.de). Lokal oft http://localhost:3000. Die Variable NEXTAUTH_URL im Dashboard bestimmt die in Magic-Link-Antworten eingebettete Basis-URL.
Überblick: Was AgencyOS tun muss
- Magic Link anfordern (serverseitig, ohne Nutzer-Session im Cockpit):
POST /api/agencyos/magic-linkmitintegrationNameund optionalreturnUrl. - Nutzer:in zum Cockpit leiten: Response enthält
data.magicLink(Pfad/agencyos/connect?token=…). Dort meldet sich eine berechtigte Person an und wählt entweder eine Organisation oder „Alle Center mit meinen Zugriffsrechten“ (nutzerweiter Key). - API-Key per Polling holen: Solange
status === "pending", regelmäßigGET /api/agencyos/magic-link?token=…aufrufen. Sobaldstatus === "completed", liefert die Antwortdata.apiKey(Präfix typischsk_agencyos_) sowiedata.accessScope("organization"oder"user") unddata.organizationId(UUID odernullbei nutzerweitem Key). - Speichern: API-Key sicher in AgencyOS hinterlegen (Geheimnis, nicht in Logs/URLs).
- API nutzen: Alle folgenden Aufrufe mit
Authorization: Bearer <apiKey>gegen/api/agencyos/v1/….
Sicherheit und Redirect
- Den API-Key niemals in die Redirect-URL legen (kein Query-Parameter mit Secret). Die Übernahme erfolgt ausschließlich über das Polling des Magic-Link-Status.
- Wenn ihr eine
returnUrl(z. B. zurück nachhttps://team.cockpit-os.de/...) mitsendet, kann das Cockpit nach erfolgreichem Abschluss dorthin weiterleiten und setzt u. a.agencyos=connectedsowiecockpit_agency=connected– ohne Key. Den Key nur aus der GET-Antwort lesen, wennstatus === "completed".
Unterschied zu WordPress
| Aspekt | WordPress-Plugin | AgencyOS |
|---|---|---|
| Geltungsbereich | ein Center pro Website-Key | Organisation: alle Center dieser Org · Nutzer: alle Center, auf die die anbindende Person in Cockpit Zugriff hat (mehrere Orgs + ohne Org) |
| Verbindungs-UI | /wordpress/connect | /agencyos/connect |
| Content-Push | POST /api/wordpress/push-content (Key = Website) | POST /api/agencyos/v1/content/push mit centerId im JSON |
| Doku Push-Body | WordPress Push-Content | Gleiche Entity-Arrays (shops, events, …); siehe unten |
1. Magic Link erstellen
POST {DASHBOARD_ORIGIN}/api/agencyos/magic-link
- Auth: keine (öffentlich wie beim WordPress-Magic-Link).
- CORS:
Access-Control-Allow-Origin: *,OPTIONSunterstützt.
Body (JSON):
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
integrationName | string | ja | Anzeigename der Integration in Cockpit (z. B. "AgencyOS Produktion") |
returnUrl | string | nein | http:// oder https://; nach Erfolg optional Redirect aus dem Browser |
Erfolg (200):
{
"success": true,
"data": {
"magicLink": "https://…/agencyos/connect?token=mla_…",
"token": "mla_…",
"expiresAt": "2026-04-01T12:00:00.000Z"
},
"message": "Magic Link erstellt"
}
Hinweis: Token-Gültigkeit 15 Minuten ab Erstellung (sofern nicht vorher abgeschlossen).
2. Magic-Link-Status abfragen (Polling)
GET {DASHBOARD_ORIGIN}/api/agencyos/magic-link?token=<token>
- Auth: keine.
Solange die Verbindung aussteht, ist data.status typischerweise "pending". Nach Abschluss im Browser:
data.status === "completed"data.apiKeygesetztdata.integrationIdgesetztdata.accessScope:"organization"oder"user"data.organizationId: UUID der verbundenen Organisation odernull, wennaccessScope === "user"
AgencyOS-Implementierung: Nicht von einem festen organizationId im Key ausgehen. Bei accessScope === "user" ist organizationId absichtlich null; die erlaubten Center ergeben sich aus den Cockpit-Rechten des Nutzers (siehe GET /v1/centers).
Fehler: u. a. 404 ungültiger Token, 410 abgelaufen (bei noch pending).
3. Verbindung im Browser abschließen (nicht von AgencyOS-Server)
Dieser Schritt läuft im Cockpit mit NextAuth-Session; AgencyOS ruft ihn normalerweise nicht per Server-to-Server auf.
- UI:
GET /agencyos/connect?token=… - Organisationen laden (Session):
GET /api/agencyos/organizations(für die klassische Variante; nutzerweite Option ist auch ohne Einträge in der Liste möglich) - Abschluss:
POST /api/agencyos/magic-link/completemit JSON:- Organisations-Key:
{ "token": "…", "organizationId": "<uuid>" } - Nutzer-Key:
{ "token": "…", "accessScope": "user" }(keinorganizationIdnötig)
- Organisations-Key:
Die Response enthält u. a. apiKey, accessScope, organizationId (nullable) für die sofortige Anzeige im Browser – für AgencyOS ist weiterhin das Polling die Quelle der Wahrheit, damit der Backend-Prozess den Key zuverlässig erhält.
4. AgencyOS API v1 (Bearer API-Key)
Alle Endpunkte unter /api/agencyos/v1/ erwarten:
Authorization: Bearer <apiKey>
apiKey ist der aus Schritt 2 übernommene AgencyOS-Integration-Key (sk_agencyos_…) – entweder an eine Organisation oder nutzerweit gebunden (accessScope aus der Polling-Antwort).
CORS: Access-Control-Allow-Origin: * (u. a. für GET, POST, PATCH, OPTIONS).
Medien-Upload zu Bunny (Bilder & Videos)
POST {DASHBOARD_ORIGIN}/api/agencyos/v1/media/upload
- Auth:
Authorization: Bearer <apiKey> - JSON (Variante A):
{ "url": "https://…", "folder?": "agencyos/uploads" }— Ressource wird geladen und nach BunnyCDN kopiert (Bild oder Video). - JSON (Variante B):
{ "base64": "…", "mimeType?": "video/mp4", "filename?": "…", "folder?": "…" }— optional mitdata:mime;base64,-Präfix. - Raw Body:
Content-Type: image/*odervideo/*oderapplication/octet-stream; Query?folder=&filename=— Rohbytes direkt nach Bunny. - Größenlimits: in etwa 10 MB für typische Bilder, 100 MB für Video (Implementierung in
route.ts). - Antwort:
{ "success": true, "bunnyUrl": "https://…b-cdn.net/…" }(bereits Bunny-URLs werden unverändert bestätigt).
MCP: Paket @mall-os/mcp-cockpit-os, Tool cockpit_agencyos_upload_media — Parameter url oder base64 (plus optional mimeType, filename, folder).
Mediathek listen: GET {DASHBOARD_ORIGIN}/api/agencyos/v1/media?centerId=<uuid> — Filter type, entityType, q, includeShared, limit, offset. MCP: cockpit_agencyos_list_media.
UI (Redaktion): Video wie Bilder über FileUpload → POST /api/upload (Multipart, Bunny-Pfad z. B. centers/{centerId}/{entityType}/video/…) und „Aus Mediathek“; nicht zu verwechseln mit dieser AgencyOS-JSON-Route.
4.1 Shopping Center auflisten
GET {DASHBOARD_ORIGIN}/api/agencyos/v1/centers
Response (200): { "success": true, "data": [ { "id", "name", "slug", "address", "city", "postalCode", "country", "status", "websiteEnabled", "organizationId", "agencyIntegrationId", "updatedAt" }, … ] }
Max. 500 Einträge, sortiert nach Name.
Bei accessScope === "organization" (Standard):
- alle Center mit
organizationId= Organisation des API-Keys, und - Center ohne Organisation (
organizationId: null), die von genau dieser Agency-Integration angelegt wurden (agencyIntegrationId= Integration des Keys).
Bei accessScope === "user":
- alle Shopping Center, auf die der anbindende Cockpit-Nutzer Zugriff hat (z. B. über
UserCenterAssignment, Heimat-Organisation, Super-Rollen – wie in Cockpit definiert), und - dieselben integrationsgebundenen „Waisen“-Center wie oben (ohne Organisation, aber
agencyIntegrationIddieser Integration).
So erscheinen „freie“ Center nur im Kontext der eigenen Integration, ohne fremde ungebundene Center zu leaken.
4.2 Shopping Center anlegen
POST {DASHBOARD_ORIGIN}/api/agencyos/v1/centers
Body (JSON) – Pflichtfelder:
| Feld | Typ | Beschreibung |
|---|---|---|
name | string | |
address | string | |
city | string | |
postalCode | string |
Optional: country (Default "DE"), slug (sonst automatisch aus Name, global eindeutig), description, phone, email, website, status (Default "active").
Organisation (wie im Cockpit):
| Feld | Typ | Beschreibung |
|---|---|---|
| Standard (Organisations-Key) | — | Ohne die folgenden Felder wird organizationId auf die Organisation des API-Keys gesetzt. |
| Standard (Nutzer-Key) | — | Nicht ohne Ziel-Org: Es muss entweder withoutOrganization/noOrganization/organizationId: null oder eine explizite organizationId (UUID) gesendet werden, für die der anbindende Nutzer in Cockpit berechtigt ist; sonst 400/403. |
withoutOrganization / noOrganization | boolean true | Center wird ohne Organisation angelegt (organizationId: null), bleibt aber dieser Integration zugeordnet (intern agencyIntegrationId), damit sie es in GET/Push weiter nutzen kann. |
organizationId | null | Gleiche Bedeutung wie withoutOrganization: true. |
organizationId | string (UUID) | Organisations-Key: nur erlaubt, wenn der Wert exakt der Organisation des Keys entspricht; sonst 403. Nutzer-Key: erlaubt, wenn der Nutzer diese Organisation verknüpfen darf (wie im Cockpit); sonst 403. |
Erfolg: HTTP 201, data enthält u. a. organizationId und agencyIntegrationId.
Fehler: 409 wenn slug bereits vergeben.
4.3 Einzelnes Center lesen / aktualisieren
GET {DASHBOARD_ORIGIN}/api/agencyos/v1/centers/{centerId}
PATCH {DASHBOARD_ORIGIN}/api/agencyos/v1/centers/{centerId}
Zugriff, wenn das Center für diesen Key erlaubt ist (Organisations-Key: gleiche Organisation oder integrationsgebundenes Waisen-Center; Nutzer-Key: gemäß Nutzerrechten oder integrationsgebundenes Waisen-Center); sonst 404.
PATCH: nur gesendete Felder werden geändert. Erlaubt u. a. name, address, city, postalCode, country, phone, email, website, description, openingHours, status, slug, websiteEnabled. Pflichtfelder dürfen nicht auf null gesetzt werden.
Später an eine Organisation hängen: assignToKeyedOrganization, assignToOrganization oder linkToKeyedOrganization mit true – nur wenn das Center aktuell ohne Organisation ist und von dieser Integration stammt.
- Organisations-Key: Es wird die Organisation des Keys verbunden (
agencyIntegrationIdwird entfernt). - Nutzer-Key: Zusätzlich
attachOrganizationId(UUID) im JSON-Pflicht – Zielorganisation, an die gehängt werden soll; nur wenn der anbindende Nutzer dafür in Cockpit berechtigt ist; sonst 403. (agencyIntegrationIdwird entfernt.)
Website-Konfiguration (GET + partial PUT)
Lesen: GET {DASHBOARD_ORIGIN}/api/agencyos/v1/centers/{centerId}/website-config
Liefert designConfig, seoConfig, contentConfig, legalConfig, parkingConfig, analyticsConfig, centerplanConfig, pagesConfig, templateContent (Template-Reiter).
Schreiben: PUT {DASHBOARD_ORIGIN}/api/agencyos/v1/centers/{centerId}/website-config
Partial-Update; templateContent wird deep-merged.
Reiter-Index (Schema): GET {DASHBOARD_ORIGIN}/api/agencyos/v1/website-config-schema?websiteTemplate=ilg
oder ?centerId={uuid} — Dashboard-Reiter mit Speicherort, fields[] (exakte JSON-Pfade), v0PublicRead (öffentliche Live-Site) und MCP-Hinweisen für alle Website-Templates.
v0 / Claude Live-Website (LESEN, ohne Auth):
GET …/api/centers/{centerId}/public-visitor-surface → data.templatePublicContent (Template-Reiter: Hero, Footer, …) + data.apiHints.pageContentGet / homepageTilesGet.
Nicht GET …/website-config im Browser — 401. Schreiben bleibt MCP: cockpit_agencyos_*. Antwort enthält v0Integration (Lesen-vs-Schreiben-Guide).
Page Content (Hero, SEO, customContent): POST …/page-content — beim Update werden nur mitgesendete Scalar-Felder geändert; customContent wird deep-gemerged (z. B. customContent.ilg.anfahrtBoxes ohne andere Seitenfelder zu löschen).
Empfohlener MCP-Workflow (ILG/RGW):
cockpit_agencyos_website_config_schema(mitwebsiteTemplateodercenterId)- Reiter aus
tabs[]wählen →fields[]/templateContentPath/customContentPathlesen - Bestehende Werte per GET (
get_center_website_config/page_content) - Partial PUT/upsert nur mit geänderten Keys
- Revalidate (automatisch in API-Response, sofern konfiguriert)
MCP:
| Tool | Funktion |
|---|---|
cockpit_agencyos_get_center_website_config | Volle Config lesen |
cockpit_agencyos_update_center_website_config | Partial Update |
cockpit_agencyos_website_config_schema | Reiter + Feldpfade pro Template |
cockpit_mcp_discover_tools | Tool-Index wenn Claude tool_search scheitert |
contentConfig.specialDays (Sonderöffnungszeiten): JSON-Array oder dasselbe als JSON-String. Pro Eintrag z. B. { "date": "2026-12-24", "label": "Heiligabend", "hours": { "open": "10:00", "close": "14:00" } } — geschlossen mit "hours": null. Optional "image" (URL).
Die Route schreibt in ShoppingCenter.specialDays und spiegelt bei strukturiertem openingHours (mit regularHours) zusätzlich openingHours.specialDays — analog zum Speichern im Cockpit „Center bearbeiten“.
4.3a Center-Kontext für KI lesen (Shops / Services)
GET {DASHBOARD_ORIGIN}/api/agencyos/v1/centers/{centerId}/context
Zugriff wie GET /v1/centers/{centerId} (Bearer-Key, sonst 404).
Liefert gebündelte Lesedaten für AgencyOS/KI, ohne die öffentliche Website-API zu nutzen:
| Query | Default | Max | Beschreibung |
|---|---|---|---|
include | center,shops | — | Komma-getrennt: center, shops, services, news, events, offers, categories, chains, floors_summary |
shopLimit | 500 | 2000 | Max. Anzahl kombinierter Einträge (Einzelshops + Filialen), sortiert nach Name |
serviceLimit | 200 | 500 | Nur wenn services in include |
newsLimit | 80 | 200 | Nur wenn news in include |
eventsLimit | 80 | 200 | Nur wenn events in include |
offersLimit | 80 | 200 | Nur wenn offers in include |
Filter: Shops und Filialen wie GET …/centers/{centerId}/shops?publicWebsite=true&status=Aktiv (inkl. Veröffentlichungsfenster). Services wie websiteServicePublicFilter (Center-Website-SSR).
News / Events / Angebote: Alle Datensätze dieses Centers (jeder Status, inkl. Entwurf), sortiert nach updatedAt absteigend — für Kontext, Dedupe und Abgleich mit AgencyOS. Keine Volltexte; pro Zeile u. a. id, title, slug, status, Daten, source, sowie agencyosPushId und wordpressPushId aus metadata, falls gesetzt.
categories: Wie getWebsiteShopCategories (Center + Global, minShopCount: 0, Status Aktiv für Zählung) — nur Referenzen zur Token-Optimierung: je Eintrag id, name, slug (keine Zähler/Icons/Farben im Kontext).
chains: Alle ShopChain, die über Shops oder Filialen (ShopLocation) an dieses Center angebunden sind (max. 500, nach Name) — nur id, name, slug.
floors_summary: Für jede aktive Etage ein kompaktes Objekt — u. a. floorId, name, floorNumber, mapSvgChars (Zeichenzahl von mapSvg ohne Auslieferung des Strings), suggestsHybridSvg (Heuristik: typische Hybrid-Marker im Markup), hasMapImageUrl, hasShopViewBoxes, shopViewBoxesKeyCount, mapLocationActiveCount. Für Svg-Markup, mapLocations und Routenplanung weiter die öffentliche Route GET /api/wayfinding/floors?centerId= oder das MCP-Tool cockpit_public_wayfinding_floors.
Response (200): { success: true, data: { center?, shops?, services?, news?, events?, offers?, categories?, chains?, floors_summary? }, meta: { …limits, counts } }
data.shops[]: kompakte Objekte u. a. id, name, category, slug, floor, location, status, isShopLocation, chain, logo, coverImage, displayId.
4.3b Wartung: Duplikate, Ketten-Merge, Domains, Centerplan
Zusätzliche AgencyOS v1-Routen (Bearer, Center-Zugriff wie bei anderen /v1/centers/{centerId}-Endpunkten):
| Route | Methode | Zweck |
|---|---|---|
…/centers/{centerId}/shop-duplicates | GET | Potenzielle Shop-Duplikate im Center (Namens-Ähnlichkeit, Query threshold 0.5–1, optional includeArchived). Antwort: Gruppen mit suggestedKeepId und archiveCandidateIds. |
…/chains/duplicates | GET | Potenzielle ShopChain-Duplikate global (Query threshold, Default 0.8). |
…/chains/merge | POST | Ketten zusammenführen (Quelle → Ziel): Filialen/Einzelshops umhängen, Quell-Kette löschen. Body: sourceChainId + targetChainId, optional dryRun, oder Bulk pairs[] (max. 25). |
…/centers/{centerId}/verify-domains | POST | DNS/HTTPS für Custom Domains prüfen und domainStatus / sslStatus in der DB aktualisieren. |
…/centers/{centerId}/wayfinding/floors | GET | Kompakte Etagen-Liste (IDs, Metriken, Zähler) ohne mapSvg-Body — für MCP/KI; volles SVG weiter GET /api/wayfinding/floors. |
…/centers/{centerId}/map-locations | GET, POST | MapLocations listen (floorId, activeOnly) bzw. anlegen. |
…/centers/{centerId}/map-locations/assign | POST | Shop/Service an SVG-Fläche zuordnen (floorId, svgId, upsert). |
…/map-locations/{locationId} | PATCH | MapLocation aktualisieren (Zuordnung, Geo, aktiv/inaktiv). |
Hilfslogik: apps/dashboard/src/lib/integration/ (find-shop-duplicates, find-chain-duplicates, merge-shop-chains, shop-name-similarity, agency-map-location). Duplikat-Archivierung kann über POST …/content/bulk-archive mit contentType: "shop" erfolgen.
4.4 Inhalte ins Center pushen
POST {DASHBOARD_ORIGIN}/api/agencyos/v1/content/push
Gleiches Konzept wie WordPress Push-Content: Body mit optionalen Arrays shops, events, news, offers, services.
Zusätzlich Pflicht:
| Feld | Typ | Beschreibung |
|---|---|---|
centerId | string | UUID des Centers; zulässig unter denselben Regeln wie GET /v1/centers/{centerId} (Organisations-Key vs. Nutzer-Key inkl. Waisen dieser Integration) |
Hinweise:
- Es gibt keine WordPress-
pages-Speicherung inWordPressWebsite; Fokus liegt auf den Entitäts-Arrays. - In Cockpit werden betroffene Inhalte mit
sourceagencyosmarkiert (analogwordpressbeim WP-Endpunkt).
Idempotenz (Events, News, Angebote, Services — wie Shops)
Für events[], news[], offers[], services[] gilt dieselbe externe Referenz wie bei Shops:
agencyosId,externalId,clientReferenceoderredaktionsReferenz, alternativ bei AgencyOS-Push einid, das keine Cockpit-UUID und keinwp_*ist.- Cockpit speichert den Wert unter
metadata.agencyosPushIdund findet beim nächsten Push dieselbe Zeile zum Update (kein Duplikat). - Reihenfolge der Zuordnung: Cockpit-UUID →
agencyosPushId→ WordPress-wordpressPushId→ slug → (nur WordPress) Legacy nach Titel.
Zusätzlich: publishDate bei News akzeptiert auch Alias publishedAt oder date.
Shops: Ketten & KI-Redaktion
Redakteure arbeiten in AgencyOS oft per natürlicher Sprache mit einer KI. Die API ist so ausgelegt, dass die KI nicht zwingend Cockpit-UUIDs kennen muss:
| Ziel | Empfohlene Felder im shops[]-Eintrag |
|---|---|
| Dieselbe Marke nicht hundertmal anlegen | Pro logischem Shop eine stabile Referenz: agencyosId oder externalId (oder bei source-Push id als freier String, keine UUID) – wird im Cockpit unter metadata.agencyosPushId gespeichert und beim nächsten Push zum Update verwendet. Zusätzlich slug pro Center, falls vorhanden. |
| „Deichmann ist eine Kette“ | kette, marke, chainName, brandName oder shopChainName mit dem Markennamen → Cockpit verbindet den Shop mit dieser ShopChain (existiert sie nicht, wird sie angelegt; nur neue Kettendaten, keine Löschungen). |
| Bereits bekannte Kette (UUID aus Cockpit) | shopChainId oder chainId setzen. |
| Nur Einzelshop ohne Kette | standaloneShop / einzelshop / clearShopChain: true oder modus: "einzel" / "standalone". |
Fachlicher Hinweis: Im Cockpit gibt es zusätzlich Filialen als ShopLocation (Kette + Center). Der Push landet zunächst auf dem Shop inkl. shopChainId-Verknüpfung; eine vollständige Filialen-Spiegelung ist davon getrennt und kann bei Bedarf später ergänzt werden.
Ausführliche Feldliste (inkl. WordPress): WordPress Push-Content – shops.
Response: analog WordPress: success, data.summary, optional data.errors.
4.4a Push-Vorschau (keine DB-Änderung)
POST {DASHBOARD_ORIGIN}/api/agencyos/v1/content/push/preview
- Gleicher JSON-Body wie
POST …/content/push(inkl.centerId), gleicher Bearer-API-Key und Center-Zugriff. - Keine
create/update-Schreiboperationen in der Datenbank — geeignet für Freigabe-Workflows (z. B. AgencyOS zeigt die geplanten Änderungen, danach echter Push). - Response (200):
success,message,data.previewPlan(Liste geplanter Schritte mitentity,actioncreate/update,matchbei Zuordnung,existingId,planned, optionalnotes),data.summary, optionaldata.errors(Validierungs-/Ketten-Fehler wie beim echten Push). - Shop-Ketten: Würde eine neue
ShopChainangelegt (namensbasierte Auflösung), erscheint das inplanned.shopChainalswould_create_chainohne die Kette anzulegen; Hinweis ggf. innotes. GET …/preview: wird nicht unterstützt (405) — JSON-Payload gehört in den POST-Body.
Content-Entwürfe (Workflow) — Lesen, Freigabe, Kundenkontakt
Dieselbe ContentDraft-Logik wie unter Workflow & Freigaben im Dashboard, per AgencyOS-Key:
| Methode | Pfad | Kurzbeschreibung |
|---|---|---|
| GET | /api/agencyos/v1/drafts | Liste; Query wie bisher (status, contentType, centerId, campaignLabel, limit). Neu: includeData=1 — pro Eintrag geparstes data (Längen serverseitig begrenzt), u. a. customerCommunication, dispatchItemId, Inhaltsfelder. |
| GET | /api/agencyos/v1/drafts/{draftId} | Ein Entwurf; optional ?includeData=1. |
| PUT | /api/agencyos/v1/drafts/{draftId} | approve / reject (unverändert). |
| POST | /api/agencyos/v1/drafts/{draftId}/customer-touchpoint-suggestion | Body: { "scenario": "workflow_approve" | "workflow_reject" | "workflow_pending", "rejectionReason"?: string } — gleiche KI-Vorschläge wie im Dashboard (Betreff, E-Mail-Entwurf, interne Hinweise). Nutzt die globale Cockpit-KI-Konfiguration. |
MCP/Claude: cockpit_agencyos_list_drafts (optional includeData: true), cockpit_agencyos_get_draft, cockpit_agencyos_draft_customer_touchpoint, cockpit_agencyos_update_draft. Pro Entwurf: createdBy, createdByName, source, createdAt.
Audit-Log (Provenance)
GET {DASHBOARD_ORIGIN}/api/agencyos/v1/audit-logs
Bearer-API-Key; Center-Zugriff wie bei anderen v1-Routen.
| Query | Beschreibung |
|---|---|
centerId | Letzte Änderungen im Center (Tagesüberblick) — oder |
entityType + entityId | Historie eines Eintrags (offer, news, shop, event, service, job, center, shop-location) |
limit | Default 50, max 100 |
includeValues | 1 / true — oldValues / newValues mitliefern (Feldänderungen) |
Response (200): success, centerId, count, data[] mit u. a. action (CREATE/UPDATE/DELETE), entityType, entityId, userName, userId, timestamp, optional metadata.
MCP: cockpit_agencyos_audit_logs — z. B. „Wer hat dieses Angebot zuletzt bearbeitet?“ (entityType + entityId aus search_content). Mit summary: true / Query summary=1: Rangliste byUser (ideal für „wer war am aktivsten?“). Ab Deploy: auch MCP-Push, Uploads und Center-Anlage via AgencyOS werden protokolliert (metadata.integrationName, channel: agencyos).
Center-Team (Zugriff & Rollen)
GET {DASHBOARD_ORIGIN}/api/agencyos/v1/centers/{centerId}/team
Bearer-API-Key; Center-Zugriff wie bei anderen v1-Routen.
Response (200): center, count, data[] mit zugewiesenen Nutzer:innen (user.name, user.email), centerRole, Content-/Shop-Berechtigungen, assignedAt.
MCP: cockpit_agencyos_center_team — z. B. „Wie viele Personen haben Zugriff auf Center X?“
Website-Cache (Revalidate)
Nach POST …/content/push (Direkt-Push, keine Drafts) invalidiert das Dashboard automatisch den Next.js-Cache auf allen in CENTER_WEBSITE_URLS / CENTER_WEBSITE_URL eingetragenen Instanzen — wenn REVALIDATION_SECRET gesetzt ist. Die Push-Antwort kann data.revalidation enthalten (instances[] pro URL).
Manuell: POST {DASHBOARD_ORIGIN}/api/agencyos/v1/centers/{centerId}/revalidate (Bearer-Key, Body optional { "fullRevalidation": true }).
MCP: cockpit_agencyos_revalidate_website
v0/Vercel: Live-Seite braucht app/api/revalidate/route.ts, REVALIDATION_SECRET auf Vercel (identisch zum Dashboard) und websitePublicUrl im Center (automatisch via Deploy-Registrierung oder manuell im Dashboard). Legacy: globale CENTER_WEBSITE_URLS. Ohne Revalidate-Secret: Daten in Cockpit aktuell, Vercel kann ISR-Cache zeigen.
Frontend-Deploy registrieren (v0 → Cockpit)
POST {DASHBOARD_ORIGIN}/api/agencyos/v1/centers/{centerId}/frontend-deployments/register
Bearer-API-Key; Center-Zugriff wie bei anderen v1-Routen.
Body (JSON):
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
channel | string | ja | website | signage | companion |
origin | string | ja | Deploy-Origin ohne Pfad, z. B. https://xyz.vercel.app |
source | string | nein | z. B. vercel, v0, mcp |
Response (200): success, data mit origin, updated, editorMessage, optional revalidation.
Öffentlich (Vercel Deploy Hook): POST {DASHBOARD_ORIGIN}/api/public/frontend-deployments/register mit Header X-Cockpit-Register-Token: frt_… (pro Center im Dashboard erzeugt) — kein centerId nötig, Center wird am Token erkannt.
MCP: cockpit_agencyos_register_frontend_deployment
Datensicherheit: Aktualisiert nur die URL des gewählten Kanals (websitePublicUrl / …) und frontendDeploymentMeta — löscht keine Inhalte und ersetzt keine Custom Domains.
Siehe v0 Deploy ans Cockpit melden.
Homepage-Kacheln & Seiten-Inhalte (MCP-Schreiben)
Bisher nur Dashboard oder öffentliches GET — ab AgencyOS v1 auch Schreiben per API-Key (mit Audit + Revalidate):
| Ressource | Lesen | Schreiben |
|---|---|---|
| Homepage-Kacheln | GET …/centers/{centerId}/homepage-tiles | POST (neu), PATCH …/homepage-tiles/{tileId}, DELETE …/homepage-tiles/{tileId} |
| Page Content | GET …/centers/{centerId}/page-content (optional ?pageType=) | POST (Upsert pro pageType) |
MCP: cockpit_agencyos_homepage_tiles (action: list/create/update/delete), cockpit_agencyos_page_content (action: list/get/upsert). Öffentliche Vorschau weiterhin cockpit_public_homepage_tiles / cockpit_public_page_content.
Gültige pageType-Werte: u. a. ueber-uns, kontakt, anfahrt, datenschutz, impressum, shops, jobs — vollständige Liste in apps/dashboard/src/lib/page-content-api.ts.
Suche (Jobs & Offices)
GET {DASHBOARD_ORIGIN}/api/agencyos/v1/search — contentType unterstützt jetzt auch job und office (Komma-getrennt). MCP: cockpit_agencyos_search_content mit gleichen Parametern.
OpenAPI (Swagger) – optional nutzen
Im Repository liegt eine maschinenlesbare Spezifikation:
- Dateien:
/openapi/agencyos-integration.yaml(AgencyOS v1 — inkl.GET …/contextmit optionalemfloors_summary),/openapi/public-wayfinding-read.yaml(öffentlicher Wayfinding-Read ohne Key:floors,centerplan)
Empfehlung:
- Markdown (diese Seite) bleibt die verständliche Anleitung inkl. Ablauf und Sicherheit.
- OpenAPI lohnt sich, wenn ihr Client-Code generieren, Contract-Tests oder Swagger UI (z. B. editor.swagger.io mit Import-URL) nutzen wollt.
- Nachteil: Zwei Quellen – bei API-Änderungen OpenAPI und diese Seite pflegen, oder langfristig die Beschreibung aus OpenAPI in die Docs einbinden (Plugin/Aufwand).
Kurz: Swagger/OpenAPI ist sinnvoll, aber nicht Pflicht. Für AgencyOS reicht zunächst diese Doku; OpenAPI ist ein komfortables Zusatzangebot.
Implementierung im Repo (Referenz)
| Thema | Pfad |
|---|---|
| Magic Link GET/POST | apps/dashboard/src/app/api/agencyos/magic-link/route.ts |
| Complete | apps/dashboard/src/app/api/agencyos/magic-link/complete/route.ts |
| Organisationen (Session) | apps/dashboard/src/app/api/agencyos/organizations/route.ts |
| Connect-UI | apps/dashboard/src/app/agencyos/connect/page.tsx |
| v1 Centers | apps/dashboard/src/app/api/agencyos/v1/centers/route.ts, …/v1/centers/[centerId]/route.ts |
| v1 Center-Kontext (KI) | apps/dashboard/src/app/api/agencyos/v1/centers/[centerId]/context/route.ts |
| Ladelogik Kontext (Shops/Services) | apps/dashboard/src/lib/integration/load-agencyos-center-context.ts |
| v1 Content Push | apps/dashboard/src/app/api/agencyos/v1/content/push/route.ts |
| v1 Push-Vorschau (dry-run) | apps/dashboard/src/app/api/agencyos/v1/content/push/preview/route.ts |
| v1 Content-Entwürfe (Liste, Detail, Touchpoint) | apps/dashboard/src/app/api/agencyos/v1/drafts/route.ts, …/drafts/[draftId]/route.ts, …/drafts/[draftId]/customer-touchpoint-suggestion/route.ts |
| v1 Audit-Log | apps/dashboard/src/app/api/agencyos/v1/audit-logs/route.ts |
| v1 Center-Team | apps/dashboard/src/app/api/agencyos/v1/centers/[centerId]/team/route.ts |
| v1 Website-Config | …/centers/[centerId]/website-config/route.ts (GET + PUT) |
| v1 Website-Reiter-Schema | …/website-config-schema/route.ts, website-config-mcp-schema.ts, mcp-tab-hints-shared.ts, mcp-tab-hints-all-templates.ts, ilg-rgw-mcp-tab-fields.ts |
| v1 Homepage-Kacheln | …/centers/[centerId]/homepage-tiles/route.ts, …/homepage-tiles/[tileId]/route.ts |
| v1 Page Content | …/centers/[centerId]/page-content/route.ts |
| v1 Mediathek (Liste) | apps/dashboard/src/app/api/agencyos/v1/media/route.ts |
| v1 Suche | apps/dashboard/src/app/api/agencyos/v1/search/route.ts |
| Push → Revalidate | apps/dashboard/src/lib/integration/agencyos-content-revalidate.ts |
| Push-Audit (Integration) | apps/dashboard/src/lib/integration/integration-audit.ts, process-center-entity-push.ts |
| Gemeinsame Push-Logik (mit WordPress geteilt) | apps/dashboard/src/lib/integration/process-center-entity-push.ts |
Verwandte Dokumentation
- KI-Website-Bau & Cockpit-Sync (Konzept & Phasen) – Zielbild Greenfield/Brownfield, bestehende Bausteine, Roadmap ohne Datenverlust
- WordPress Push-Content – Detaillierte Feldlisten für
shops/events/news/offers/services - Centerplan WordPress-Integration – Kontext WordPress (nicht AgencyOS)
Nutzungsstatistik: Seitenaufrufe werden anonymisiert erfasst. Im Umami-Dashboard nach diesem Pfad filtern: /en/developer-guide/api-agencyos-integration