Skip to main content

portal.yaml reference

Everything about a tenant's portals is declared in one file in the config repository:

tenants/meridian/portal/
├── portal.yaml # this reference — all of the tenant's portals
├── portal.staging.yaml # optional per-environment overlay (deep-merged)
├── guest/
│ └── src/ # tenant-owned UI source for the `guest` portal
└── agent/
└── src/ # tenant-owned UI source for the `agent` portal

Config presence is the feature gate. A tenant with no portal.yaml has no portals; a portal id not declared in the file returns 404 PORTAL_NOT_FOUND on every route. No tenant or portal is ever special-cased in platform code. Changes go live on merge — the platform re-reads the file from GitLab with a short (60-second) cache, so there is no deployment step.

When the platform runs with an environment name set, a portal.{environment}.yaml overlay next to the base file is deep-merged over it, so per-environment differences (for example pointing live.sf_environment at a sandbox) stay in one small file.

Top-level blocks

KeyRequiredDescription
tenant_idNoWhen present it must match the tenant directory the file lives in; a mismatch invalidates the whole config
providerNoTenant-level default data provider: mock or live (default mock). Individual portals can override it
portals[]YesOne entry per portal — at least one. See below
uiNoAdvisory shell metadata (title, favicon_url, meta string map) surfaced to the tenant UI via the public config endpoint

portals[] entries

One tenant runs any number of portals — a guest portal, an agent workspace, an operations console — as entries in the same file, each with its own UI source directory and its own settings.

KeyRequiredDescription
idYesURL-safe identifier (lowercase letters, digits, hyphens; max 64 chars). Forms the portal's path: /{tenant_id}/{id}
typeYesThe portal's audience, e.g. guest, agent, operations. Identity namespaces are partitioned by type: magic-link sign-in resolves travelers only for guest-type portals, and a lookup for one type never resolves an identity belonging to another
labelYesDisplay name, surfaced via the public config endpoint
providerNoPer-portal provider override: the shorthand string form (provider: mock) or the object form (provider: { type: live }) — both are equivalent
liveNoLive-provider settings. Currently one key: sf_environment — the Salesforce environment name, validated upstream against the tenant blueprint's salesforce_environments
authNoDeclarative auth metadata (e.g. mode: session_token). Accepted and stored, treated as sensitive — it is never exposed through the public config endpoint. The enforced authentication model itself is platform-wide; see Authentication
featuresNoFeature flag map — see Feature flags
brandingNoAdvisory branding metadata — see Branding

Feature flags

features is a map of flag names to true / false (booleans, or the strings "true" / "false"). A feature is on unless explicitly set to false — portals that omit the block keep every capability enabled.

Five flags gate API routes. A disabled flag short-circuits its route: list endpoints return an empty collection and the single-form endpoints return 404, without the data provider ever being called.

FlagWhat it gates
itinerary_daysDay-by-day itinerary (GET .../itinerary-days)
payment_schedulePayment schedule and payment transactions (GET .../payment-schedules, GET .../payment-transactions)
documentsTrip documents (GET .../documents)
pre_departure_formsPre-departure forms — the list, the single-form read, and the submit endpoint
optional_extrasOptional extras (GET .../optional-extras)

Trip listing and trip detail are not feature-gated — a portal always exposes the traveler's trips. You may declare additional flag names (for example flags your own tenant UI reads from the public config endpoint); the platform passes them through verbatim.

Branding

The branding block is advisory, tenant-facing metadata. The platform never interprets these values — there is no platform theming engine. They are surfaced verbatim through the public config endpoint (GET /api/portal/v1/{tenantId}/{portalId}/config) so your tenant-owned UI can read them and style itself. Styling is entirely the tenant UI's responsibility.

KeyDescription
brand_nameDisplay brand name
logo_urlAbsolute URL to the brand logo
primary_colorBrand color, e.g. a hex value
font_familyPreferred font family

All fields are optional, so tenants adopt them incrementally.

Worked example: Meridian Travel Co.

Meridian runs a guest portal and an agent workspace from one file. The guest portal is live against Meridian's Salesforce UAT environment; the agent portal still runs on the mock provider while it is being built out.

tenant_id: meridian
provider: mock # tenant-level default; portals override below

ui:
title: Meridian Travel — My Trip
favicon_url: https://cdn.meridiantravel.example/favicon.ico

portals:
- id: guest
type: guest
label: Meridian Guest Portal
provider: live
live:
sf_environment: uat
auth:
mode: session_token
features:
itinerary_days: true
payment_schedule: true
documents: true
pre_departure_forms: true
optional_extras: false # not sold through the portal yet
branding:
brand_name: Meridian Travel Co.
primary_color: "#0B4F6E"
logo_url: https://cdn.meridiantravel.example/logo.svg

- id: agent
type: agent
label: Meridian Agent Workspace
provider: mock # still evaluating on seeded data
auth:
mode: session_token
branding:
brand_name: Meridian Travel Co.

With this file merged, https://<portal-host>/meridian/guest and https://<portal-host>/meridian/agent serve the two portals, each bundled from its own src/ directory — see Tenant UI source.

Validation

The file is validated against the platform schema when loaded. An invalid file (schema violation, or a tenant_id that does not match the directory) is treated as absent — the tenant's portals return 404 and the platform logs a portal_config_invalid warning — so a bad merge can never serve a half-parsed config.