The NestJS service exposes two surfaces:
- Mobile API —
/api/v1/*— read-only, schema-versioned, called by the React Native app. - Admin API —
/api/admin/*— read/write, auth-gated, called by the Next.js authoring tool.
All paths in this document are relative to http://localhost:3000.
Compose and return the published screen the mobile client should render.
Required headers
| Header | Example | Notes |
|---|---|---|
x-ui-schema-version |
1 |
Negotiated by ScreenVersionGuard. Below the supported floor → 426. |
x-user-id |
u_42 |
Used to load user context, run feature-flag bucketing. |
x-client-os |
ios |
Used by targeting rules. |
x-client-os-version |
18.0 |
Used by targeting rules. |
x-client-app-version |
1.0.0 |
Used by targeting rules. |
Response (200)
A ScreenDto JSON document:
{
"id": "home",
"schemaVersion": 1,
"capabilities": ["text", "image", "button", "stack", "spacer", "productCard", "promoBanner", "categoryRail", "sectionHeader"],
"components": [
{ "id": "h-greeting", "type": "sectionHeader", "text": "Welcome back, Joud", "fallbackText": "Welcome back" },
/* ... */
]
}Every component in components[] carries a fallback field — a smaller BaseComponentDto the client renders if it can't render the parent. See fallback.builder.ts.
Response (426)
Sent when x-ui-schema-version is below the minimum supported version:
{ "error": "SCHEMA_TOO_OLD", "minSupported": 1 }The mobile app catches this and routes the user to a "please update" screen.
A text/event-stream the mobile app subscribes to. Emits one event per published screen update:
event: screen-updated
data: {"screen":"home","rev":12}
Mobile clients use this as a hint, not an order — see Mobile renderer guide for how stale-while-revalidate consumes it.
All admin endpoints require:
x-admin-secret: <ADMIN_SECRET from apps/api/.env (default: dev-only-secret)>
The default secret is development-only — replace before production. See Production readiness.
List every screen the admin can edit.
[
{ "id": "home", "rev": 12, "draftRev": 13, "status": "draft", "layout": [...] },
{ "id": "product-detail-extras", "rev": 4, "status": "published", "layout": [...] }
]Create a new screen template. Body must validate against screenTemplateSchema from @sdui/contracts.
Read one screen, including draft state.
Replace the current draft. Body validated against screenTemplateSchema. Bumps draftRev. Does not affect the published revision the mobile app receives.
Save a partial change to the current draft.
Promote the current draft to published. Bumps rev. Emits an SSE screen-updated event for the mobile client.
Resolve the current draft against a synthetic context and return the resulting ScreenDto. Used by the admin UI's "Preview JSON" modal. No persistence.
Remove a screen template entirely. Mobile clients receive a 404 on subsequent fetches.
List the available data sources and dynamic value tokens the admin UI is allowed to bind. Driven by composition.ts on the contracts package.
{
"dataSources": [
{ "repo": "products", "segments": ["picked", "trending", "new"] },
{ "repo": "promos", "segments": ["home-hero", "detail-extras"] },
{ "repo": "categories" }
],
"dynamicValues": [
"{{user.id}}", "{{user.firstName}}", "{{user.segment}}", "{{user.bucket}}",
"{{client.os}}", "{{client.osVersion}}", "{{client.appVersion}}",
"{{screen.id}}"
]
}The admin UI uses this catalog to populate dropdowns and prevent editors from referencing tokens the resolver can't fill.
| Concept | Where it lives | Bumped when |
|---|---|---|
| Schema version | MIN_SUPPORTED_SCHEMA / CURRENT_SCHEMA in @sdui/contracts |
The envelope shape changes (additive only). |
| Component version | Encoded in type strings (e.g. productCard.v2) |
A specific component's shape changes. |
| Screen revision | rev on ScreenTemplate |
A new published revision lands. |
| Draft revision | draftRev on ScreenTemplate |
An admin save lands without publishing. |
See §8 of the article for the additive-only rule and the negotiation flow.
| Code | Where it comes from | Meaning |
|---|---|---|
| 400 | ValidationPipe / Zod |
Body or DTO failed validation. |
| 401 | BasicAuthGuard |
Missing / wrong x-admin-secret. |
| 404 | Repositories | Unknown screen id, unknown user id, etc. |
| 426 | ScreenVersionGuard |
x-ui-schema-version below MIN_SUPPORTED_SCHEMA. |
| 500 | Anywhere else | Bug — capture and fix; the resolver must not throw in prod. |