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
| Key | Required | Description |
|---|---|---|
tenant_id | No | When present it must match the tenant directory the file lives in; a mismatch invalidates the whole config |
provider | No | Tenant-level default data provider: mock or live (default mock). Individual portals can override it |
portals[] | Yes | One entry per portal — at least one. See below |
ui | No | Advisory 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.
| Key | Required | Description |
|---|---|---|
id | Yes | URL-safe identifier (lowercase letters, digits, hyphens; max 64 chars). Forms the portal's path: /{tenant_id}/{id} |
type | Yes | The 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 |
label | Yes | Display name, surfaced via the public config endpoint |
provider | No | Per-portal provider override: the shorthand string form (provider: mock) or the object form (provider: { type: live }) — both are equivalent |
live | No | Live-provider settings. Currently one key: sf_environment — the Salesforce environment name, validated upstream against the tenant blueprint's salesforce_environments |
auth | No | Declarative 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 |
features | No | Feature flag map — see Feature flags |
branding | No | Advisory 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.
| Flag | What it gates |
|---|---|
itinerary_days | Day-by-day itinerary (GET .../itinerary-days) |
payment_schedule | Payment schedule and payment transactions (GET .../payment-schedules, GET .../payment-transactions) |
documents | Trip documents (GET .../documents) |
pre_departure_forms | Pre-departure forms — the list, the single-form read, and the submit endpoint |
optional_extras | Optional 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.
| Key | Description |
|---|---|
brand_name | Display brand name |
logo_url | Absolute URL to the brand logo |
primary_color | Brand color, e.g. a hex value |
font_family | Preferred 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.