Skip to main content

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.png
  • centers/…/…
  • 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 logologoUrl 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):

InputTypical Result
Path starts with /uploads/, uploads/, centers/, global/Append to CDN base (see below)
URL already contains b-cdn.netunchanged (in Prod possibly httphttps)
localhost:3000 / localhost:3001 in URLMap 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).


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 under content corresponds to the Dashboard tab “Template Content” — without formRecipients, passwords, API keys. If nothing has been maintained yet: content: {}.
  • v0Integration: Brief guide Reading (public) vs. Writing (MCP) — identical to cockpit_website_config_schemav0Integration.

Complete key list: PublicVisitorSurfaceV1 in code (see section 1).


3b. templatePublicContent — Hero & Template Tab (v0)

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

FieldMeaning
templateIdwebsiteTemplate of the center (e.g., rgw)
rootKeyKey under themeOverrides.templateContent (e.g., rgw)
contentWhitelist — 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: mondaysunday (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-config and /api/agencyos/v1/…/website-config are for MCP/Dashboard (Bearer/Session) — not for v0 live sites in the browser. Instead: templatePublicContent + page-content + apiHints.
  • POST /api/ai/visitor-chatbot is public with CORS, but does not require an OpenAI key on the client; optional apiKey (WordPress) / centerId in 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:

  1. Media: relative paths /uploads/…, centers/…, global/… → Base https://cockpitos.b-cdn.net (or Env), see url-resolver.ts.
  2. Branding: GET …/public-visitor-surfacedata.center + data.chatbot.primaryColor.
  3. Template (Hero/Footer): data.templatePublicContent.content — all templates; no website-config GET without auth.
  4. Types: PublicVisitorSurfaceV1 in build-public-visitor-surface.ts.
  5. by-slug = flat JSON without { success, data }; Shops = { success, data, total, pagination }.
  6. Opening Hours: openingHours as object/string (mondaysunday, hours vs. open/closed) — always readable for visitors, never raw JSON.
  7. Chatbot: public-visitor-surfacedata.chatbot (Consent, enabled); conversation only POST …/visitor-chatbot, no OpenAI key in client; 429 friendly.
  8. Current Time Window: For News/Events/Jobs current-bundle or individual endpoints …/news?published=true, …/events, …/jobs — Cockpit “Visible from/until” will be server-side filtered; no manual hiding in v0 code.
  9. Errors: if (!res.ok) → Fallback; never render error objects in JSX (React #418).
  10. 3D/Hybrid Center Plan: Part F + for reconstruction problems Part F2 (Chat or Instruction) — extrusion from mapSvg, not mapImage.
  11. Signage / Kiosk + NOW!: Part H + Part I; Cockpit signage remains, v0 optional parallel — Parallel Frontends.
  12. Multi-Center (shared layout): Part Gby-domain, no hardcoded centerId.
  13. No OpenAPI; use live response of a center as “Ground Truth”.

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