Public API — Example Responses, Media URLs, Types (for v0 & external Frontends)
Supplement to the API contract. Goal: specific field names, branding locations, media resolutions, and where TypeScript is available — without having to try each endpoint individually.
1. OpenAPI / Swagger
Currently not available. The contract is in Markdown; the most precise “schema” source for public-visitor-surface is the TypeScript type PublicVisitorSurfaceV1 in:
apps/dashboard/src/lib/build-public-visitor-surface.ts
All the keys of the payload and the nesting (center, seo, chatbot, centerplan, …) are explicitly defined there.
2. Media URLs (logo vs. logoUrl, relative paths)
What the API often delivers
Many GET routes return raw strings from the database, e.g.:
/uploads/shop-logo.pngcenters/…/…global/…- or already absolute URLs (
https://…)
There is no uniform renaming to imageUrl across all endpoints: for example, by-slug intentionally sets logo and logoUrl to the same value (priority logo → logoUrl in the Center). Shop objects typically use logo, coverImage (no parallel imageUrl field in the Shops API).
How the Center website resolves (reference for your frontend)
The logic is in apps/center-website/lib/url-resolver.ts (resolveMediaUrl):
| Input | Typical Result |
|---|---|
Path starts with /uploads/, uploads/, centers/, global/ | Append to CDN base (see below) |
URL already contains b-cdn.net | unchanged (in Prod possibly http → https) |
localhost:3000 / localhost:3001 in URL | Map path part to CDN base (Dev) |
Otherwise http(s)://… | pass through, possibly HTTPS in Prod |
CDN Base (Default): https://cockpitos.b-cdn.net
Overridable via environment variable NEXT_PUBLIC_BUNNY_CDN_URL or BUNNY_CDN_URL (without trailing slash logically irrelevant — code normalizes).
Example:
/uploads/x.png → https://cockpitos.b-cdn.net/uploads/x.png
External apps (v0/Vercel) that directly call the Dashboard API should implement the same rule or server-side process media URLs once through this function (porting the ~60 lines from url-resolver.ts).
3. public-visitor-surface — Branding & Logo
Route: GET /api/centers/{centerId}/public-visitor-surface
Envelope:
{
"success": true,
"data": { }
}
The fields data.center (Center core data, colors, logos) and data.chatbot (Chat UI, including primaryColor) are the central points. Brief example (shortened, actual values may vary):
{
"success": true,
"data": {
"schemaVersion": 1,
"center": {
"id": "uuid",
"name": "Example Center",
"slug": "example-center",
"shortName": null,
"city": "Berlin",
"address": "Sample Street 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": "Welcome",
"subline": "…",
"cta": "Learn more",
"ctaHref": "/shops"
}
},
"pageVisibility": { "shops": true, "contact": true }
}
},
"v0Integration": {
"goldenRule": "…",
"publicRead": { "templateContent": "…" },
"forbiddenInPublicFrontend": ["GET …/website-config without 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",
"…": "more hints (Wayfinding, Current, …)"
}
}
}
Note: apiHints are relative paths; the base is the Dashboard host (e.g., https://dashboard.cockpit-os.de), see getDashboardApiUrl().
templatePublicContent: Visitor-safe template config for all website templates (rgw,ilg,mec-shopbox, …). Structure undercontentcorresponds to the Dashboard tab “Template Content” — withoutformRecipients, passwords, API keys. If nothing has been maintained yet:content: {}.v0Integration: Brief guide Reading (public) vs. Writing (MCP) — identical tocockpit_website_config_schema→v0Integration.
Complete key list: PublicVisitorSurfaceV1 in code (see section 1).
3b. templatePublicContent — Hero & Template Tab (v0)
Source: GET …/public-visitor-surface → data.templatePublicContent
| Field | Meaning |
|---|---|
templateId | websiteTemplate of the center (e.g., rgw) |
rootKey | Key under themeOverrides.templateContent (e.g., rgw) |
content | Whitelist — everything visitors are allowed to see |
RGW Hero (Example):
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). Page Tab (Directions, Contact): still GET …/page-content, filter entry by pageType.
Do not do: GET …/website-config or AgencyOS config without Bearer — 401; v0 should not render error objects in JSX.
4. More Example Shapes (brief)
GET /api/centers/by-slug/{slug}
Returns directly an object (without success/data wrapper):
{
"id": "uuid",
"name": "Example Center",
"slug": "example-center",
"city": "…",
"address": "…",
"baseColor": "#3b82f6",
"secondaryColor": "#64748b",
"logo": "https://… or /uploads/…",
"logoUrl": "same value as logo",
"coverImageUrl": null,
"websiteFavicon": null,
"theme": "light",
"websiteTemplate": "mec-template-a",
"organization": null,
"urls": {
"dashboard": "https://…",
"manager": "https://…",
"signage": "https://…",
"companion": "https://…",
"main": "https://…"
}
}
GET /api/centers/{centerId}/shops?publicWebsite=true&status=Active&limit=100&offset=0
{
"success": true,
"data": [
{
"id": "uuid",
"name": "Shop Name",
"slug": "shop-slug",
"category": "Fashion",
"description": "…",
"shortDescription": "…",
"floor": "Ground Floor",
"logo": "/uploads/…",
"coverImage": "/uploads/…",
"openingHours": null,
"isShopLocation": false,
"tags": [],
"…": "other Prisma fields depending on object"
}
],
"total": 42,
"pagination": { "limit": 100, "offset": 0, "hasMore": false, "nextOffset": null },
"meta": { "…": "…" }
}
Branches have, among other things, isShopLocation: true and chain. For public fields and filters, the backend uses @mall-os/database (websiteShopPublicListingWhere / websiteShopLocationPublicListingWhere).
openingHours at shops (common JSON object)
Not always null — often an object or a string containing JSON. Keys: monday … sunday (in English). For each day, e.g. hours as a text span or open/close with the value "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 (including v0): defensively parse, format for visitors readable — see copy-paste block in the API contract.
5. Authentication — protected routes (website-config etc.)
The public read contract includes no sessions and no API keys for website-config.
website-configand/api/agencyos/v1/…/website-configare for MCP/Dashboard (Bearer/Session) — not for v0 live sites in the browser. Instead:templatePublicContent+page-content+apiHints.POST /api/ai/visitor-chatbotis public with CORS, but does not require an OpenAI key on the client; optionalapiKey(WordPress) /centerIdin the body — see route documentation in the repo.
If you later need protected APIs for your own apps, that is a separate product topic (Token, OAuth, BFF), not part of this Public-Read contract.
6. Recommendation for v0 Custom Instructions
Limit: v0 allows max. 10 Custom Instructions of max. 5000 characters → Slot plan in the Content Creator Handbook "v0 + Cockpit (How to)". Part D (“Cockpit Workflow”) first. Core: D, A, B, C, E (+ J after Vercel; F/G/H/I depending on the project; F2 only Chat).
The long single block in the API contract is for Chat/Cursor without this limit.
Briefly about the content:
- Media: relative paths
/uploads/…,centers/…,global/…→ Basehttps://cockpitos.b-cdn.net(or Env), seeurl-resolver.ts. - Branding:
GET …/public-visitor-surface→data.center+data.chatbot.primaryColor. - Template (Hero/Footer):
data.templatePublicContent.content— all templates; nowebsite-configGET without auth. - Types:
PublicVisitorSurfaceV1inbuild-public-visitor-surface.ts. by-slug= flat JSON without{ success, data }; Shops ={ success, data, total, pagination }.- Opening Hours:
openingHoursas object/string (monday–sunday,hoursvs.open/closed) — always readable for visitors, never raw JSON. - Chatbot:
public-visitor-surface→data.chatbot(Consent, enabled); conversation onlyPOST …/visitor-chatbot, no OpenAI key in client; 429 friendly. - Current Time Window: For News/Events/Jobs
current-bundleor individual endpoints…/news?published=true,…/events,…/jobs— Cockpit “Visible from/until” will be server-side filtered; no manual hiding in v0 code. - Errors:
if (!res.ok)→ Fallback; never render error objects in JSX (React #418). - 3D/Hybrid Center Plan: Part F + for reconstruction problems Part F2 (Chat or Instruction) — extrusion from
mapSvg, notmapImage. - Signage / Kiosk + NOW!: Part H + Part I; Cockpit signage remains, v0 optional parallel — Parallel Frontends.
- Multi-Center (shared layout): Part G —
by-domain, no hardcodedcenterId. - No OpenAPI; use live response of a center as “Ground Truth”.
Related Documentation
Nutzungsstatistik: Seitenaufrufe werden anonymisiert erfasst. Im Umami-Dashboard nach diesem Pfad filtern: /en/developer-guide/public-center-website-api-beispiele-und-medien