Zum Hauptinhalt springen

Public API — Beispiel-Responses, Medien-URLs, Typen (für v0 & externe Frontends)

Ergänzung zum API-Vertrag. Ziel: konkrete Feldnamen, Branding-Ort, Medien-Auflösung und wo es TypeScript gibt — ohne dass du jeden Endpoint einzeln ausprobieren musst.


1. OpenAPI / Swagger

Aktuell nicht vorhanden. Der Vertrag ist Markdown; die präziseste „Schema“-Quelle für public-visitor-surface ist der TypeScript-Typ PublicVisitorSurfaceV1 in:

apps/dashboard/src/lib/build-public-visitor-surface.ts

Dort sind alle Keys des Payloads und die Verschachtelung (center, seo, chatbot, centerplan, …) explizit definiert.


2. Medien-URLs (logo vs. logoUrl, relative Pfade)

Was die API oft liefert

Viele GET-Routen geben rohe Strings aus der Datenbank zurück, z. B.:

  • /uploads/shop-logo.png
  • centers/…/…
  • global/…
  • oder bereits absolute URLs (https://…)

Es gibt keine einheitliche Umbenennung zu imageUrl über alle Endpunkte: z. B. by-slug setzt absichtlich logo und logoUrl auf denselben Wert (Priorität logologoUrl im Center). Shop-Objekte nutzen typischerweise logo, coverImage (kein paralleles imageUrl-Feld in der Shops-API).

Wie die Center-Website auflöst (Referenz für dein Frontend)

Die Logik steht in apps/center-website/lib/url-resolver.ts (resolveMediaUrl):

EingabeTypisches Ergebnis
Pfad beginnt mit /uploads/, uploads/, centers/, global/Anhängen an CDN-Basis (siehe unten)
URL enthält bereits b-cdn.netunverändert (in Prod ggf. httphttps)
localhost:3000 / localhost:3001 in URLPfadanteil auf CDN-Basis abbilden (Dev)
Sonst http(s)://…durchreichen, in Prod ggf. HTTPS

CDN-Basis (Default): https://cockpitos.b-cdn.net
Überschreibbar per Umgebungsvariable NEXT_PUBLIC_BUNNY_CDN_URL oder BUNNY_CDN_URL (ohne trailing slash in der Logik egal — Code normalisiert).

Beispiel:

/uploads/x.png  →  https://cockpitos.b-cdn.net/uploads/x.png

Externe Apps (v0/Vercel), die direkt die Dashboard-API aufrufen, sollten dieselbe Regel implementieren oder Medien-URLs serverseitig einmal durch diese Funktion jagen (Portierung der ~60 Zeilen aus url-resolver.ts).


Route: GET /api/centers/{centerId}/public-visitor-surface

Envelope:

{
"success": true,
"data": { }
}

Die Felder data.center (Center-Stammdaten, Farben, Logos) und data.chatbot (Chat-UI, inkl. primaryColor) sind die zentralen Stellen. Kurzbeispiel (gekürzt, echte Werte variieren):

{
"success": true,
"data": {
"schemaVersion": 1,
"center": {
"id": "uuid",
"name": "Beispiel Center",
"slug": "beispiel-center",
"shortName": null,
"city": "Berlin",
"address": "Musterstraße 1",
"postalCode": "10115",
"country": "DE",
"baseColor": "#3b82f6",
"secondaryColor": "#64748b",
"logo": "/uploads/…",
"logoUrl": "/uploads/…",
"favicon": null,
"websiteFavicon": null,
"websiteTemplate": "cev-ai-theme",
"comingSoonEnabled": false,
"customDomain": null
},
"seo": {
"title": null,
"description": null,
"keywords": null,
"ogImage": null,
"ogTitle": null,
"ogDescription": null
},
"chatbot": {
"enabled": true,
"primaryColor": "#0066CC",
"greetingMessage": "…",
"position": "bottom-right",
"consentTitle": "…",
"consentDescription": "…"
},
"centerplan": { "initialViewMode": "2d", "showCenterplanLegend": true, "showLogosOnPlan": true },
"wayfindingMap": null,
"features": { "shops": true, "news": true },
"templatePublicContent": {
"templateId": "rgw",
"rootKey": "rgw",
"content": {
"hero": {
"slide1": {
"image": "/uploads/…",
"headline": "Willkommen",
"subline": "…",
"cta": "Mehr erfahren",
"ctaHref": "/shops"
}
},
"pageVisibility": { "shops": true, "kontakt": true }
}
},
"v0Integration": {
"goldenRule": "…",
"publicRead": { "templateContent": "…" },
"forbiddenInPublicFrontend": ["GET …/website-config ohne Auth"]
},
"apiHints": {
"visitorChatbotPost": "/api/ai/visitor-chatbot",
"pageContentGet": "/api/centers/{centerId}/page-content",
"homepageTilesGet": "/api/centers/{centerId}/homepage-tiles",
"wayfindingCenterplanGet": "/api/wayfinding/centerplan?centerId=…",
"doohPublicPlaylistsListGet": "/api/centers/{centerId}/dooh/public/playlists",
"doohPublicPlaylistBySlugGet": "/api/centers/{centerId}/dooh/public/playlist?slug=",
"doohPublicActiveGet": "/api/centers/{centerId}/dooh/public/active",
"doohPublicLocalHeroGet": "/api/centers/{centerId}/dooh/public/local-hero",
"…": "weitere Hinweise (Wayfinding, Aktuelles, …)"
}
}
}

Hinweis: apiHints sind relative Pfade; Basis ist der Dashboard-Host (z. B. https://dashboard.cockpit-os.de), siehe getDashboardApiUrl().

  • templatePublicContent: Besucher-sichere Template-Config für alle Website-Templates (rgw, ilg, mec-shopbox, …). Struktur unter content entspricht dem Dashboard-Reiter „Template-Inhalte“ — ohne formRecipients, Passwörter, API-Keys. Wenn noch nichts gepflegt: content: {}.
  • v0Integration: Kurzguide Lesen (öffentlich) vs. Schreiben (MCP) — identisch zu cockpit_agencyos_website_config_schemav0Integration.

Vollständige Key-Liste: PublicVisitorSurfaceV1 im Code (siehe Abschnitt 1).


3b. templatePublicContent — Hero & Template-Reiter (v0)

Quelle: GET …/public-visitor-surfacedata.templatePublicContent

FeldBedeutung
templateIdwebsiteTemplate des Centers (z. B. rgw)
rootKeySchlüssel unter themeOverrides.templateContent (z. B. rgw)
contentWhitelist — alles, was Besucher sehen dürfen

RGW Hero (Beispiel):

const surface = await fetch(`${API}/api/centers/${centerId}/public-visitor-surface`).then(r => r.json())
const tc = surface?.data?.templatePublicContent?.content
const slides = [tc?.hero?.slide1, tc?.hero?.slide2, tc?.hero?.slide3].filter(Boolean)

Social Lounge: content.heroSlider.slides (Array). Seiten-Reiter (Anfahrt, Kontakt): weiterhin GET …/page-content, Eintrag nach pageType filtern.

Nicht tun: GET …/website-config oder AgencyOS-Config ohne Bearer — 401; v0 darf Error-Objekte nicht in JSX rendern.


4. Weitere Beispiel-Shapes (kurz)

GET /api/centers/by-slug/{slug}

Liefert direkt ein Objekt (ohne success/data-Wrapper):

{
"id": "uuid",
"name": "Beispiel Center",
"slug": "beispiel-center",
"city": "…",
"address": "…",
"baseColor": "#3b82f6",
"secondaryColor": "#64748b",
"logo": "https://… oder /uploads/…",
"logoUrl": "gleicher Wert wie logo",
"coverImageUrl": null,
"websiteFavicon": null,
"theme": "light",
"organization": null,
"urls": {
"dashboard": "https://…",
"manager": "https://…",
"signage": "https://…",
"companion": "https://…",
"main": "https://…"
}
}

GET /api/centers/{centerId}/shops?publicWebsite=true&status=Aktiv&limit=100&offset=0

{
"success": true,
"data": [
{
"id": "uuid",
"name": "Shopname",
"slug": "shop-slug",
"category": "Mode",
"description": "…",
"shortDescription": "…",
"floor": "EG",
"logo": "/uploads/…",
"coverImage": "/uploads/…",
"openingHours": null,
"isShopLocation": false,
"tags": [],
"…": "weitere Prisma-Felder je nach Objekt"
}
],
"total": 42,
"pagination": { "limit": 100, "offset": 0, "hasMore": false, "nextOffset": null },
"meta": { "…": "…" }
}

Filialen haben u. a. isShopLocation: true und chain. Für öffentliche Felder und Filter nutzt das Backend @mall-os/database (websiteShopPublicListingWhere / websiteShopLocationPublicListingWhere).

openingHours bei Shops (häufiges JSON-Objekt)

Nicht immer null — oft ein Objekt oder ein String, der JSON enthält. Keys: mondaysunday (englisch). Pro Tag z. B. hours als Textspanne oder open/close mit Wert "closed".

{
"monday": { "hours": "10:00 - 20:00" },
"tuesday": { "hours": "10:00 - 20:00" },
"wednesday": { "hours": "10:00 - 20:00" },
"thursday": { "hours": "10:00 - 20:00" },
"friday": { "hours": "10:00 - 20:00" },
"saturday": { "hours": "10:00 - 20:00" },
"sunday": { "open": "closed", "close": "closed" }
}

Frontends (inkl. v0): defensiv parsen, für Besucher lesbar formatieren — siehe Copy-Paste-Block im API-Vertrag.


5. Authentifizierung — geschützte Routen (website-config etc.)

Der öffentliche Read-Vertrag beinhaltet keine Session und keine API-Keys für website-config.

  • website-config und /api/agencyos/v1/…/website-config sind für MCP/Dashboard (Bearer/Session) — nicht für v0 Live-Sites im Browser. Stattdessen: templatePublicContent + page-content + apiHints.
  • POST /api/ai/visitor-chatbot ist öffentlich mit CORS, braucht aber keinen OpenAI-Key im Client; optional apiKey (WordPress) / centerId im Body — siehe Route-Doku im Repo.

Wenn ihr später bewusst geschützte APIs für eigene Apps braucht, ist das ein separates Produkt-Thema (Token, OAuth, BFF), nicht Teil dieses Public-Read-Vertrags.


6. Empfehlung für v0 Custom Instructions

Limit: v0 erlaubt max. 10 Custom Instructions à max. 5000 Zeichen → Slot-Plan in Content-Creator-Handbuch „v0 + Cockpit (So geht’s)“. Teil D („Cockpit Workflow“) zuerst. Kern: D, A, B, C, E (+ J nach Vercel; F/G/H/I je nach Projekt; F2 nur Chat).
Der lange Einzelblock im API-Vertrag ist für Chat/Cursor ohne dieses Limit.

Kurz zum Inhalt:

  1. Medien: relative Pfade /uploads/…, centers/…, global/… → Basis https://cockpitos.b-cdn.net (oder Env), siehe url-resolver.ts.
  2. Branding: GET …/public-visitor-surfacedata.center + data.chatbot.primaryColor.
  3. Template (Hero/Footer): data.templatePublicContent.content — alle Templates; kein website-config GET ohne Auth.
  4. Typen: PublicVisitorSurfaceV1 in build-public-visitor-surface.ts.
  5. by-slug = flaches JSON ohne { success, data }; Shops = { success, data, total, pagination }.
  6. Öffnungszeiten: openingHours als Objekt/String (mondaysunday, hours vs. open/closed) — immer lesbar für Besucher darstellen, nie Roh-JSON.
  7. Chatbot: public-visitor-surfacedata.chatbot (Consent, enabled); Konversation nur POST …/visitor-chatbot, kein OpenAI-Key im Client; 429 freundlich.
  8. Aktuelles-Zeitfenster: Für News/Events/Jobs aktuelles-bundle oder die Einzel-Endpunkte …/news?published=true, …/events, …/jobs — Cockpit „Sichtbar ab/bis“ wird serverseitig gefiltert; kein manuelles Ausblenden im v0-Code.
  9. Fehler: if (!res.ok) → Fallback; nie Error-Objekt in JSX (React #418).
  10. Centerplan 3D/Hybrid: Teil F + bei Nachbau-Problemen Teil F2 (Chat oder Instruction) — Extrusion aus mapSvg, nicht mapImage.
  11. Signage / Kiosk + NOW!: Teil H + Teil I; Cockpit-Signage bleibt, v0 optional parallel — Parallele Frontends.
  12. Multi-Center (shared layout): Teil Gby-domain, kein hardcodiertes centerId.
  13. Kein OpenAPI; Live-Response eines Centers als „Ground Truth“ nutzen.

Verwandte Doku

Nutzungsstatistik: Seitenaufrufe werden anonymisiert erfasst. Im Umami-Dashboard nach diesem Pfad filtern: /developer-guide/public-center-website-api-beispiele-und-medien