Zum Hauptinhalt springen

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 Automationen. Vollständiger Key wie gewohnt einmalig in der Antwort, nicht dauerhaft in der UI.

Persönliches MCP-Login (zusätzlich, ersetzt den Key nicht): Nach cockpitOS-Login unter Mein Konto → MCP verbinden ein Token sk_mcp_… erzeugen (GET/POST /api/mcp/tokens, DELETE /api/mcp/tokens/{id}). Derselbe AgencyOS-v1-Bearer-Platz: Authorization: Bearer sk_mcp_…. Rechte = Cockpit-Rechte dieser Person. Journal/userId und Audit zeigen den User. MCP-Env: COCKPIT_MCP_USER_TOKEN hat Vorrang vor COCKPIT_AGENCYOS_API_KEY. GET /api/agencyos/v1/me bzw. MCP cockpit_whoami zeigt authKind.

MCP-Tool-Referenz: Alle registrierten Tools mit Parametern und Beispiel-Prompts — MCP-Tool-Referenz.

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.

Basis-URL

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

  1. Magic Link anfordern (serverseitig, ohne Nutzer-Session im Cockpit): POST /api/agencyos/magic-link mit integrationName und optional returnUrl.
  2. 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).
  3. API-Key per Polling holen: Solange status === "pending", regelmäßig GET /api/agencyos/magic-link?token=… aufrufen. Sobald status === "completed", liefert die Antwort data.apiKey (Präfix typisch sk_agencyos_) sowie data.accessScope ("organization" oder "user") und data.organizationId (UUID oder null bei nutzerweitem Key).
  4. Speichern: API-Key sicher in AgencyOS hinterlegen (Geheimnis, nicht in Logs/URLs).
  5. 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 nach https://team.cockpit-os.de/...) mitsendet, kann das Cockpit nach erfolgreichem Abschluss dorthin weiterleiten und setzt u. a. agencyos=connected sowie cockpit_agency=connectedohne Key. Den Key nur aus der GET-Antwort lesen, wenn status === "completed".

Unterschied zu WordPress

AspektWordPress-PluginAgencyOS
Geltungsbereichein Center pro Website-KeyOrganisation: 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-PushPOST /api/wordpress/push-content (Key = Website)POST /api/agencyos/v1/content/push mit centerId im JSON
Doku Push-BodyWordPress Push-ContentGleiche Entity-Arrays (shops, events, …); siehe unten

POST {DASHBOARD_ORIGIN}/api/agencyos/magic-link

  • Auth: keine (öffentlich wie beim WordPress-Magic-Link).
  • CORS: Access-Control-Allow-Origin: *, OPTIONS unterstützt.

Body (JSON):

