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.pngcenters/…/…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 logo → logoUrl 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):
| Eingabe | Typisches Ergebnis |
|---|---|
Pfad beginnt mit /uploads/, uploads/, centers/, global/ | Anhängen an CDN-Basis (siehe unten) |
URL enthält bereits b-cdn.net | unverändert (in Prod ggf. http → https) |
localhost:3000 / localhost:3001 in URL | Pfadanteil 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).
3. public-visitor-surface — Branding & Logo
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 untercontententspricht dem Dashboard-Reiter „Template-Inhalte“ — ohneformRecipients, Passwörter, API-Keys. Wenn noch nichts gepflegt:content: {}.v0Integration: Kurzguide Lesen (öffentlich) vs. Schreiben (MCP) — identisch zucockpit_agencyos_website_config_schema→v0Integration.
Vollständige Key-Liste: PublicVisitorSurfaceV1 im Code (siehe Abschnitt 1).
3b. templatePublicContent — Hero & Template-Reiter (v0)
Quelle: GET …/public-visitor-surface → data.templatePublicContent
| Feld | Bedeutung |
|---|---|
templateId | websiteTemplate des Centers (z. B. rgw) |
rootKey | Schlüssel unter themeOverrides.templateContent (z. B. rgw) |
content | Whitelist — 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: monday … sunday (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-configund/api/agencyos/v1/…/website-configsind für MCP/Dashboard (Bearer/Session) — nicht für v0 Live-Sites im Browser. Stattdessen:templatePublicContent+page-content+apiHints.POST /api/ai/visitor-chatbotist öffentlich mit CORS, braucht aber keinen OpenAI-Key im Client; optionalapiKey(WordPress) /centerIdim 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:
- Medien: relative Pfade
/uploads/…,centers/…,global/…→ Basishttps://cockpitos.b-cdn.net(oder Env), sieheurl-resolver.ts. - Branding:
GET …/public-visitor-surface→data.center+data.chatbot.primaryColor. - Template (Hero/Footer):
data.templatePublicContent.content— alle Templates; keinwebsite-configGET ohne Auth. - Typen:
PublicVisitorSurfaceV1inbuild-public-visitor-surface.ts. by-slug= flaches JSON ohne{ success, data }; Shops ={ success, data, total, pagination }.- Öffnungszeiten:
openingHoursals Objekt/String (monday–sunday,hoursvs.open/closed) — immer lesbar für Besucher darstellen, nie Roh-JSON. - Chatbot:
public-visitor-surface→data.chatbot(Consent, enabled); Konversation nurPOST …/visitor-chatbot, kein OpenAI-Key im Client; 429 freundlich. - Aktuelles-Zeitfenster: Für News/Events/Jobs
aktuelles-bundleoder die Einzel-Endpunkte…/news?published=true,…/events,…/jobs— Cockpit „Sichtbar ab/bis“ wird serverseitig gefiltert; kein manuelles Ausblenden im v0-Code. - Fehler:
if (!res.ok)→ Fallback; nie Error-Objekt in JSX (React #418). - Centerplan 3D/Hybrid: Teil F + bei Nachbau-Problemen Teil F2 (Chat oder Instruction) — Extrusion aus
mapSvg, nichtmapImage. - Signage / Kiosk + NOW!: Teil H + Teil I; Cockpit-Signage bleibt, v0 optional parallel — Parallele Frontends.
- Multi-Center (shared layout): Teil G —
by-domain, kein hardcodiertescenterId. - 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