Every state the master-admin console must render is contract. Loading, the fresh-install empty case, a load failure, the suspend confirmation and its root-portal refusal, the create-portal slug conflict, and the one most often got wrong: a non-platform-admin who reaches this route must be denied in place — never silently shown the fleet, never bounced to a login they'll sail back through.
Loading…
1 portal
Only the seeded root portal exists. Create the first tenant portal to onboard a customer — they'll get their own users, catalog, and billing.
Suspending immediately blocks every sign-in to this portal — its users, its apps, its billing. Existing sessions are ended. Data is retained; you can resume it later. Resellers billing their own customers keep their Connect account, but checkout is paused.
That slug is already in use by another portal. Slugs are unique and permanent — pick another.
Contract. Loading covers the in-flight GET /api/v1/admin/portals. (d2) is a
real state — a fresh install has only the seeded root portal. (d3) is a non-2xx list with
data-action="retry". (d4) confirms PATCH .../{id}
{ status: "suspended" }. (d5): the root portal is refused
409 ROOT_PORTAL_PROTECTED — client pre-disable + server guard. (d6):
POST /api/v1/admin/portals returning 409 SLUG_TAKEN renders inline on
the field, never a toast that loses the form. (d7) is the rule: a 403 is an
authorization denial (platform-admin only, Permit ReBAC) shown in place — only a
401 re-authenticates.