FeldTypPflichtBeschreibung
integrationNamestringjaAnzeigename der Integration in Cockpit (z. B. "AgencyOS Produktion")
returnUrlstringneinhttp:// 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).


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.apiKey gesetzt
  • data.integrationId gesetzt
  • data.accessScope: "organization" oder "user"
  • data.organizationId: UUID der verbundenen Organisation oder null, wenn accessScope === "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/complete mit JSON:
    • Organisations-Key: { "token": "…", "organizationId": "<uuid>" }
    • Nutzer-Key: { "token": "…", "accessScope": "user" } (kein organizationId nötig)

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 mit data:mime;base64,-Präfix.
  • Raw Body: Content-Type: image/* oder video/* oder application/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_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_list_media.

Partner-Medien-Einreichungen (KAM-Warteschlange)

Center-Ansprechpartner reichen Fotos/Videos über die Partner-App (v0) oder Token-Links ein; Redaktion bearbeitet die Warteschlange im Cockpit (Center-Reiter Medien-Einreichungen). Nutzer-Doku: Partner-Medien-Einreichungen.

AgencyOS (Bearer API-Key):

MethodePfadBeschreibung
GET/api/agencyos/v1/centers/{centerId}/media-intakeListe; Query status, limit, offset
POST/api/agencyos/v1/centers/{centerId}/media-intakeEinreichung — Body: topic, description?, conditions?, contributionType?, eventDate?, shopId?, mediaItems[]
PATCH/api/agencyos/v1/centers/{centerId}/media-intake/{submissionId}Status/Notiz — Body: status (draft | new | in_review | accepted | rejected | published), notes?
GET/api/agencyos/v1/centers/{centerId}/partner-tasksPartner-Aufgaben listen
POST/api/agencyos/v1/centers/{centerId}/partner-tasksAufgabe anlegen — Body: title, description?, dueDate?, shopId?, requiresAck?

MCP: cockpit_list_media_intake, cockpit_create_media_intake, cockpit_update_media_intake_status, cockpit_create_partner_task.

Partner-API (Manager-App / MallCrew): Vollständige Referenz → Partner-API v1.

Basis /api/partner/v1/ — Login POST …/auth/loginpartnerToken; Shops GET …/centers/{centerId}/shops; Upload POST …/media/upload?centerId=; Einreichung GET/POST …/centers/{centerId}/media-intake; Entwürfe/Kommentare/KI/Aufgaben ab 0.1.213 (siehe Partner-API-Doku).

Gast ohne Login: GET/POST …/intake/submit + POST …/intake/upload mit X-Intake-Token: mi_…. Manager-App: Proxy + HttpOnly-Cookie — Details: docs/v0-partner-media-intake-prompt.md, docs/v0-mallcrew-prompt.md.

AgencyOS Partner-Aufgaben: GET/POST /api/agencyos/v1/centers/{centerId}/partner-tasks — MCP: cockpit_create_partner_task.

Migrationen:

SkriptMigration
pnpm db:migrate:media-intake20260828130000_add_media_intake_SAFE.sql
pnpm db:migrate:mallcrew-extensions20260828160000_add_mallcrew_extensions_SAFE.sql

UI (Redaktion): Video wie Bilder über FileUploadPOST /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 agencyIntegrationId dieser 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:

FeldTypBeschreibung
namestring
addressstring
citystring
postalCodestring

Optional: country (Default "DE"), slug (sonst automatisch aus Name, global eindeutig), description, phone, email, website, latitude, longitude, status (Default "active").

Organisation (wie im Cockpit):

FeldTypBeschreibung
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 / noOrganizationboolean trueCenter wird ohne Organisation angelegt (organizationId: null), bleibt aber dieser Integration zugeordnet (intern agencyIntegrationId), damit sie es in GET/Push weiter nutzen kann.
organizationIdnullGleiche Bedeutung wie withoutOrganization: true.
organizationIdstring (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}

GET enthält websiteUrls: cockpitPreview = https://preview.cockpit-os.de/{slug} (Cockpit-Layout testen), liveSubdomain = https://{slug}.cockpit-os.de (Live-Standard). recommendedPreview für Vorschau-Links nutzen — nicht die Subdomain.

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.

PUT: nur gesendete Felder werden geändert. Erlaubt u. a. name, address, city, postalCode, country, phone, email, website, description, openingHours, latitude, longitude, status, slug, websiteEnabled, brandDna (JSON-Partial: toneOfVoice, targetAudience, usps, tabooWords, examplePost, visualStyle, …), brandGuidelines (Freitext). GET liefert dieselben Felder. MCP: cockpit_update_center. Dashboard: Center → Reiter Design (?tab=design). Pflichtfelder dürfen nicht auf null gesetzt werden. Koordinaten: gültige Zahlen (latitude −90…90, longitude −180…180); null oder leerer String löscht den Wert. Öffentlich lesbar für v0 unter GET …/public-visitor-surfacedata.wayfindingMap.

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 (agencyIntegrationId wird 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. (agencyIntegrationId wird 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, chatbotConfig, comingSoonEnabled / comingSoonHeadline / comingSoonConfig, themeConfig, qrCode, templateContent (Template-Reiter).

Schreiben: PUT {DASHBOARD_ORIGIN}/api/agencyos/v1/centers/{centerId}/website-config
Partial-Update; templateContent wird deep-merged.

chatbotConfig (Besucher-Chatbot): Aktivierung und UI-Einstellungen wie im Dashboard-Reiter Website → Chatbot. Persistenz in AIConfiguration (level=center, appType=center_website) plus themeOverrides.chatbotEnabledFallback. Minimal zum Einschalten: { "chatbotConfig": { "enabled": true } }. API-Keys in Antworten maskiert (••••); beim PUT werden maskierte Keys nicht überschrieben.

comingSoonEnabled / comingSoonHeadline / comingSoonConfig (Vorschaltseite): Wie Dashboard-Reiter Vorschaltseite. comingSoonConfig ist ein JSON-Objekt (z. B. Hintergrund, Zusatztexte). Partial PUT — nur gesendete Keys werden geändert.

themeConfig (Theme-Vererbung): { "inheritFromOrganization": true } setzt websiteTheme=null (Organisations-Theme erben). { "inheritFromOrganization": false } ohne customTheme.iddesign-only (nur Design-Farben). Mit customTheme.id → explizites Center-Theme.

qrCode (Wayfinding-QR-Modal): Texte für das QR-Modal im Centerplan (title, description, buttonText). Persistenz in themeOverrides.qrCodeConfig — analog Dashboard-Reiter QR-Code.

MCP-Beispiel (Chatbot):

cockpit_update_center_website_config {
"centerId": "…",
"chatbotConfig": {
"enabled": true,
"greetingMessage": "Willkommen! Wie kann ich helfen?",
"aiAssistantPageTemplate": "mally"
}
}

Aggregierte Bot-Konfiguration (bots-profile): Bots & Assistenten Hub. Reads: GET …/bots-profile, MCP cockpit_get_bots_profile, Public data.bots. Writes: PATCH …/bots-profile, MCP cockpit_update_bots_profile. Signage Template-Optionen only: MCP cockpit_update_signage_template_options (Lesen: cockpit_signage_template_options). Bot-UI: github.com/sawmuedev/mallpilot-chat-core (v0 importiert — siehe V0_INSTRUCTION_BOT_CORE_PATCH.md).

MCP-Beispiel (QR-Modal-Texte):

cockpit_update_center_website_config {
"centerId": "…",
"qrCode": {
"title": "QR-Code scannen",
"description": "Scanne den Code, um auf dem Handy weiterzumachen.",
"buttonText": "Verstanden"
}
}

legalConfig.cookieConsentTitleTemplate und legalConfig.cookieConsentDescription werden in themeOverrides persistiert (wie Dashboard-Reiter Legal). Öffentliche Website: GET …/public-visitor-surfacevisitorPrivacy.cookieBannerTitleTemplate.

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-surfacedata.templatePublicContent (Template-Reiter: Hero, Footer, …) + data.apiHints.pageContentGet / homepageTilesGet.
Nicht GET …/website-config im Browser — 401. Schreiben bleibt MCP: cockpit_*. 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):

  1. cockpit_website_config_schema (mit websiteTemplate oder centerId)
  2. Reiter aus tabs[] wählen → fields[] / templateContentPath / customContentPath lesen
  3. Bestehende Werte per GET (get_center_website_config / page_content)
  4. Partial PUT/upsert nur mit geänderten Keys
  5. Revalidate (automatisch in API-Response, sofern konfiguriert)

MCP:

ToolFunktion
cockpit_get_center_website_configVolle Config lesen
cockpit_update_center_website_configPartial Update
cockpit_website_config_schemaReiter + Feldpfade pro Template
cockpit_mcp_discover_toolsTool-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:

QueryDefaultMaxBeschreibung
includecenter,shopsKomma-getrennt: center, shops, services, news, events, offers, categories, chains, floors_summary
shopLimit5002000Max. Anzahl kombinierter Einträge (Einzelshops + Filialen), sortiert nach Name
serviceLimit200500Nur wenn services in include
newsLimit80200Nur wenn news in include
eventsLimit80200Nur wenn events in include
offersLimit80200Nur 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):

RouteMethodeZweck
…/centers/{centerId}/shop-duplicatesGETPotenzielle Shop-Duplikate im Center (Namens-Ähnlichkeit, Query threshold 0.5–1, optional includeArchived). Antwort: Gruppen mit suggestedKeepId und archiveCandidateIds.
…/chains/duplicatesGETPotenzielle ShopChain-Duplikate global (Query threshold, Default 0.8).
…/chains/mergePOSTKetten 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-domainsPOSTDNS/HTTPS für Custom Domains prüfen und domainStatus / sslStatus in der DB aktualisieren.
…/centers/{centerId}/wayfinding/floorsGETKompakte Etagen-Liste (IDs, Metriken, Zähler) ohne mapSvg-Body — für MCP/KI; volles SVG weiter GET /api/wayfinding/floors.
…/centers/{centerId}/map-locationsGET, POSTMapLocations listen (floorId, activeOnly) bzw. anlegen.
…/centers/{centerId}/map-locations/assignPOSTShop/Service an SVG-Fläche zuordnen (floorId, svgId, upsert).
…/map-locations/{locationId}PATCHMapLocation aktualisieren (Zuordnung, Geo, aktiv/inaktiv).
…/centers/{centerId}/signage-template-optionsPUTSignage Template-Optionen (Partial): { templateId, kiosk?, companion? }signageTemplateOptions[templateId]. MCP: cockpit_update_signage_template_options.
…/centers/{centerId}/signage-global-optionsGETSignage global (öffentlich): Wartungsmodus + centerLogoUrl — alle Templates. MCP: cockpit_signage_global_options.
…/centers/{centerId}/signage-global-optionsPUTSignage global schreiben: { maintenanceModeEnabled?, maintenanceMessage? }signageTemplateOptions._global. MCP: cockpit_update_signage_global_options.
…/wayfinding/floors/{floorId}PATCHCenterplan-Etage aktualisieren (name, floorNumber, isDefault, isActive, color) — kein SVG-Upload.
…/centers/{centerId}/touchscreensGET, POSTTouchscreen-/Stele-Standorte listen bzw. anlegen (floorId, position, screenOrientation, optional chromeOsSerialNumber, annotatedAssetId, annotatedLocation).
…/touchscreens/{touchscreenId}PATCH, DELETETouchscreen aktualisieren (inkl. floorId, Google-Admin-Felder) bzw. deaktivieren (soft delete: isActive=false, Plan-Zuordnung entfernt). Bei Etagenwechsel wird eine MapLocation-Zuordnung auf anderer Etage automatisch gelöscht.
…/touchscreens/{touchscreenId}/assignPOST, DELETETouchscreen einer MapLocation zuordnen bzw. Zuordnung entfernen.
…/signage-screens/search?q={term}GETSignage-Screen-Suche (SN, Asset-ID, Stele-/Center-Name) — nur Center im Agency-Key-Scope; optional centerId.

Intern (kioskOS, nicht öffentlich): GET /api/internal/kioskos/screens?q={term} — Bearer KIOSKOS_SCREENS_SECRET; Suche über Center-Name/Slug, Asset-ID, Seriennummer.

MCP-Tools (AgencyOS-Key): cockpit_list_wayfinding_floors, cockpit_update_wayfinding_floor, cockpit_list_map_locations, cockpit_create_map_location, cockpit_assign_map_location, cockpit_update_map_location, cockpit_list_touchscreens, cockpit_create_touchscreen, cockpit_update_touchscreen, cockpit_search_signage_screens, cockpit_assign_touchscreen, cockpit_unassign_touchscreen, cockpit_delete_touchscreen.

Typischer Agent-Workflow (Centerplan-Mapping):

  1. cockpit_public_center_by_slugcenterId
  2. cockpit_list_wayfinding_floorsfloorId pro Etage
  3. cockpit_public_wayfinding_floors (omitMapSvg/floorNumber) oder cockpit_list_map_locations → bestehende svgId/Zuordnungen prüfen
  4. cockpit_assign_map_location mit floorId, svgId, shopId (oder serviceId)
  5. Optional Stele: cockpit_create_touchscreen + cockpit_assign_touchscreen
  6. Google-Admin-Zuordnung: cockpit_search_signage_screenscockpit_update_touchscreen (SN, Asset-ID)

Hinweis Eingänge/Routing: Waypoints/Eingänge (GET /api/centers/{centerId}/entrances) sind weiterhin öffentlich lesbar; Schreiben erfolgt im Dashboard (Centerplan-Editor) — noch kein AgencyOS/MCP-Schreibtool.

Hilfslogik: apps/dashboard/src/lib/integration/ (find-shop-duplicates, find-chain-duplicates, merge-shop-chains, shop-name-similarity, agency-map-location).

Inhalte archivieren (Soft-Delete, kein Hard-Delete)

CockpitOS löscht Shops/News/Events/Angebote/Services nicht physisch aus der Datenbank — sie werden auf status: "Archiviert" gesetzt (ggf. isActive: false). Grund: Verknüpfungen (Angebote/Events/Jobs an Shops), Audit, Wiederherstellung im Dashboard.

WegEndpoint / MCP
EinzelnPUT …/agencyos/v1/shops|events|news|offers/{id} mit { "status": "Archiviert" } — MCP: cockpit_archive_content
MehrerePOST …/content/bulk-archivecontentType: offer, event, news, shop, service; ids[] oder (nur Angebote/Events/News) autoExpired: true
Push-AlternativePOST …/content/push mit z. B. shops: [{ "id": "…", "status": "Archiviert" }]

MCP: cockpit_archive_content, cockpit_bulk_archive. Kein cockpit_delete_shop — absichtlich nur Archivierung.

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:

FeldTypBeschreibung
centerIdstringUUID 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 in WordPressWebsite; Fokus liegt auf den Entitäts-Arrays.
  • In Cockpit werden betroffene Inhalte mit source agencyos markiert (analog wordpress beim WP-Endpunkt).

Idempotenz (Events, News, Angebote, Services — wie Shops)

Für events[], news[], offers[], services[] gilt dieselbe externe Referenz wie bei Shops:

  • agencyosId, externalId, clientReference oder redaktionsReferenz, alternativ bei AgencyOS-Push ein id, das keine Cockpit-UUID und kein wp_* ist.
  • Cockpit speichert den Wert unter metadata.agencyosPushId und findet beim nächsten Push dieselbe Zeile zum Update (kein Duplikat).
  • Reihenfolge der Zuordnung: Cockpit-UUIDagencyosPushIdWordPress-wordpressPushIdslug → (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:

ZielEmpfohlene Felder im shops[]-Eintrag
Dieselbe Marke nicht hundertmal anlegenPro 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 KettestandaloneShop / 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.

Filialen: Branche pro Standort (nicht Kette)

Wenn dieselbe Kette in mehreren Centern unterschiedliche Branchen-Namen braucht (z. B. Aufräumen doppelter Kategorie-Labels nur in einem Center):

SchrittAPI / MCP
Kategorien lesenGET …/categories?centerId=… oder cockpit_list_categoriesid der Ziel-Kategorie
Filiale findencockpit_list_shop_locations (centerId, ggf. chainSlug) → locationId
Override setzenPUT …/shop-locations/{locationId} mit { "categoryId": "<uuid>" } oder MCP cockpit_update_shop_location
Wieder Kette erben{ "categoryId": null }

Nicht cockpit_update_chain / Ketten-category ändern, wenn andere Center die Ketten-Branche unverändert lassen sollen. Die öffentliche Website wertet ShopLocation.categoryRef vor ShopChain.category aus.

Kategorie-Kacheln auf der Website (categoryIcon / cardSettings)

Globale Defaults (alle Kategorien ohne eigenes cardSettings) liegen im Dashboard-Reiter Webseiten → Design unter designConfig.categoryCardSettings — u. a. showElements.categoryIcon.

ZielAPI / MCP
Global für Center (Icon ein/aus für alle Kategorien)PUT …/centers/{centerId}/website-config mit designConfig.categoryCardSettings.showElements.categoryIcon oder MCP cockpit_update_center_website_config
Nur eine Kategorie (Center-Override bei globaler Kategorie)PUT …/categories/{categoryId} mit { "centerId": "…", "cardSettings": …, "icon": "…", "coverImage": "…" } oder MCP cockpit_update_category
LesenGET …/categories liefert cardSettings; öffentlich: public-visitor-surfacedesign.designConfig.categoryCardSettings

Bei globalen Kategorien ist centerId im PUT Pflicht (Center-spezifische Override-Kopie, wie im Dashboard).

Website-Config Lesen/Schreiben (Agenten): cockpit_get_center_website_config / cockpit_update_center_website_config — Reiter-Feldpfade via cockpit_website_config_schema. Wichtig für Base-Template:

SettingPfad
Kategorie-Icon globaldesignConfig.categoryCardSettings.showElements.categoryIcon
NOW! Hero/TiteldesignConfig.toGoHeroImage, toGoGreeting, toGoTitle, toGoSubtitle, toGoBackgroundImage
Center-InhaltecontentConfig.shortName, tagline, socialMedia, specialDays, …
Shops-Filter / Shop-BildercontentConfig.features.showCategoryFilter, showShopImages, …
Centerplan-HintergrundcenterplanConfig.showMapBackground
Centerplan-Renderer/LayoutcenterplanConfig.mapRenderer, layoutMode, overlayGeoBounds, … — vollständige Feldliste: cockpit_website_config_schema → Tab centerplan
Parken-SeiteparkingConfig.parkingHeroTitle, parkingContentBoxes, … — Tab parking im Schema
Seiten ein/auspagesConfig.shops, pagesConfig.wayfinding, … — Tab pages im Schema
Themenwelten (Gastro/Kategorie/Büro)GET/POST …/centers/{centerId}/theme-worlds?kind=… · MCP cockpit_list_center_theme_worlds, cockpit_upsert_center_theme_world

contentConfig.features und designConfig.categoryCardSettings werden partial gemerged (nur gesendete Keys überschreiben).

Themenwelten (Base-Template)

AktionRESTMCP
ListeGET …/centers/{centerId}/theme-worlds?kind=gastronomy|category|officecockpit_list_center_theme_worlds
AnlegenPOST …/theme-worlds Body { kind, name, slug, … }cockpit_upsert_center_theme_world (ohne themeId)
AktualisierenPUT …/theme-worlds/{themeId} Body { kind, … }cockpit_upsert_center_theme_world (mit themeId)
LöschenDELETE …/theme-worlds/{themeId}?kind=…cockpit_delete_center_theme_world

shopIds / shopLocationIds beim Update ersetzen die Shop-Zuordnung (wie Dashboard). officeIds nur bei kind=office.

Shops/Filialen: Vorübergehend geschlossen (sichtbar + Badge)

Shop oder Filiale bleibt status=Aktiv und auf der Website in Liste/Suche sichtbar; Besucher sehen Badge/Hinweis.

FeldTypBeschreibung
temporarilyClosedbooleantrue = Hinweis aktiv
temporarilyClosedNotestring | nullOptionaler Badge-Text; leer = „Vorübergehend geschlossen“
temporarilyClosedUntilISO 8601 | nullOptional; nach diesem Datum wird der Hinweis beim Rendern ausgeblendet

API: PUT …/shops/{shopId} bzw. PUT …/shop-locations/{locationId} · MCP: cockpit_update_shop / cockpit_update_shop_location. Nutzer-Doku: Shops vorübergehend geschlossen.

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 mit entity, action create/update, match bei Zuordnung, existingId, planned, optional notes), data.summary, optional data.errors (Validierungs-/Ketten-Fehler wie beim echten Push).
  • Shop-Ketten: Würde eine neue ShopChain angelegt (namensbasierte Auflösung), erscheint das in planned.shopChain als would_create_chain ohne die Kette anzulegen; Hinweis ggf. in notes.
  • 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:

MethodePfadKurzbeschreibung
GET/api/agencyos/v1/draftsListe; 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. Antwort enthält guestReviewUrl / guestReviewExpiresAt, wenn ein gültiger Token existiert (ohne Roh-Token).
PUT/api/agencyos/v1/drafts/{draftId}{ "action": "approve" | "reject", "reason"?: string } — Freigabe führt executeContentDraft aus (wie Dashboard).
GET/POST/api/agencyos/v1/drafts/{draftId}/review-linkGast-Freigabe-Link erzeugen (POST) oder gültigen Link lesen (GET). Antwort: url, guestReviewUrl, expiresAt.
POST/api/agencyos/v1/drafts/{draftId}/customer-touchpoint-suggestionBody: { "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.

Gast-Freigabe (Website-Entwürfe, ohne Login)

Analog Social (/freigabe/social/{token}):

Öffentliche UI{DASHBOARD_ORIGIN}/freigabe/content/{token}
Öffentliche APIGET/POST {DASHBOARD_ORIGIN}/api/content/review/{token}
Token-Prefixcr_… (30 Tage Gültigkeit beim Erzeugen)
Dashboard-Link erzeugenPOST /api/content/drafts/{draftId}/review-link (Session) oder AgencyOS-Route oben
POST Body (Gast){ "action": "approve" | "reject", "reviewerName"?: string, "reason"?: string, "comment"?: string } — bei Ablehnung landen reason/comment in rejectionReason und feedback

AgencyOS Cutover: Eigene Route /review/{token}302 auf {DASHBOARD_ORIGIN}/freigabe/content/{token} (Token 1:1 übernehmen, wenn Cockpit-Link; sonst neuen Link per POST …/review-link erzeugen). Keine eigene Content-Freigabe-UI in AgencyOS mehr pflegen.

MCP/Claude: cockpit_list_drafts (optional includeData: true), cockpit_get_draft (liefert guestReviewUrl), cockpit_content_draft_review_link (action: create|get), cockpit_draft_customer_touchpoint, cockpit_update_draft. Pro Entwurf: createdBy, createdByName, source, createdAt.


Product-Feedback (Dashboard-Inbox)

Getrennt von Besucher-Feedback. Nur globaler AgencyOS-Key (accessScope=global) oder COCKPIT_FEEDBACK_AGENT_SECRET.

MethodePfadZweck
GET/api/agencyos/v1/product-feedbackInbox listen (status, type, limit)
POST/api/agencyos/v1/product-feedbackAnlegen (type + message, optional centerId/pageLabel) — Melder cockpitOS-Assistent. Oder Claim (id / referenceCode)
GET/api/agencyos/v1/product-feedback/{id}Ein Ticket (UUID oder FB-…)
POST/api/agencyos/v1/product-feedback/{id}/dispatchBestehendes Ticket nachschicken (nur bug/change + Status new)
PATCH/api/agencyos/v1/product-feedback/{id}status, resolutionNote, cursorPrUrl, messageAppend. done nur mit Antwort und grünem /api/health

MCP: cockpit_support_intake (Pflicht-Einstieg: Alltagsbeschreibung reicht, zuerst nachschauen), danach cockpit_product_feedback_list, cockpit_product_feedback_create, cockpit_product_feedback_claim, cockpit_product_feedback_dispatch, cockpit_product_feedback_update.

Faustregel für Agenten: Die Beschreibung der Person ist der Prompt. Nicht nach Fachbegriffen oder der technischen Ursache fragen. Nicht an Menschen weiterleiten, bevor im Cockpit nachgeschaut wurde. Ticket-message = ihr Wortlaut.

Nutzer-Doku: Feedback an cockpitOS.

Audit-Log (Provenance)

GET {DASHBOARD_ORIGIN}/api/agencyos/v1/audit-logs

Bearer-API-Key; Center-Zugriff wie bei anderen v1-Routen.

QueryBeschreibung
centerIdLetzte Änderungen im Center (Tagesüberblick) — oder
entityType + entityIdHistorie eines Eintrags (offer, news, shop, event, service, job, center, shop-location)
limitDefault 50, max 100
includeValues1 / trueoldValues / newValues mitliefern (Feldänderungen)

Response (200): success, centerId, count, data[] mit u. a. action (CREATE/UPDATE/DELETE), entityType, entityId, entityLabel, actorDisplay (primary, secondary, isIntegration), userName, userId, timestamp, optional metadata.

MCP: cockpit_audit_logs — z. B. „Wer hat dieses Angebot zuletzt bearbeitet?“ (entityType + entityId aus search_content). Mit summary: true / Query summary=1: Rangliste byUser. Persönliches MCP-Token: userId = Cockpit-User, channel: mcp_user. AgencyOS-Key: weiter channel: agencyos. GET /api/agencyos/v1/me / cockpit_whoami zeigt authKind. Journal-Liste enthält actor (Name, E-Mail).


MCP-Aktionen rückgängig machen (Journal)

Was: Jeder schreibende AgencyOS-Aufruf (POST/PUT/PATCH/DELETE) speichert den Vorher-Stand. Agenten können die letzten Aktionen listen und eine Aktion zurücksetzen — Inhalte, Website-Config, DNS, nicht nur ein einzelner Endpunkt.

Warum: Audit zeigt nur „wer hat geändert“. Undo braucht den konkreten Vorher-Stand.

Wer: MCP/Claude, AgencyOS-Integrationen. Redaktion sieht den Effekt in den betroffenen Modulen.

Wo: MCP cockpit_undo_mcp_actions. API:

MethodePfadBeschreibung
GET/api/agencyos/v1/actionsListe (Query: hours default 24 max 72, limit, centerId)
POST/api/agencyos/v1/actions{ action: "list" | "undo", actionId?, last?, dryRun?, hours? } — Undo zuerst mit dryRun: true

So testen

  1. Eine unkritische Änderung schreiben (z. B. Shop-Name oder DNS-Dry-Run danach dryRun=false).
  2. cockpit_undo_mcp_actions mit action=list — die Aktion erscheint mit undoable: true.
  3. Dieselbe ID mit action=undo und dryRun=true — Vorschau der Schritte.
  4. Denselben Aufruf mit dryRun=false — Daten bzw. DNS sind wieder wie vorher.

Betrieb: Neue Tabelle agency_os_actions (additive Migration). Keine Secrets im Journal (Passwort, API-Key, Token). Nicht rückgängig: Benutzer löschen, Passwort-Reset, Einladung, Medien-Upload, Social-Engage, Domain kaufen/transferieren, Zone löschen.

MCP: cockpit_undo_mcp_actions — „Mach die letzte Änderung rückgängig“ → action=undo, last=true, zuerst dryRun=true.


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_center_team — z. B. „Wie viele Personen haben Zugriff auf Center X?“


Benutzer-Verwaltung (AgencyOS / MCP)

Vollständige Benutzerverwaltung im Scope des API-Keys (Organisation, nutzerweit oder global) — analog zur Dashboard-Nutzer-Verwaltung, ohne SUPER_ADMIN-Rolle.

MethodePfadBeschreibung
GET/api/agencyos/v1/usersListen (Filter: organizationId, role, email, centerId, limit, offset)
POST/api/agencyos/v1/usersAnlegen (name, email, role; optional organizationId, centerAssignments, organizationAssignments, invite)
GET/api/agencyos/v1/users/{userId}Einzelner Benutzer inkl. Zuweisungen
PUT/api/agencyos/v1/users/{userId}Aktualisieren (isActive, Rolle, Zuweisungen; newPassword: true = Reset-Link)
DELETE/api/agencyos/v1/users/{userId}Löschen
POST/api/agencyos/v1/users/{userId}/inviteEinladungs-E-Mail erneut senden
POST/api/agencyos/v1/users/{userId}/reset-passwordPasswort-Reset-Link per E-Mail

Scope: Organisations-Key verwaltet nur Benutzer der eigenen Organisation (bzw. Center in dieser Org). Nutzerweiter Key: Org/Center gemäß Cockpit-Rechten des anbindenden Users. Global-Key: alle Benutzer außer Super-Admins.

Center-Zuweisungen: Nur Center, die der Key ohnehin lesen/schreiben darf (centerAccessibleByAgencyIntegration).

MCP: cockpit_list_users, cockpit_get_user, cockpit_create_user, cockpit_update_user, cockpit_delete_user, cockpit_invite_user, cockpit_reset_user_password

Code: apps/dashboard/src/lib/integration/agencyos-user-admin.ts, Routen unter apps/dashboard/src/app/api/agencyos/v1/users/


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_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-Kanäle lesen (Cockpit vs. v0)

GET {DASHBOARD_ORIGIN}/api/agencyos/v1/centers/{centerId}/frontend-channels

Bearer-API-Key; gleiche Übersicht wie Dashboard Website-Management → Frontend-Kanäle (v0).

Response (200): success, data mit u. a.:

FeldBedeutung
channels.website.cockpitPreview{slug}.cockpit-os.de
channels.website.v0VercelStaging / v0VercelProductionRegistrierte v0/Vercel-URLs
resolvedUrls.websiteEffektiver Dashboard-Link (v0 wenn gesetzt, sonst Cockpit)
frontendDeploymentMetaLetzte Meldung pro Kanal (registeredAt, origin, …)
interpretation.isV0WebsiteConnectedKurz: v0 schon gemeldet?
websiteStackStatusCockpit / Vercel / Live-Status
apiHintsu. a. qrResolveGet, qrScanPost, qrTrackPost, companionQrPath, websiteQrPath (Standort-QRs, siehe API-Vertrag)

MCP: cockpit_frontend_channels — nach Deploy prüfen; fehlt Meldung → cockpit_register_frontend_deployment. data.apiHints enthält Standort-QR-Vertrag (qrResolveGet, qrScanPost, …).

Standort-QRs (MCP): cockpit_qr_codesaction=list|get|create|resolve; öffentliche Auflösung wie v0 /companion/qr/[qrCodeId]. Nicht Handoff (/api/public/handoff-sessions).

Hinweis: Keine Route-Registry pro Pfad — nur Kanal-URLs (Cockpit-Subdomain vs. Vercel vs. Custom Domain).


Website-Live-Inventar (Bulk)

GET {DASHBOARD_ORIGIN}/api/agencyos/v1/website-live-inventory

Bearer-API-Key; liefert für alle zugänglichen Center (max. 500) den Website-Live-Status in einem Aufruf — statt pro Center frontend-channels zu pollen.

Query-Parameter (optional):

ParameterBeschreibung
chainSlug / chainNameWie bei GET …/centers — nur Center mit Filiale dieser Kette
organizationId / organizationNameNur Center einer Organisation
websiteEnabledOnly=trueNur Center mit aktivierter Website
excludeTestLike=trueTest-/Demo-Center (Name/Slug) ausblenden

Response (200): success, data.items[], data.summary, meta.

Pro Eintrag in items:

FeldBedeutung
bucketoff | cockpit_only | v0_preview | v0_production | custom_domain_live
isV0WebsiteConnectedv0/Vercel-Deploy gemeldet?
urls.cockpitPreview / v0Staging / v0Production / customDomainKanal-URLs
urls.activeSurfaceEffektive Besucher-Oberfläche (Priorität: Production → Domain → Staging → Cockpit)
domainStatus / domainVerifiedDNS/Domain-Stand
websiteStackOverallLeveloff | configured | pending | active | warning

summary.byBucket zählt Center pro Klasse; summary.v0ConnectedCount und customDomainActiveCount für schnelle KPIs.

MCP: cockpit_website_live_inventory — gleiche Filter als Tool-Parameter.

Hinweis: Kein HTTP-Ping auf Live-URLs — Klassifikation aus Cockpit-Daten (wie Dashboard „Frontend-Kanäle“). Für einen einzelnen Center-Detailstand weiterhin cockpit_frontend_channels oder cockpit_dns_status.


United-Domains-Konten

Was / Warum: Die UD-APIs (Reseller + Retail-DNS) sind das Konto — nicht das Cockpit. Agenten sollen alle Domains dort listen, einzelne Einträge inkl. DNS lesen und Records setzen können (Parked, Redirects, Altbestand, DNS-only). Der Cockpit-Abgleich ist optional. cockpit_website_live_inventory bleibt nur für Live-URLs im Cockpit; cockpit_dns_status nur für ein Center.

GET {DASHBOARD_ORIGIN}/api/agencyos/v1/domains/ud-inventory

Bearer-API-Key; liest direkt aus den United-Domains-Konten: Reseller QueryDomainList + QueryDNSZoneList + Retail-Zonen. centers[] ist nur der Abgleich mit Cockpit-Centern.

Query-Parameter (optional):

ParameterBeschreibung
qFilter: Domain oder Center-Name (Teilstring)
organizationId / organizationNameNur Treffer zu Centern dieser Organisation (z. B. ILG)
unmatchedOnly=trueNur UD-Domains ohne passendes Center — nur Global-Key, ohne Org-Filter

Mandantentrennung: Org- und User-Keys sehen nur Domains, die zu ihren Centern passen. Domains im UD-Konto ohne Cockpit-Treffer (andere Kunden, Parked, Altbestand) nur Super-Admin bzw. Agency-Key mit accessScope=global.

Response (200): success, data.configured, data.warnings, data.summary, data.items[], data.cockpitOnly[].

Pro Eintrag in items:

FeldBedeutung
domain / apexDomain aus UD, normalisiert
sourcesreseller und/oder retail
status / expirationWenn Reseller wide=1 liefert
centersPassende Cockpit-Center (leer = nur UD)

cockpitOnly: Center-Domains, die nicht im UD-Konto liegen (anderer Registrar/Nameserver).

Eine Domain (Status + DNS):

GET {DASHBOARD_ORIGIN}/api/agencyos/v1/domains/ud-domain?domain=example.de

Registrierung (StatusDomain), Ablauf, Nameserver, Zone/SOA (StatusDNSZone), Ist-Records, Web-Weiterleitungen (QueryWebFwdList, wenn die API sie liefert). Kein centerId. Authinfo/EPP werden nicht zurückgegeben. capabilities sagt, welche Record-Typen schreibbar sind.

DNS setzen:

POST {DASHBOARD_ORIGIN}/api/agencyos/v1/domains/ud-dns

Body: { "domain": "example.de", "dryRun": true, "createZoneIfMissing": false, "records": [{ "op": "set", "type": "A", "name": "@", "value": "216.24.57.1", "ttl": 300 }] }.

FeldBedeutung
dryRunDefault true — nur Plan/Vorschau. Schreiben nur mit false.
createZoneIfMissingReseller: Zone anlegen, falls keine existiert. Retail: Domain muss schon im Portfolio liegen.
records[].opadd (dazu), set (gleicher Name+Typ ersetzen), delete
records[].typeNur A, AAAA, CNAME, MX, TXT, SRV, CAA
records[].valueMX: "10 mail.example.de." (Priorität + Ziel). SRV: "0 1 443 mail.example.de." (Priorität + Gewicht + Port + Ziel). Alternativ bei SRV getrennte Felder priority, weight, port, target — werden zu einem Wert zusammengeführt.

name ist @, www, eine Subdomain oder * (Zonen-Wildcard, auch als *.example.de). Beim Abgleich gilt * gleich *.<apex> — sonst bleibt matchedExisting bei Leichen-Wildcards 0.

Nicht möglich (bewusst): NS/SOA ändern, Zone löschen, Nameserver, Authinfo, Domain kaufen/transferieren. CNAME auf @ wird abgelehnt. Max. 20 Records pro Aufruf. Kill-Switch: COCKPIT_DISABLE_DNS_PROVIDER_WRITE=1.

Mandantentrennung (Schreiben): wie Lesen — Org-/User-Keys nur, wenn die Domain zu einem ihrer Center passt. Global-Key: jede Domain im Konto.

Dashboard: Domains & Go-Live — Filter Alle / Mit Center / Nur UD, Button Details. Session: GET /api/domains/ud-inventory, GET /api/domains/ud-domain?domain=, POST /api/domains/ud-dns.

MCP: cockpit_ud_account (list / inspect / update-dns) · cockpit_ud_dns · cockpit_list_ud_domains · cockpit_ud_domain

So testen

  1. inspect auf eine Domain — dns.records und capabilities.writableTypes prüfen.
  2. update-dns / POST …/ud-dns mit dryRun=true — Response enthält plan und nextStep, UD bleibt unverändert.
  3. Denselben Body mit dryRun=false — Record erscheint in inspect.
  4. Org-Key auf eine fremde Domain: 403.

Betrieb: Dashboard-Env UD_RESELLING_LOGIN / UD_RESELLING_PASSWORD und/oder UD_DNS_API_KEY. Vollständige Kontoliste nur Super-Admin / Developer bzw. Agency-Key mit accessScope=global.


Outstand-Kanal-Inventar (Bulk)

GET {DASHBOARD_ORIGIN}/api/agencyos/v1/social/center-accounts

Bearer-API-Key; liefert für alle zugänglichen Center (max. 500) die Outstand.io-Kanal-Verknüpfungen in einem Aufruf — statt pro Center website-config zu lesen (dort sind socialMedia nur Website-Links, kein Outstand).

Query-Parameter (optional):

ParameterBeschreibung
chainSlug / chainNameWie bei GET …/centers — nur Center mit Filiale dieser Kette
organizationId / organizationNameNur Center einer Organisation
centerIdEinzelnes Center (UUID)
connectedOnly=trueNur Center mit mindestens einem aktiven Outstand-Kanal
activeAccountsOnly=trueIn accounts nur aktive Mappings (isActive=true)
excludeTestLike=trueTest-/Demo-Center (Name/Slug) ausblenden

Response (200): success, data.items[], data.summary, meta.

Pro Eintrag in items:

FeldBedeutung
outstandConnectedMindestens ein aktiver Kanal verknüpft?
activeAccountCount / totalAccountCountAnzahl aktiver bzw. aller Mappings
accounts[]outstandAccountId, network, username, isActive, …

summary.centersWithOutstand / centersWithoutOutstand für schnelle KPIs.

MCP: cockpit_list_outstand_centers — gleiche Filter als Tool-Parameter.

Hinweis: Liefert Verknüpfungsstand aus center_social_accounts, keine Outstand-Performance-Metriken (Reichweite, Follower). Dafür weiterhin Dashboard Social Reporting.


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):

FeldTypPflichtBeschreibung
channelstringjawebsite | signage | companion
originstringjaDeploy-Origin ohne Pfad, z. B. https://xyz.vercel.app
sourcestringneinz. 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_register_frontend_deployment — vorher optional cockpit_frontend_channels zur Verifikation.

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):

RessourceLesenSchreiben
Homepage-KachelnGET …/centers/{centerId}/homepage-tilesPOST (neu), PATCH …/homepage-tiles/{tileId}, DELETE …/homepage-tiles/{tileId}
Page ContentGET …/centers/{centerId}/page-content (optional ?pageType=)POST (Upsert pro pageType)

MCP: cockpit_homepage_tiles (action: list/create/update/delete), cockpit_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/searchcontentType unterstützt jetzt auch job und office (Komma-getrennt). MCP: cockpit_search_content mit gleichen Parametern.


Büros & Praxen (AgencyOS + MCP)

Was: CRUD für Offices/Praxen über AgencyOS v1 — Schreibfelder aligned mit Dashboard /api/offices (Partial-Update).

Warum: MCP/AgencyOS kannten bisher nur Basis-Felder; Logo, Bilder, Qualifikationen und Veröffentlichungszeiten fehlten.

Wer ist betroffen: Redaktion (Dashboard unverändert), Dev/Agenten (AgencyOS, MCP).

MethodePfadBeschreibung
GET/api/agencyos/v1/officesListe (centerId, q, type, status, limit)
POST/api/agencyos/v1/officesAnlegen — centerId, name, type oder officeTypeId
GET/api/agencyos/v1/offices/{officeId}Detail inkl. officeType, Center
PUT/api/agencyos/v1/offices/{officeId}Partial-Update — nur gesetzte Felder
DELETE/api/agencyos/v1/offices/{officeId}Archivieren (status: Archiviert)

Schreibbare Felder (Auszug): Stammdaten (name, type, status, specialty, description, …), Kontakt (phone, email, website, bookingUrl), Medien (logo, coverImage, teamPhoto, images), Team (owner, teamSize), JSON-Felder (openingHours, services, languages, qualifications, certifications, insuranceTypes, appointmentType, onlineServices), Barrierefreiheit/Parkplatz/Notfall, Flags (featured, verified), officeTypeId, publishStartDate, publishEndDate, displayId.

JSON-Felder: Text, JSON-String oder Array/Objekt — Serialisierung wie im Dashboard (apps/dashboard/src/lib/agencyos-office-fields.ts).

Workflow-Entwurf: POST mit submitToWorkflow: true → Content-Draft statt Direktanlage (wie bisher).

MCP: cockpit_list_offices, cockpit_get_office, cockpit_create_office, cockpit_update_office, cockpit_archive_office

So testen:

  1. GET /api/agencyos/v1/offices?centerId=… mit Bearer-Key.
  2. POST mit name, type, optional logo und qualifications: ["Facharzt"].
  3. PUT …/{officeId} mit featured: trueupdatedFields in der Antwort prüfen.
  4. MCP cockpit_get_office mit derselben officeId.

Teams-Benachrichtigungen (cockpitOS → AgencyOS, Phase 2)

Was: Wenn im Cockpit eine Social-Freigabe angefordert wird, kann das Dashboard optional einen HTTP-Hook an AgencyOS senden. AgencyOS liefert dann proaktive Teams-DMs oder Adaptive Cards an die gewählten Freigeber (Notification-Hub: team.cockpit-os.de).

Warum: Redaktion arbeitet im Cockpit; der Teams-Bot lebt in AgencyOS. Das CMS bleibt Content-Quelle und meldet Events — keine eigene Bot-Logik im Monorepo.

Wer ist betroffen: Redaktion/Freigeber (Teams), Dev/Betrieb (Env auf beiden Seiten).

Ablauf

cockpitOS (dieses Repo) — bereits vorbereitet

ThemaPfad
HTTP-Clientapps/dashboard/src/lib/integration/agencyos-teams-notify.ts
Hook nach Freigabe-Anfrageapps/dashboard/src/lib/integration/social-review-request-notify.ts
AuslöserapplySubmitReview in social-approval-actions.ts

Env (Dashboard, nur Server):

VariablePflichtBeschreibung
AGENCYOS_TEAMS_NOTIFY_URLja (für Aktivierung)Vollständige URL, z. B. https://team.cockpit-os.de/api/integrations/notifications/social-review
AGENCYOS_TEAMS_NOTIFY_SECRETja (für Aktivierung)Gemeinsames Geheimnis; Header Authorization: Bearer …

Ohne beide Variablen: kein Aufruf — Dashboard-Glocke, Desktop-Push und E-Mail laufen unverändert.

AgencyOS — zu implementieren (Notification-API)

POST {AGENCYOS_ORIGIN}/api/integrations/notifications/social-review

  • Auth: Authorization: Bearer <AGENCYOS_TEAMS_NOTIFY_SECRET> (gleicher Wert wie in Cockpit-Env)
  • Content-Type: application/json

Body (von Cockpit gesendet):

{
"source": "cockpitos",
"draftId": "cuid",
"centerId": "uuid",
"centerName": "Center XY",
"caption": "Post-Text…",
"requester": {
"userId": "cockpit-user-uuid",
"name": "Max Mustermann",
"email": "max@example.com"
},
"approvers": [
{ "userId": "uuid", "name": "Anna", "email": "anna@example.com" }
],
"approvalMode": "all",
"reviewUrl": "https://dashboard.cockpit-os.de/dashboard/social/approvals?highlight=…",
"guestReviewUrl": "https://dashboard.cockpit-os.de/freigabe/social/token (optional)"
}
FeldTypBeschreibung
source"cockpitos"Herkunftssystem
draftIdstringSocialPostDraft.id im Cockpit
approvalMode"all" | "one"Entspricht Cockpit-Freigaberegel
reviewUrlstringDeep-Link ins Cockpit-Freigabe-Board
guestReviewUrlstring | nullOptionaler Gast-Link ohne Login

User-Mapping: Cockpit-userId und AgencyOS-userId sind nicht identisch. AgencyOS soll Freigeber primär über email (oder teamsUserId / Entra aadObjectId) auflösen.

Erfolg (200):

{
"success": true,
"sent": { "teams": 2, "inApp": 1, "skipped": 0 }
}

Fehler: 401 ungültiges Secret, 422 fehlende Pflichtfelder.

Erwartetes Verhalten in AgencyOS:

  1. Pro Approver mit teamsConversationRef: Adaptive Card (Caption, Center, Freigeben-Link zum Cockpit)
  2. Ohne Bot-Kontakt: In-App-Notification + PWA-Push in AgencyOS (falls User dort existiert)
  3. Kein Blockieren des Cockpit-Workflows bei AgencyOS-Ausfall (Cockpit loggt nur)

So testen (End-to-End)

Schnellster Weg (Smoke-Test, ~30 Sekunden):

./scripts/smoke-agencyos-teams-notify.sh

Das Skript fragt Secret und Freigeber-E-Mail ab (oder aus Env / --load-env aus apps/dashboard/.env.local). Es prüft Auth (401) und sendet eine Test-Card an AgencyOS → Teams.

VariableWas du brauchst
AGENCYOS_TEAMS_NOTIFY_SECRETGleicher Wert in Cockpit und AgencyOS (Render)
TEST_APPROVER_EMAILE-Mail einer Person, die in AgencyOS existiert und den Teams-Bot einmal geöffnet hat

Vollständiger Flow (Cockpit-UI):

  1. AgencyOS: Notification-Endpunkt deployen + Secret setzen
  2. Cockpit Render: AGENCYOS_TEAMS_NOTIFY_URL + AGENCYOS_TEAMS_NOTIFY_SECRET setzen → Redeploy
  3. Freigeber: Teams-Bot in AgencyOS mindestens einmal öffnen (teamsConversationRef)
  4. Cockpit: Social-Post → Freigabe anfordern mit Test-Prüfer
  5. Prüfer erhält Teams-Nachricht; Cockpit-Glocke/E-Mail weiterhin wie bisher

Abgrenzung

SystemSocial-FreigabenTeams
cockpitOSSocialPostDraft, /dashboard/social/approvalsHook nur wenn Env gesetzt
AgencyOSeigener SocialPost / RedaktionsplanBot, Crons, Cards (MVP dort)

Social-Review-Outcome (Phase 2b)

Was: Nach Freigabe/Ablehnung/Publish-Fehler meldet Cockpit das Ergebnis an AgencyOS → Teams-DM an den Ersteller (nicht den Freigeber).

Warum: Der Anfragende erhält bewusst keine Freigabe-Anfrage-Benachrichtigung; das Outcome schließt den Kreis im Teams-Bot.

ThemaPfad
HTTP-Clientapps/dashboard/src/lib/integration/agencyos-teams-notify.ts (notifyAgencyOsTeamsSocialReviewOutcome)
AuslösernotifySocialReviewOutcome in social-review-notify.ts (nach Dashboard + E-Mail)

URL: Aus AGENCYOS_TEAMS_NOTIFY_URL abgeleitet (…/social-review…/social-review-outcome) oder explizit AGENCYOS_TEAMS_NOTIFY_OUTCOME_URL.

POST {AGENCYOS_ORIGIN}/api/integrations/notifications/social-review-outcome

  • Auth: gleiches AGENCYOS_TEAMS_NOTIFY_SECRET
  • Body:
{
"source": "cockpitos",
"draftId": "cuid",
"centerId": "uuid",
"centerName": "Center XY",
"caption": "Post-Text…",
"outcome": "approved",
"requester": { "userId": "…", "name": "…", "email": "sb@…" },
"reviewer": { "userId": "…", "name": "…", "email": "julia@…" },
"rejectionReason": null,
"reviewUrl": "https://dashboard.cockpit-os.de/dashboard/social/approvals?highlight=…"
}
outcomeBedeutung
approvedFreigegeben und Publish erfolgreich (oder geplant)
rejectedAbgelehnt — rejectionReason gesetzt
publish_failedFreigegeben, Outstand-Publish fehlgeschlagen

AgencyOS: Endpunkt + Card an requester.email (User-Match per E-Mail).

Social-Review-Digest (AgencyOS v1)

Was: Offene Cockpit-SocialPostDraft mit status=in_review, gruppiert nach Freigeber-E-Mail — für täglichen Digest-Cron in AgencyOS (neben native AgencyOS-Posts).

MethodePfad
GET/api/agencyos/v1/social-review-digest
GET/api/agencyos/v1/social-review-digest?approverEmail=julia@…

Auth: Agency-API-Key. MCP: cockpit_social_review_digest.

Response (Auszug):

{
"success": true,
"generatedAt": "2026-07-10T09:00:00.000Z",
"recipientCount": 3,
"totalOpenDrafts": 5,
"recipients": [
{
"userId": "uuid",
"name": "Julia",
"email": "julia@example.com",
"openCount": 2,
"items": [
{
"draftId": "cuid",
"centerName": "Burgaupark Jena",
"captionExcerpt": "Sommer-Sale…",
"requesterName": "Saad",
"approvalMode": "any",
"reviewUrl": "https://dashboard.cockpit-os.de/dashboard/social/approvals?highlight=…",
"guestReviewUrl": null
}
]
}
]
}

Smoke:

COCKPIT_AGENCYOS_API_KEY=sk_agencyos_… ./scripts/smoke-agencyos-social-review-digest.sh

In-Chat-Freigabe (AgencyOS Proxy)

Was: AgencyOS kann Freigabe/Ablehnung direkt aus der Teams-Card auslösen — Proxy zu Cockpit.

MethodePfad
POST/api/agencyos/v1/social/drafts/{draftId}/review

Body:

{
"action": "approve",
"approverEmail": "julia@example.com",
"reason": "optional bei reject"
}
  • Freigeber wird per E-Mail im Cockpit aufgelöst (muss aktiv sein und in assignedApproverIds stehen).
  • Gleiche Regeln wie Dashboard (all/any, Multi-Freigabe, Publish).
  • MCP: cockpit_social_review (draftId, action, approverEmail, optional reason).

Implementierung: load-agencyos-social-review-digest.ts, social-review-digest/route.ts, social/drafts/[draftId]/review/route.ts.

Social-Entwürfe, Community, Performance (AgencyOS v1)

Was: Agenten können Social-Entwürfe listen, anlegen, bearbeiten, Freigabe auslösen, Community lesen und moderieren sowie Social-Performance auswerten. Nicht über AgencyOS/MCP: KI-Bild, Kanal-OAuth, PDF-Export.

MethodePfadMCP
GET/api/agencyos/v1/social/drafts?centerId=&status=cockpit_list_social_drafts
POST/api/agencyos/v1/social/draftscockpit_create_social_draft
GET/api/agencyos/v1/social/drafts/{draftId}cockpit_get_social_draft
PATCH/api/agencyos/v1/social/drafts/{draftId}cockpit_update_social_draft
DELETE/api/agencyos/v1/social/drafts/{draftId}cockpit_delete_social_draft
POST/api/agencyos/v1/social/drafts/{draftId}/reviewcockpit_social_review
GET/api/agencyos/v1/social/drafts/{draftId}/quality-checkcockpit_social_draft_quality_check (optional forSchedule)
GET/api/agencyos/v1/social/engage?centerId=&engagePostId=&days=&includeMetrics=cockpit_social_engage
POST/api/agencyos/v1/social/engagecockpit_social_engage_action
GET/api/agencyos/v1/social/reporting?centerId=&days=&includeAds=cockpit_social_reporting
GET/api/agencyos/v1/social/analytics?centerId=&days=&postLimit=cockpit_social_analytics
POST/api/agencyos/v1/social/sync-metricscockpit_social_sync_metrics
GET/api/agencyos/v1/social/center-accountscockpit_list_outstand_centers
GET/api/agencyos/v1/centers/{centerId}/social-templatescockpit_social_templates action=list
POST/api/agencyos/v1/centers/{centerId}/social-templatescockpit_social_templates action=create
PATCH/api/agencyos/v1/centers/{centerId}/social-templatescockpit_social_templates action=set_required (required)
PATCH/api/agencyos/v1/centers/{centerId}/social-templates/{templateId}cockpit_social_templates action=update
DELETE/api/agencyos/v1/centers/{centerId}/social-templates/{templateId}cockpit_social_templates action=delete

Social-Bildvorlagen (FB-Y4JZXXR3): PNG/WebP-Rahmen pro Center, optionaler Canva-Link. Kein Canva-OAuth. required=true blockiert Freigabe/Publish ohne containers[0].options.socialTemplateId aus demselben Center. Default: nicht Pflicht.

POST Entwurf anlegen (/social/drafts):

{
"centerId": "uuid",
"caption": "Sommer-Sale ab morgen!",
"scheduledAt": "2026-09-01T10:00:00.000Z",
"postType": "feed",
"mediaUrls": ["https://….b-cdn.net/…/bild.jpg"],
"firstComment": "Link in Bio",
"source": "karma-migration",
"action": "submit_review",
"assignedApproverEmails": ["juw@schickma.de"],
"approvalMode": "any"
}
  • containers[] = volles Medien-/Options-JSON; mediaUrls[] (MCP-Shortcut) wird serverseitig zu containers gemappt.
  • Optional: centerIds, linkUrl, location, socialAccountIds, campaignLabel, revisionNotes.
  • Ohne action: Status pending (Entwurf speichern).
  • action: submit_review: Freigabe anfordern (+ optional Teams-Hook).
  • action: publish_direct: Sofort publishen/planen — nur wenn der Integration-Nutzer (Magic-Link) Approver-Rolle hat.
  • assignedApproverEmails: Robi/Teams-freundlich — E-Mails aktiver Cockpit-Nutzer.

PATCH Entwurf (/social/drafts/{draftId}): Felder wie oben; action: cancel_schedule für geplante Posts; scheduledAt, Caption und Medien sind auch bei Status scheduled bzw. approved mit Termin änderbar. Mit Outstand-ID synchronisiert der bestehende Update-Pfad; ohne ID nur Cockpit-Speicher. Vertrag der Felder unverändert.

Reel: postType: "reel" braucht mindestens ein Video in containers[].media (MP4/MOV/WEBM oder Dateiname mit Video-Endung). Bild allein → 400 vor Outstand. Geplante Posts laufen über Outstand-scheduledAt; cockpitOS gleicht überfällige Termine per Cron/Webhook ab, ohne Force-Publish.

DELETE Entwurf (/social/drafts/{draftId}): Entwürfe, Zur Freigabe, Überarbeitung und geplante Posts löschen (geplant inkl. Outstand). Live-Posts: 409. MCP: cockpit_delete_social_draft.

  • centerId ist bei Engage, Reporting, Analytics und Sync Pflicht.
  • Engage lesen: Kommentartexte mit engagePostId + includeComments=true; Metriken mit includeMetrics=true. Response enthält platformCommentId, platformPostId, zernioAccountId für Aktionen.
  • Reporting mit includeAds=true: Meta/Google Ads-KPIs (Zernio-Snapshots) neben organischem Überblick.
  • Engage schreiben (POST): action = reply | like | hide | delete | deleteRemote | repost. delete = Kommentar IG/Threads. deleteRemote = Live-Post auf Facebook/X/LinkedIn/Bluesky. repost = erneut teilen (optional quote). Body-Felder wie Dashboard /api/social/engage.
  • Analytics liefert Plattform-KPIs (IG/FB/…), Posts mit Metriken, Follower-Trend, Engagement — ideal für Claude-Auswertungen.
  • Reporting = KPI-Überblick ohne Post-Liste; Sync holt Live-Daten von Outstand (optional Zernio).

Typischer Agent-Flow:

  1. cockpit_list_outstand_centers — welche Kanäle sind verbunden?
  2. cockpit_social_sync_metrics — frische Zahlen (bei leeren KPIs)
  3. cockpit_social_analytics — Auswertung (Plattformvergleich, Top-Posts)
  4. cockpit_social_engage — Community/Kommentare vertiefen (engagePostId, includeComments=true)
  5. cockpit_social_engage_action — Antwort/Like/Hide mit IDs aus Schritt 4

Implementierung: load-social-analytics.ts, load-agencyos-social-cockpit.ts, social-engage-actions.ts, social/analytics/route.ts, social/sync-metrics/route.ts, …

Organization Accountability (ILG/HBB, „Wer ist zuständig?“)

Was: Aggregiert KAM-Zuständigkeiten und Org-Kontakte pro Organisation aus Cockpit — für Bot-Fragen wie „Wer ist für ILG zuständig?“.

MethodePfad
GET/api/agencyos/v1/organizations/{slugOrId}/accountability

slugOrId: Slug (ilg, hbb), Name oder UUID.

MCP: cockpit_organization_accountability

Response (Auszug):

{
"success": true,
"data": {
"organization": { "id": "…", "name": "ILG", "slug": "ilg" },
"centerCount": 27,
"centersWithKam": 1,
"keyAccountManagers": [
{
"userId": "…",
"name": "Julia Warns",
"email": "juw@schickma.de",
"assignmentKind": "primary",
"centerCount": 1,
"centers": [{ "centerId": "…", "centerName": "Burgaupark Jena" }]
}
],
"hasAccountability": true,
"hints": ["Haupt-KAM laut Cockpit: Julia Warns (juw@schickma.de)", "26 von 27 Centern ohne KAM-Zuweisung im Cockpit"]
}
}

Hinweis: AgencyOS search_centers nutzt heute nur Customer.accountManagerId — ohne Cockpit-Fallback bleibt die Bot-Antwort leer, obwohl Cockpit-Daten existieren können. AgencyOS soll bei leerem accountManager diese API nachladen.

Pfad Cockpit: load-organization-accountability.ts, organizations/[slugOrId]/accountability/route.ts

Organisations-KAM mit Vererbung (2026-07-10)

Was: KAMs können einmal pro Organisation gesetzt werden und gelten für alle Center — außer ein Center hat eigene KAM-Zuweisung (Abweichung).

EbeneDashboardAPI
Organisation/dashboard/organizations/{slug} — KAM-KarteGET/PUT /api/organizations/{slug}/key-account-managers
Center (Abweichung)Center-Profil → Tab TeamGET/PUT /api/centers/{id}/key-account-managersrevertToOrganization: true setzt Vererbung zurück
AgencyOSGET/PUT …/organizations/{slugOrId}/key-account-managers, GET/PUT …/centers/{centerId}/key-account-managers (liefert source, hasCenterOverride)

Vererbungsregel: Keine Center-Zeilen → Organisations-KAMs. Mindestens eine Center-Zeile → nur Center-KAMs.

MCP lesen: cockpit_center_key_account_managers, cockpit_organization_key_account_managers, cockpit_organization_accountability, cockpit_kam_briefing_index, cockpit_kam_quality_watch_index

MCP schreiben: cockpit_set_center_key_account_managers, cockpit_set_organization_key_account_managers — Body wie Dashboard-PUT (userIds[], assignments[], Center: revertToOrganization: true). User-UUIDs über cockpit_center_team oder Nutzer-Verwaltung.

Noch ohne AgencyOS/MCP: KAM-Zuweisung aus der Nutzer-Bearbeitung (kamCenterAssignments am User-Endpoint).


Externe Ansprechpartner (Center)

Was: Agentur, Haustechnik, Sicherheit usw. pro Center — Tab Team & Ansprechpartner im Dashboard; Speicherung in CenterContact mit metadata.contactKind = external.

Warum: KI-Kontext, Notfall-Listen und AgencyOS-Automation brauchen strukturierte externe Kontakte — bisher nur Dashboard ohne MCP.

MethodePfadBeschreibung
GET/api/agencyos/v1/centers/{centerId}/external-contactsListe
POST/api/agencyos/v1/centers/{centerId}/external-contactsAnlegen
PUT/api/agencyos/v1/centers/{centerId}/external-contacts/{contactId}Aktualisieren
DELETE/api/agencyos/v1/centers/{centerId}/external-contacts/{contactId}Löschen

Body (POST/PUT): name (Pflicht), optional company, role, email, phone, type (Haustechnik, …), emergency, responseTime, notes.

MCP: cockpit_center_external_contacts, cockpit_create_center_external_contact, cockpit_update_center_external_contact, cockpit_delete_center_external_contact

Migration: packages/database/migrations/20260722_center_contact_metadata_SAFE.sql — Spalte metadata (JSONB), email nullable.

So testen: Dashboard Center → Team → Kontakt anlegen → MCP GET mit gleicher centerId → Eintrag in data[].


Website-Ansprechpartner (Center Website → Kontakte)

Was: Center Manager und weitere Ansprechpartner für Kontaktseite / Impressum — Tab Center Website → Grundeinstellungen → Kontakte (CenterContact mit metadata.contactKind = website).

Abgrenzung: Nicht externe Team-Kontakte (Agentur, Haustechnik) — die liegen unter Center → Team & Ansprechpartner (external-contacts).

MethodePfadBeschreibung
GET/api/agencyos/v1/centers/{centerId}/contactsListe (Auth)
POST/api/agencyos/v1/centers/{centerId}/contactsAnlegen
PUT/api/agencyos/v1/centers/{centerId}/contacts/{contactId}Aktualisieren
DELETE/api/agencyos/v1/centers/{centerId}/contacts/{contactId}Löschen
GET/api/centers/{centerId}/contacts/publicÖffentlich (v0, nur aktive)

Body (POST/PUT): role + name (Pflicht), optional email, phone, department, photoUrl, displayOrder, isActive.

MCP lesen: cockpit_center_website_contacts, cockpit_public_center_contacts (v0)

MCP schreiben: cockpit_create_center_website_contact, cockpit_update_center_website_contact, cockpit_delete_center_website_contact

v0: GET …/public-visitor-surfaceapiHints.contactsPublicGet

So testen: Dashboard Center Website → Kontakt anlegen → cockpit_center_website_contacts → gleicher Eintrag; v0: cockpit_public_center_contacts.


Center-Mail (Mailbox & Alias, Phase 0–1)

Was: Kunden-Postfächer und Weiterleitungen. Inventar in cockpitOS (center_mailboxes, center_mail_aliases); der Mail-Anbieter steckt hinter MailProvider.

Hinweis: Kein öffentlicher Endpunkt. Passwort nur einmal in der POST-Response, nie in der Liste. Phase 0b: aktive Mailbox-/Alias-Adressen sind Empfänger für POST /api/centers/{centerId}/send-contact-inquiry (Resend).

MethodePfadBeschreibung
GET/api/agencyos/v1/centers/{centerId}/mailboxesListe
POST/api/agencyos/v1/centers/{centerId}/mailboxesAnlegen (localPart, optional domain, quotaMb, name)
PATCH/api/agencyos/v1/centers/{centerId}/mailboxes/{mailboxId}Quota / aktiv / Name
POST/api/agencyos/v1/centers/{centerId}/mailboxes/{mailboxId}/passwordNeues Passwort (confirm=true)
DELETE/api/agencyos/v1/centers/{centerId}/mailboxes/{mailboxId}Löschen
GET/api/agencyos/v1/centers/{centerId}/mail-aliasesWeiterleitungen
POST/api/agencyos/v1/centers/{centerId}/mail-aliasesAnlegen (localPart, target, optional domain). Extern: verificationRequired
POST/api/agencyos/v1/centers/{centerId}/mail-aliases/{aliasId}/verifyBestätigung erneut senden
DELETE/api/agencyos/v1/centers/{centerId}/mail-aliases/{aliasId}Löschen
GET/api/agencyos/v1/centers/{centerId}/mail-dnsSoll-DNS + Diff (nichts schreiben)
POST/api/agencyos/v1/centers/{centerId}/mail-dnsdryRun Standard true; Schreiben nur mit dryRun=false und confirm=true
GET POST/api/agencyos/v1/mailboxesOhne Center: Plattform/eigenständig. POST: localPart, optional domain/centerId. Nur Super-Admin-Key ohne Center
PATCH DELETE/api/agencyos/v1/mailboxes/{mailboxId}Quota / aktiv / Name bzw. löschen
POST/api/agencyos/v1/mailboxes/{mailboxId}/passwordNeues Passwort (confirm=true)
GET POST/api/agencyos/v1/mail-aliasesWeiterleitungen ohne Pflicht-Center
POST/api/agencyos/v1/mail-aliases/{aliasId}/verifyBestätigung erneut senden
DELETE/api/agencyos/v1/mail-aliases/{aliasId}Löschen
GET/api/agencyos/v1/mailcow/uiCapabilities (UI-Texte/App-Links ja; Logo/SOGo nein). Nur globaler oder persönlicher Super-Admin-Key
POST/api/agencyos/v1/mailcow/uiconfirm=true Pflicht. action=ui (Titel/Hilfe/Footer) oder action=appLinks (links[])

MCP: cockpit_list_mailboxes, cockpit_create_mailbox, cockpit_update_mailbox, cockpit_reset_mailbox_password, cockpit_delete_mailbox, cockpit_list_mail_aliases, cockpit_create_mail_alias, cockpit_resend_mail_alias_verification, cockpit_delete_mail_alias, cockpit_mail_dns_plan, cockpit_mail_dns_apply, cockpit_mailcow_admin_status, cockpit_mailcow_set_ui, cockpit_mailcow_set_app_links

Dashboard-Session (gleiche Services, kein Bearer): /api/centers/{centerId}/mailboxes, mail-aliases, mail-dns. Reiter E-Mail am Center. Öffentliche Bestätigung: POST /api/mail-alias/verify (Token, kein Bearer). Super-Admin: GET/POST /api/mailboxes, /api/mailcow/ui.

Migration: packages/database/migrations/20260903160000_add_center_mail_SAFE.sql, 20260909090000_add_mail_alias_verify_SAFE.sql, 20260909100000_add_mail_owner_kind_SAFE.sql

So testen: Center-Mail Provisioning


Operational Briefing (Center-KAM-Daily, Phase 2)

Was: Liefert pro Center die operativen To-dos aus dem Cockpit — offene Social-Postings, Google-Rezensionen ohne Antwort, Reporting-Erinnerungen, Website-Workflow-Entwürfe. AgencyOS nutzt die API für tägliche Teams-DMs an KAMs (Cron center-kam-briefing).

Warum: KAM-Daten (Tasks) liegen in AgencyOS; Social/Reviews/Reporting in Cockpit — eine Aggregat-API vermeidet Duplikate.

Endpunkte

MethodePfadBeschreibung
GET/api/agencyos/v1/centers/{centerId}/operational-briefingEin Center
GET/api/agencyos/v1/operational-briefing?centerIds=id1,id2Bulk (max. 30, nur erlaubte Center)

Auth: Authorization: Bearer <sk_agencyos_…> (gleicher Key wie andere v1-Routen)

MCP: cockpit_operational_briefing — Parameter centerId oder centerIds (kommagetrennt).

Beispiel-Response (data)

{
"centerId": "uuid",
"centerName": "Rathaus-Galerie Wuppertal",
"generatedAt": "2026-07-08T20:00:00.000Z",
"social": {
"pending": 1,
"inReview": 2,
"totalOpen": 3,
"href": "https://dashboard.cockpit-os.de/dashboard/social/approvals?status=in_review"
},
"workflow": { "openDrafts": 0, "href": "https://dashboard.cockpit-os.de/dashboard/workflow" },
"reviews": {
"configured": true,
"available": true,
"unanswered": 3,
"responseNeeded": 1,
"href": "https://dashboard.cockpit-os.de/dashboard/analytics/reviews?centerId=…"
},
"reporting": {
"configured": true,
"reminderMonth": "2026-06",
"monthNotSent": true,
"quarterReminder": false,
"href": "https://dashboard.cockpit-os.de/dashboard/analytics/client-report?centerId=…"
},
"hints": [
"Social: 2 in Prüfung, 1 ausstehend",
"Google: 3 Rezensionen ohne Antwort (1 mit Handlungsbedarf)",
"Reporting: Monatsbericht Juni 2026 noch nicht versendet"
],
"hasActionItems": true
}

Reporting-Regeln (Cockpit)

FlagBedeutung
monthNotSentVormonat (reminderMonth) hat keinen Eintrag in client_report_notify_log und Center hat clientReportNotifyEmail
quarterReminderErste 14 Tage von Jan/Apr/Jul/Okt und monthNotSent

Smoke-Test (Cockpit-Repo)

COCKPIT_AGENCYOS_API_KEY=sk_agencyos_… CENTER_ID=uuid ./scripts/smoke-agencyos-operational-briefing.sh

Implementierung (Cockpit)

PfadRolle
apps/dashboard/src/lib/integration/load-agencyos-operational-briefing.tsAggregat-Logik
…/centers/[centerId]/operational-briefing/route.tsEinzel-Center
…/operational-briefing/route.tsBulk

AgencyOS implementiert Cron + Teams-Nachricht — siehe Prompt in Repo-Doku / separater Agent-Chat.


KAM-Zuordnung (Cockpit) & Briefing-Index

Was: Key Account Manager werden im Cockpit pro Center gepflegt — mehrere Personen möglich (z. B. Urlaubsvertretung). AgencyOS nutzt die E-Mail-Adresse für Teams-DMs; die operative Datenaggregation bleibt in operational-briefing.

Warum: KAM war bisher nur in AgencyOS (keyAccountManagerId, ein User). Cockpit ist die führende Quelle für Center-Stammdaten und operative To-dos.

Dashboard (Redaktion / Betrieb)

WoPfad
UICenter-Detail → Tab Team & Ansprechpartner → Karte Key Account Manager (KAM)
API (Session)GET/PUT /api/centers/{centerId}/key-account-managers — Body PUT siehe JSON-Beispiele unten

Body PUT (Session-API):

{ "userIds": ["uuid-1", "uuid-2"] }
{ "assignments": [{ "userId": "uuid-1", "assignmentKind": "primary" }] }
{ "revertToOrganization": true }

Wer: SUPER_ADMIN, CENTER_ADMIN, ORG_MARKETING_MANAGER (Schreiben); Lesen für alle mit Center-Zugang.

MCP: Lesen cockpit_center_key_account_managers · Schreiben cockpit_set_center_key_account_managers · Org: cockpit_organization_key_account_managers / cockpit_set_organization_key_account_managers

So testen:

  1. Center öffnen → Profil → Team.
  2. Einen oder mehrere aktive Cockpit-User als KAM hinzufügen.
  3. GET /api/agencyos/v1/centers/{centerId}/key-account-managers mit AgencyOS-Key — gleiche Liste.
  4. PUT mit gleichem Key + userIds oder MCP cockpit_set_center_key_account_managers.
  5. GET /api/agencyos/v1/kam-briefing-index — Empfänger gruppiert nach KAM inkl. Briefings.

AgencyOS-v1-Endpunkte

MethodePfadBeschreibung
GET/api/agencyos/v1/centers/{centerId}/key-account-managersEffektive KAM-Liste (inkl. Vererbung)
PUT/api/agencyos/v1/centers/{centerId}/key-account-managersCenter-KAMs setzen oder revertToOrganization: true
GET/api/agencyos/v1/organizationsOrganisationen listen (Global: alle; Org-Key: eigene)
POST/api/agencyos/v1/organizationsOrganisation anlegen (nur Global-API-Key); Body: name (Pflicht), optional slug, type (SINGLE_MALL | MULTI_MALL | PORTFOLIO), description, website, Kontaktfelder
GET/api/agencyos/v1/organizations/{slugOrId}Organisation lesen (Slug, UUID oder Name)
PATCH/api/agencyos/v1/organizations/{slugOrId}Organisation aktualisieren (Partial Update; Org-Key: Stammdaten; Global-Key zusätzlich isActive, maxCenters, maxUsers)
GET/api/agencyos/v1/organizations/{slugOrId}/key-account-managersOrganisations-KAMs
PUT/api/agencyos/v1/organizations/{slugOrId}/key-account-managersOrganisations-KAMs setzen
GET/api/agencyos/v1/organizations/{slugOrId}/hostingShared Vercel: vercelProjectsByTemplate, Fallback vercelSharedProjectId
PATCH/api/agencyos/v1/organizations/{slugOrId}/hostingMap pflegen (nur Global-API-Key); Body: vercelProjectsByTemplate, optional vercelSharedProjectId
GET/api/agencyos/v1/organizations/{slugOrId}/bulk-center-hosting?websiteTemplate=…Übersicht Hosting aller Center (optional Template-Filter)
POST/api/agencyos/v1/organizations/{slugOrId}/bulk-center-hostingBulk: Center websiteHostingChannel + vercelProjectMode + optional Org-Map merge
PATCH/api/agencyos/v1/centers/{centerId}/hostingEinzelnes Center: Website-Hosting-Kanal + Vercel-Modus
PATCH/api/agencyos/v1/centers/{centerId}/signage-hostingSignage/Companion: Hosting-Kanal, signageVercelProjectMode/Id, signagePublicUrl
GET/api/agencyos/v1/kam-briefing-indexAlle KAM-Empfänger + Briefings (gruppiert nach User)
GET/api/agencyos/v1/kam-briefing-index?onlyWithActionItems=trueNur Empfänger mit mindestens einem Center mit hasActionItems

operational-briefing enthält zusätzlich kams[] pro Center:

"kams": [
{
"userId": "uuid",
"name": "Max Mustermann",
"email": "max@example.com",
"sortOrder": 0,
"assignmentKind": "primary"
},
{
"userId": "uuid2",
"name": "Julia Vertretung",
"email": "julia@agency.de",
"sortOrder": 1,
"assignmentKind": "substitute"
}
]

Smoke-Tests (Cockpit-Repo)

COCKPIT_AGENCYOS_API_KEY=sk_agencyos_… ./scripts/smoke-agencyos-kam-briefing-index.sh
ONLY_WITH_ACTION_ITEMS=true COCKPIT_AGENCYOS_API_KEY=… ./scripts/smoke-agencyos-kam-briefing-index.sh

Implementierung (Cockpit)

PfadRolle
packages/database/migrations/20260708233000_add_center_key_account_managers_SAFE.sqlTabelle
apps/dashboard/src/lib/center-key-account-managers.tsLade-/Speicher-Logik, Briefing-Index
…/api/centers/[centerId]/key-account-managers/route.tsDashboard-API
…/api/agencyos/v1/centers/[centerId]/key-account-managers/route.tsAgencyOS-Read
…/api/agencyos/v1/kam-briefing-index/route.tsCron-Einstieg für Teams-DMs
apps/dashboard/src/components/center-kam-assignments-card.tsxUI

AgencyOS: Cron center-kam-briefing soll kam-briefing-index konsumieren (nicht mehr ShoppingCenter.keyAccountManagerId als Primärquelle).

MCP: cockpit_center_key_account_managers, cockpit_kam_briefing_index, cockpit_operational_briefing (ein Center oder Bulk centerIds).


Content-Quality-Watch (Variante A)

Was: Liefert pro Center Qualitäts-Hinweise für den AgencyOS Teams-Bot — fehlende Center-/Shop-Stammdaten, Inhalte ohne Titelbild, Inhaltslücken (Mengen) und lange Inaktivität ohne Veröffentlichung. Getrennt vom operativen Tages-Briefing.

Warum: Proaktive KAM-Nachrichten wie „8 Shops ohne Logo“ oder „seit 6 Wochen nichts veröffentlicht“ brauchen eine Aggregat-API — nicht 30 Einzelabfragen pro Center.

Endpunkte

MethodePfadBeschreibung
GET/api/agencyos/v1/centers/{centerId}/content-quality-watchEin Center
GET/api/agencyos/v1/content-quality-watch?centerIds=id1,id2Bulk (max. 30)
GET/api/agencyos/v1/content-quality-watch?…&onlyWithIssues=trueBulk nur mit hasQualityIssues
GET/api/agencyos/v1/kam-quality-watch-indexNach KAM gruppiert
GET/api/agencyos/v1/kam-quality-watch-index?onlyWithIssues=trueNur KAMs mit Qualitätsproblemen

Query-Parameter (optional): inactiveDays (21), minOffers (3), minEvents (1), minNews (1), minJobs (0), maxShopSamples (5)

Auth: Authorization: Bearer <sk_agencyos_…>

MCP: cockpit_content_quality_watch, cockpit_kam_quality_watch_index

Beispiel-Response (data)

{
"centerId": "uuid",
"centerName": "Rathaus-Galerie Wuppertal",
"hasQualityIssues": true,
"qualityScore": 62,
"qualityHints": [
"Stammdaten: Center ohne Öffnungszeiten",
"Shops: 8 von 142 ohne Logo (u.a. H&M, Zara, …)",
"Inhalte: 3 aktive Angebote ohne Titelbild",
"Aktivität: seit 42 Tagen nichts veröffentlicht"
],
"issues": {
"center": { "missingLogo": false, "missingCoverImage": false, "missingHeroImage": true, "missingOpeningHours": true },
"shops": { "activeTotal": 142, "withoutLogo": 8, "withoutCoverImage": 12, "withoutOpeningHours": 5, "samples": [] },
"content": { "activeOffersWithoutImage": 3, "activeEventsWithoutImage": 0, "publishedNewsWithoutImage": 1, "gaps": [] },
"activity": { "lastPublishedAt": "2026-05-28T10:00:00.000Z", "daysSinceLastPublish": 42, "inactiveThresholdDays": 21, "isInactive": true }
},
"links": {
"shops": "https://dashboard.cockpit-os.de/dashboard/content/centers/…?tab=shops",
"centerEdit": "https://dashboard.cockpit-os.de/dashboard/content/centers/…?tab=team",
"content": "https://dashboard.cockpit-os.de/dashboard/content/centers/…"
},
"kams": [{ "userId": "…", "name": "…", "email": "…", "assignmentKind": "primary" }]
}

Smoke-Test (Cockpit-Repo)

COCKPIT_AGENCYOS_API_KEY=sk_agencyos_… CENTER_ID=uuid ./scripts/smoke-agencyos-content-quality-watch.sh

Der Smoke-Test prüft Einzel-Center und kam-quality-watch-index?onlyWithIssues=true.

Implementierung (Cockpit)

PfadRolle
apps/dashboard/src/lib/integration/load-agencyos-content-quality-watch.tsAggregat-Logik
…/centers/[centerId]/content-quality-watch/route.tsEinzel-Center
…/content-quality-watch/route.tsBulk
…/kam-quality-watch-index/route.tsKAM-Index für Cron
apps/dashboard/src/lib/center-key-account-managers.tsloadKamQualityWatchIndex

AgencyOS: Cron center-quality-watch + Bot-Tools — separater Agent-Chat im SMG-AgencyOS-Repo.


OpenAPI (Swagger) – optional nutzen

Im Repository liegt eine maschinenlesbare Spezifikation:

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)

ThemaPfad
Magic Link GET/POSTapps/dashboard/src/app/api/agencyos/magic-link/route.ts
Completeapps/dashboard/src/app/api/agencyos/magic-link/complete/route.ts
Organisationen (Session)apps/dashboard/src/app/api/agencyos/organizations/route.ts
Connect-UIapps/dashboard/src/app/agencyos/connect/page.tsx
v1 Centersapps/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 Pushapps/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, Gast-Link)apps/dashboard/src/app/api/agencyos/v1/drafts/route.ts, …/drafts/[draftId]/route.ts, …/drafts/[draftId]/review-link/route.ts, …/drafts/[draftId]/customer-touchpoint-suggestion/route.ts
Content-Gast-Freigabe (öffentlich)apps/dashboard/src/app/api/content/review/[token]/route.ts, apps/dashboard/src/app/freigabe/content/[token]/page.tsx
v1 Audit-Logapps/dashboard/src/app/api/agencyos/v1/audit-logs/route.ts
v1 Aktionsjournal / Undoapps/dashboard/src/app/api/agencyos/v1/actions/route.ts, agency-os-action-journal.ts
v1 Center-Teamapps/dashboard/src/app/api/agencyos/v1/centers/[centerId]/team/route.ts
v1 Website-Config…/centers/[centerId]/website-config/route.ts (GET + PUT)
v1 Outstand-Kanal-Inventar (Bulk)apps/dashboard/src/app/api/agencyos/v1/social/center-accounts/route.ts, outstand-center-inventory.ts
v1 Social-Entwürfe / Engage / Reportingsocial/drafts/route.ts, social/drafts/[draftId]/route.ts, social/drafts/[draftId]/quality-check/route.ts, social/engage/route.ts, social/reporting/route.ts, load-agencyos-social-cockpit.ts
Social Pre-Flight-QA (Dashboard)apps/dashboard/src/lib/integration/social-preflight-qa.ts, GET /api/social/drafts/[draftId]/quality-check
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 Sucheapps/dashboard/src/app/api/agencyos/v1/search/route.ts
Push → Revalidateapps/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
Teams-Hook Social-Freigabe (Phase 2)apps/dashboard/src/lib/integration/agencyos-teams-notify.ts, social-review-request-notify.ts
Operational Briefing (KAM-Daily)load-agencyos-operational-briefing.ts, …/operational-briefing/route.ts
KAM-Zuordnung & Briefing-Indexcenter-key-account-managers.ts, …/key-account-managers/route.ts, …/kam-briefing-index/route.ts

Verwandte Dokumentation

Nutzungsstatistik: Seitenaufrufe werden anonymisiert erfasst. Im Umami-Dashboard nach diesem Pfad filtern: /developer-guide/api-agencyos-integration