Register an app

Apps are onboarded through a declarative manifest (app.json / manifest.json) — plugin-style registration on the control plane or the tenant IdP.

KindWhere it livesWho sees it
Global cataloguePublished once on the control plane (fw-saas-admin)Available to platforms/tenants (subject to allowlists)
Tenant-privateRegistered only on that tenant's IdPVisible only to that tenant
AccessManifest + RBACGrant app:{id}:access and fine-grained permissions

This page is for tenant admins and app developers. For the OIDC code+PKCE wiring itself, see Connect an app.


1. Two places to register

Global catalogue (fw-saas-admin)

Tenant-private (fw-saas-tenant IdP)


2. App kinds — one flag

"deployable": true | false
deployableMeaningTypical example
trueHosted Worker / bundle path (catalogue deploy on WfP)CRM, ia-chat
falseExternal contract only: OAuth client + permissions, no platform deployJarvis, fees on a custom host

External apps must declare oauth.redirect_uris. Hosted apps may omit OAuth URIs only when the callback lives on the app's own url host (host trust is scoped per app — there is no same-domain bypass; see Connect an app).


3. Manifest (app.json / manifest.json)

MVP fields:

FieldRequiredNotes
idyesSlug [a-z][a-z0-9-]{0,62} — also the OAuth client_id
nameyesDisplay name
descriptionnoShort blurb
iconnoAbsolute URL
versionnoSemver (1.2.3) — used for catalogue update detection; allowed on private too
deployableyes*Default false if omitted
homepage / support / support_urlnoLinks for operators
oauth.redirect_urisrequired if not deployableExact callback URLs
permissions[]no{ id, label, description? } — app-scoped ids e.g. jarvis.chat.read

\* Omitting deployable means false (external).

Implicit permission

On register, the IdP always merges:

app:{id}:access

Meaning: may enter this app. Fine permissions are separate. Having only access and zero fine perms is valid — the app decides its UI (user B1 vs B2).

Example — external (Jarvis)

{
  "id": "jarvis",
  "name": "Jarvis",
  "description": "AI assistant (OIDC relying party, not hosted by Fixweb)",
  "version": "1.4.0",
  "deployable": false,
  "homepage": "https://app.fixweb.cloud",
  "oauth": {
    "redirect_uris": [
      "https://app.fixweb.cloud/callback"
    ]
  },
  "permissions": [
    { "id": "jarvis.chat.read", "label": "Read chat history" },
    { "id": "jarvis.chat.write", "label": "Send messages" }
  ]
}

Example — hosted catalogue app

{
  "id": "crm",
  "name": "CRM",
  "version": "2.0.0",
  "deployable": true,
  "homepage": "https://fixweb.cloud",
  "permissions": [
    { "id": "crm.contacts.read", "label": "Read contacts" },
    { "id": "crm.contacts.write", "label": "Edit contacts" }
  ]
}

4. Evolving a manifest (no reinstall)

You do not destroy or reinstall a deployed app to change its IdP manifest (permissions, OAuth callbacks, homepage, …). Re-register in place.

Catalogue / hosted apps

  1. Update the manifest on the control plane

(POST /api/admin/applications/register or Applications → Register from manifest). Include the full permissions[] you want as the new catalog.

  1. Tenants pick up the change without redeploy: the tenant console

re-syncs the catalogue IdP manifest onto the local IdP when Apps / Catalog loads, and again after activate / update.

  1. Re-grant roles/users/groups if you added new permission ids (existing

grants stay; removed ids are dropped from the catalog and their grants).

Tenant-private apps

Re-POST /api/admin/applications/register (or Apps → Register app) on that tenant IdP with the updated manifest. Same merge rules.

Partial re-register

If the body omits permissions or oauth, those fields are left unchanged on the IdP (hostname / metadata sync cannot wipe the RBAC catalog). An explicit "permissions": [] does replace the fine-grained catalog (app:{id}:access is always kept).

Deploy bindings (app.manifest.json)

Worker resources (KV, secrets, features, vars, …) live on the release, not on the IdP row. To evolve them: publish a new version with the updated deploy manifest, then activate / update — do not destroy the deployment. Versions are immutable (409 version_exists); see the publish contract.


5. Where to click (tenant admin)

  1. Open the tenant admin console (https://admin-admin.fixweb.cloud).
  2. Sidebar → Apps.
  3. Register app → paste or upload the manifest → Register.
  4. Select the app → Grant to role / user / group → pick permissions (include app:{id}:access to enter the app).

Legacy gated grants (Roles → applications) still work alongside permission grants.


6. RBAC — IdP is source of truth

Users, roles, groups, and permissions live on the tenant IdP.

Registering an app merges its permissions (plus app:{id}:access) into the tenant permission catalog.

Admins grant via any combination of:

PathAPI / UI
Role default grantsApps → grant to role, or PUT /api/admin/roles/:id/permissions
User direct grantsApps → grant to user, or PUT /api/admin/users/:id/permissions
Group grantsApps → grant to IdP group (seen at SSO), or PUT /api/admin/group-permissions

Groups are IdP groups (e.g. Microsoft / Google “Marketing”) — not inventing a second group system.

JWT claims (relying parties)

Access tokens expose under properties:

ClaimMeaning
rolesRole ids
permissionsEffective union (roles + user + group grants)
appsApplication ids the user may enter

Verify with JWKS — always:

{issuer}/.well-known/jwks.json

Never pin a kid or PEM. Details: Connect an app · Integration guide.


7. API (tenant IdP admin)

Every route below is mounted under /api/admin on the tenant IdP origin and requires an admin credential — one of:

CredentialHeaderWho uses it
Admin user access tokenAuthorization: Bearer <token>Tenant admins (the console uses this)
Bootstrap tokenx-admin-token: <ADMIN_BOOTSTRAP_TOKEN>First deploy / headless seeding
Operator grantx-impersonation-grant: <grant>Platform operator, minted on the control plane
POST /api/admin/applications/register
Authorization: Bearer <admin access token>
Content-Type: application/json

{ …manifest… }

Optional extras on the same body: url, access_level, visible, source (private default | catalogue).

GET  /api/admin/applications
GET  /api/admin/applications/:id
GET  /api/admin/applications/:id/grants
GET  /api/admin/permissions?application_id=:id
PUT  /api/admin/roles/:id/permissions
PUT  /api/admin/users/:id/permissions
PUT  /api/admin/group-permissions

Control plane catalogue:

POST https://admin.fixweb.cloud/api/admin/applications/register

8. Checklist