BFF Pattern
A Backend-for-Frontend can help a custom page compose data and actions without leaking credentials or overloading the event model.
Use a BFF for
- combining Cessy projections with third-party read data
- server-side calls that require secrets
- UI-specific aggregation that should not become a domain projection
- file transforms, exports, or imports that need controlled server logic
- feature flags or workspace-specific preview helpers
- user-delegated chat flows that need to combine Cessy channels with frontend session state
Do not use a BFF for
- replacing commands with arbitrary mutations
- bypassing permissions
- storing business facts outside the event history
- hiding long-running policy behavior from Activity
- making design-time API calls from runtime pages
Recommended shape
Keep the BFF thin:
- authenticate the user
- authorize against app intent
- call Cessy Runtime APIs with a user-delegated token
- call approved external services
- return UI-shaped data
If the BFF starts owning decisions, move that behavior into commands, events, and policies.
Partner chat flow
For an external partner frontend, keep chat orchestration in the BFF and keep channel state in Cessy:
- list safe app agent metadata with
GET /api/rest/<tenant-id>/<app-id>/agents - create a fresh conversation with
POST /api/rest/<tenant-id>/<app-id>/channels/dm - list visible threads with
GET /api/rest/<tenant-id>/<app-id>/user-channel-views - load messages with
GET /api/rest/<tenant-id>/<app-id>/channels/<channel-id>/messages - post user messages with
POST /api/rest/<tenant-id>/<app-id>/channels/<channel-id>/messages - advance read state with
POST /api/rest/<tenant-id>/<app-id>/channels/<channel-id>/read
The agent catalog returns only id, name, label, and kind. Use a published
agent’s selected id as agentId in the DM request. A Workspace catalog can
also contain preview-only agents, but DM creation resolves its target from the
published app configuration; promote those agents before opening a DM. Each
successful DM request creates a new conversation and returns its channelId
and sessionId; repeating the request does not resolve an older conversation.
Continue an existing thread by posting to its known channel id. The BFF may
shape these responses for the UI, but it should not invent channel ids, mutate
business state directly, or store authoritative conversation history outside
Cessy.
When the user sends a natural chat message from a concrete product context,
include agentTurnContext in the DM/open or message request instead of
prefixing hidden instructions into content. This keeps the channel message
public and human-readable while giving the agent private, turn-scoped
orientation:
Use the context for ids, projection pointers, truth status, compact snapshots,
orientation flags, and small namespaced extensions for builder-specific
hints. Do not put secrets, long transcripts, or large documents there. The
context does not authorize actions; Cessy Runtime permissions and explicit
user-action requirements still apply.
Custom Page Chat and legacy workspace sends that still include pageContext
without agentTurnContext are normalized into the same private, turn-scoped
context internally. Do not rely on page context appearing in public messages or
hidden routing text.
For POST /channels/dm, include agentTurnContext only when the same request
also posts content; createOnly DM opens do not have an associated turn.
Cessy Auth for BFFs
Use delegated user auth. The BFF should obtain a Cessy OAuth access token for the logged-in human user, then call Runtime REST with that token:
Do not use a tenant API key for user-facing BFF actions. API keys are for
server-to-server automation and authenticate as apiKey principals.
User-delegated surfaces, including partner chat and channel-read endpoints,
require a user principal and reject API keys and agent service tokens.
The read-only GET /agents catalog can use normal Runtime REST authentication,
but that does not make the same API key valid for a subsequent channel call.
OAuth flow
- Register an OAuth client for the partner BFF with
/api/oauth/register. Use the BFF callback URL as the redirect URI and requestopenid runtime. - Start an authorization-code + PKCE flow at
/api/oauth/authorizewithscope=openid runtimeand the targettenantIdandappId. If the client supports a resource parameter, use the app Runtime MCP resource as the OAuth resource:https://<cessy-origin>/api/mcp/<tenant-id>/<app-id>. - Cessy shows its consent screen. The user must be signed in through Cessy Auth and must have access to the selected tenant; Cessy also validates that the selected app belongs to that tenant.
- Exchange the authorization code at
/api/oauth/tokenwith the PKCE verifier. - Store the refresh token server-side in the BFF. Access tokens expire after 1 hour and can be refreshed with the refresh-token grant.
Token responses look like this:
The access token is a Cessy-issued JWT. Runtime REST uses these claims:
Runtime REST then resolves the user’s principal context and applies the app’s command, projection, page, and channel authorization rules. The BFF should treat Cessy as the authority for tenant/app membership and app permissions; local BFF checks should only narrow access for UI intent, never broaden it.
Calling Runtime REST
For app data, use the generated app contract:
Then call the generated command and projection endpoints described by that OpenAPI document. For partner chat or channel UIs, the app-scoped Runtime REST channel endpoints use the same delegated user token:
Channel endpoints additionally require that the channel contains an agent from
{appId} and that the delegated user can read or act on the channel.
Partner setup checklist
When onboarding a partner BFF, configure the integration with tenant-specific values supplied through that tenant’s Cessy setup:
- one OAuth client per partner BFF callback
- Cessy users granted access to the tenant
- authorization requests scoped to the tenant and app
- BFF calls to
/api/rest/{tenantId}/{appId}/...with the delegated user token
Do not place real tenant ids, app ids, client secrets, refresh tokens, or user ids in docs, prompts, screenshots, page bundles, or shared configuration. Keep those values in environment-specific secret storage.
Common auth failures
Verification
Test the Cessy runtime path and the BFF path separately. A custom page is not verified until the command/projection contract works without the page and the page works against the published contract.
For BFF auth, verify at least one delegated user flow end to end:
- complete OAuth login and consent for the target tenant/app
- fetch
/api/rest/<tenant-id>/<app-id>/openapiwith the issued token - call one generated read endpoint or channel summary endpoint through the BFF
- confirm an API key fails on user-delegated channel endpoints