Public Center-Website API — Vertrag (Read)
Dieses Dokument ist der verbindliche Referenz-Vertrag für öffentliche Lesezugriffe auf Center-Daten über das CockpitOS-Dashboard (Next.js API Routes). Es dient:
- als Spezifikation für Backend (P0/P1),
- als Kontext für v0 / andere Frontends,
- als Grundlage für späteres Dogfooding (
server-data-loadernur noch per HTTP).
Siehe auch: Lückenanalyse & Prioritäten (CORS-Status, fehlende Routen).
Basis
| Feld | Wert |
|---|---|
| Produktions-Basis-URL | https://dashboard.cockpit-os.de |
| Konfiguration im Code | getDashboardApiUrl() in packages/dashboard-api — Env: DASHBOARD_API_URL, NEXT_PUBLIC_DASHBOARD_URL oder NEXT_PUBLIC_API_URL |
| Auth (dieser Vertrag) | Keine Session, keine API-Keys — nur öffentlich zulässige Lesedaten. Schreibzugriffe und website-config GET sind nicht Teil dieses Vertrags. |
| Content-Type | application/json |
| Fehler | Typisch { success: false, error: string } oder { error: string } mit passendem HTTP-Status (400, 404, 500). Exakte Felder können je Route leicht variieren. |
Status je Endpunkt (Stand laut Gap-Analyse)
- Bereit + CORS * — Browser von anderer Origin (z. B. Vercel) kann direkt
fetchen. - Bereit, CORS offen (P0) — gleiche URL nutzbar von Server (SSR); für Client-seitiges
fetchmuss CORS nachgezogen werden. - Geplant (P1) — noch nicht oder nur teilweise am Dashboard; Vertrag beschreibt Ziel.
Ablauf: centerId ermitteln
Externe Apps kennen oft den Slug oder die Custom Domain, nicht die UUID.
1) Nach Slug
GET /api/centers/by-slug/{slug}
| CORS | Ja (*) |
| Antwort | Objekt mit u. a. id, name, slug, city, address, baseColor, logo, urls |
Beispiel (gekürzt):
{
"id": "34eca9c3-1ea7-4c5a-b83a-d2b6bfb0c9f2",
"name": "Beispiel Center",
"slug": "beispiel-center",
"city": "Berlin",
"address": "Musterstraße 1",
"baseColor": "#3b82f6",
"secondaryColor": "#64748b",
"logo": "https://…",
"logoUrl": "https://…",
"coverImageUrl": null,
"websiteFavicon": null,
"theme": "light",
"organization": null,
"urls": {
"dashboard": "https://dashboard.cockpit-os.de/centers/34eca9c3-…",
"manager": "https://beispiel-center.manager.cockpit-os.de",
"signage": "https://beispiel-center.signage.cockpit-os.de",
"companion": "https://beispiel-center.signage.cockpit-os.de/companion",
"main": "https://preview.cockpit-os.de/beispiel-center"
}
}
2) Nach Custom Domain
GET /api/centers/by-domain?domain={host}
| Parameter | domain — Hostname ohne Pfad, z. B. www.palais-vest.de oder palais-vest.de |
| CORS | Ja (*) |
Beispiel:
{
"success": true,
"center": {
"id": "…",
"name": "…",
"slug": "…",
"customDomain": "palais-vest.de",
"websiteEnabled": true,
"domainVerified": true,
"baseColor": "#…",
"secondaryColor": "#…",
"logoUrl": "https://…",
"organizationId": "…",
"comingSoonEnabled": false
}
}
Theme & Website-Konfiguration (öffentlich)
Theme-Konfiguration (SPA / Template)
GET /api/centers/by-slug/{slug}/theme-config
| CORS | Ja (*) |
| Antwort | Objekt aus SpaThemeManager.getSpaThemeConfig(centerId) — Struktur center- und template-abhängig (Theme-Plugin, templateContent, Farben, Feature-Flags). Für stabile Felder: Live-Response eines Centers loggen und im Frontend defensiv parsen. |
Hinweis: Vollständiges GET /api/centers/{centerId}/website-config ist auth-pflichtig und kein Teil dieses Vertrags. Entscheidung offen: später schlanker public-Endpoint oder nur dieses Bündel (by-slug + theme-config + nachfolgende Daten-Endpoints).
Shops
GET /api/centers/{centerId}/shops
| Query | Default | Beschreibung |
|---|---|---|
limit | 100 | Seitengröße |
offset | 0 | Offset |
| CORS | Ja (*) |
Beispiel (Struktur):
{
"success": true,
"data": [
{
"id": "…",
"name": "Shop oder Filiale",
"category": "Fashion",
"isShopLocation": false,
"tags": [],
"logo": "https://…",
"slug": "…"
},
{
"id": "…",
"name": "Filiale XY",
"category": "Food",
"isShopLocation": true,
"chain": { "id": "…", "name": "Kette", "logo": "https://…" },
"floor": "EG"
}
],
"total": 42,
"pagination": {
"limit": 100,
"offset": 0,
"hasMore": false,
"nextOffset": null
},
"meta": {
"total": 42,
"limit": 100,
"offset": 0,
"returned": 42,
"centerId": "…",
"centerName": "…",
"breakdown": {
"standaloneShops": 10,
"shopLocations": 32,
"shopLocationsFiltered": 0
}
}
}
Shop-Kategorien
GET /api/categories
| Query | Beschreibung |
|---|---|
centerId | UUID des Centers (empfohlen) |
status | z. B. Aktiv |
includeGlobal | true / false |
minShopCount | Mindestanzahl Shops pro Kategorie |
forServices | true für Service-Kategorien |
| CORS | P0 — aktuell ohne * (SSR ok, Browser-Cross-Origin erst nach Nachziehen) |
Antwort: JSON-Array von Kategorie-Objekten (inkl. u. a. id, name, icon, color, shopCount — exakte Felder siehe Prisma-Modell / Live-Response).
[
{
"id": "…",
"name": "Fashion",
"description": "…",
"icon": "shirt",
"color": "#e11d48",
"sortOrder": 1,
"shopCount": 12
}
]
News, Events, Angebote, Jobs
Alle unter GET /api/centers/{centerId}/… mit CORS *.
News
GET /api/centers/{centerId}/news
| Query | Beschreibung |
|---|---|
limit | Default 20 |
offset | Default 0 |
published | true filtert veröffentlichte + sichtbare Daten |
category | String-Filter auf category |
{
"success": true,
"data": [ { "id": "…", "title": "…", "slug": "…", "excerpt": "…", "image": "…", "publishDate": "2026-03-01T10:00:00.000Z", "status": "…", "center": { "id": "…", "name": "…", "slug": "…" } } ],
"meta": { "total": 0, "limit": 20, "offset": 0, "returned": 0, "centerId": "…" }
}
P1 — Baustellen-Tagebuch: Filter isConstructionDiary fehlt auf dieser Route; geplant: Query constructionDiary=true oder GET /api/centers/{centerId}/construction-diary.
Events
GET /api/centers/{centerId}/events — Query u. a. limit, offset (siehe Route).
Angebote (Liste)
GET /api/centers/{centerId}/offers — Query u. a. limit, offset.
P1 — Einzel-Angebot: GET /api/centers/{centerId}/offers/{offerId} oder ?id= — noch zu spezifizieren/implementieren.
Jobs
GET /api/centers/{centerId}/jobs — Query u. a. limit, offset.
Services
GET /api/centers/{centerId}/services
| CORS | Ja (*) |
Antwortstruktur siehe Live-Call; typisch Liste/Success-Wrapper analog zu anderen Center-Routen.
Büros & Praxen
GET /api/offices
| Query | Beschreibung |
|---|---|
centerId | empfohlen |
type | Typ-Filter oder all |
status | z. B. Aktiv oder all |
| CORS | Ja (*) |
{
"success": true,
"data": [
{
"id": "…",
"name": "…",
"type": "Praxis",
"floor": "1. OG",
"logo": "https://…",
"center": { "id": "…", "name": "…", "slug": "…" },
"officeType": { "id": "…", "name": "…" }
}
],
"count": 1
}
Page Content (CMS-Seiten, z. B. Öffnungszeiten)
GET /api/centers/{centerId}/page-content
| CORS | P0 — ergänzen für Browser-Cross-Origin |
{
"pageContents": [
{
"id": "…",
"centerId": "…",
"pageType": "oeffnungszeiten",
"pageTitle": "Öffnungszeiten",
"pageSubtitle": null,
"pageDescription": "…",
"metaTitle": "…",
"metaDescription": "…",
"metaKeywords": null,
"customContent": {},
"createdAt": "2026-01-01T00:00:00.000Z",
"updatedAt": "2026-01-01T00:00:00.000Z"
}
]
}
pageType-Werte sind center-abhängig (u. a. shops, gastronomy, oeffnungszeiten, kontakt, …).
Homepage-Kacheln
GET /api/centers/{centerId}/homepage-tiles
| CORS | Ja (*) |
{
"tiles": [
{
"id": "…",
"centerId": "…",
"title": "…",
"subtitle": null,
"href": "/shops",
"icon": "shopping-bag",
"order": 0,
"mobileSize": "1x",
"desktopSize": "1x1",
"backgroundType": "gradient",
"backgroundConfig": null,
"contentType": null,
"contentConfig": null,
"enabled": true,
"gridColumnSpan": 1,
"gridRowSpan": 1
}
]
}
Themes: Gastronomie, Kategorien, Büros
| Route | CORS (laut Analyse) |
|---|---|
GET /api/centers/{centerId}/gastronomy-themes | P0 |
GET /api/centers/{centerId}/category-themes | P0 |
GET /api/centers/{centerId}/office-themes | P0 |
Antwort: jeweils Array/Objekt mit Theme- und Shop-/Office-Zuordnungen — Live-Response als Referenz nutzen (umfangreiche Includes).
Wayfinding / Centerplan
| Route | Methode | CORS (laut Analyse) |
|---|---|---|
GET /api/wayfinding/centerplan?centerId={uuid} | GET | P0 |
GET /api/wayfinding/floors?centerId={uuid} | GET | P0 |
POST /api/wayfinding/routing | POST | P0 (Body: Start/End-Waypoints oder Location-IDs, siehe Route) |
GET /api/centers/{centerId}/entrances | GET | P0 |
Centerplan GET liefert u. a. id, centerId, name, floors (geordnet) — verschachtelte Struktur siehe Prisma-Select in der Route.
Optional geplant (P1)
| Endpoint | Zweck |
|---|---|
GET /api/centers/{centerId}/aktuelles (oder ähnlich) | News + Events + Offers + Jobs in einem Response (weniger Roundtrips) |
GET …/news?constructionDiary=true oder …/construction-diary | Nur Baustellen-Tagebuch-Einträge |
GET …/offers/{offerId} | Einzelnes Angebot inkl. Shop |
Copy-Paste: Kontext für v0 / KI
Baue eine öffentliche Center-Website (Next.js, Server Components wo möglich).
API-Basis: https://dashboard.cockpit-os.de
Ablauf:
1) Center auflösen: GET /api/centers/by-slug/{slug} → id ist centerId (UUID).
Alternativ Custom Domain: GET /api/centers/by-domain?domain={hostname}
2) Theme/UI-Daten: GET /api/centers/by-slug/{slug}/theme-config
3) Inhalte (centerId einsetzen):
- Shops: GET /api/centers/{centerId}/shops?limit=100&offset=0
- Kategorien: GET /api/categories?centerId={centerId}&status=Aktiv&includeGlobal=true
- News: GET /api/centers/{centerId}/news?published=true&limit=20
- Events: GET /api/centers/{centerId}/events
- Angebote: GET /api/centers/{centerId}/offers
- Jobs: GET /api/centers/{centerId}/jobs
- Services: GET /api/centers/{centerId}/services
- Büros: GET /api/offices?centerId={centerId}&status=Aktiv
- CMS-Seiten (Öffnungszeiten etc.): GET /api/centers/{centerId}/page-content
- Homepage-Kacheln: GET /api/centers/{centerId}/homepage-tiles
- Centerplan: GET /api/wayfinding/centerplan?centerId={centerId}
- Etagen/Karte: GET /api/wayfinding/floors?centerId={centerId}
Hinweise:
- Responses nutzen teils { success, data }, teils direkte Arrays oder { tiles }, { pageContents } — jeweils prüfen.
- Einige Routen brauchen noch CORS für reines Browser-fetch von einer anderen Domain; bevorzuge fetch im Server (RSC/Route Handler).
- Keine Secrets im Client; keine Schreib-Endpunkte ohne separates Auth-Konzept.
Pflege dieses Dokuments
- Nach CORS-P0: Status-Spalten oben und betroffene Abschnitte auf „Bereit + CORS *“ anheben.
- Nach P1-Implementierung: neue URLs und Beispiele ergänzen.
- Bei Schema-Änderungen in Prisma: Beispiel-JSON anpassen oder auf „Live-Response“ verweisen.
Code-Referenz: apps/dashboard/src/app/api/…
Nutzungsstatistik: Seitenaufrufe werden anonymisiert erfasst. Im Umami-Dashboard nach diesem Pfad filtern: /en/developer-guide/public-center-website-api-vertrag