Workflows

End-to-end sequences across the resources in the API Reference. If you’re adding a feature, check here first — it’s probably a step inside one of these, not a new flow.

  1. Purchase → access
  2. Admin turns an app off for a user
  3. Role change
  4. New user, first app
  5. A customer needs dedicated or regional infrastructure
  6. An org admin narrows who an org-wide grant reaches
  7. Settings resolution, in practice

Purchase → access

The most common path: someone buys an app and it shows up, switched on, in their hub.

sequenceDiagram
    participant Billing
    participant Hub as Hub API
    participant User
    participant App as Downstream App

    Billing->>Hub: POST /v1/webhooks/incoming/billing — order.paid (user, application)
    Hub->>Hub: upsert Entitlement(status=active, source=purchase, order_id=...)
    Hub->>Hub: emit entitlement.granted
    User->>Hub: GET /v1/users/me/entitlements
    Hub-->>User: app now listed, status=active
    User->>App: launches app (SSO redirect)
    App->>Hub: verify token or GET effective-permissions
    Hub-->>App: entitlement=active, effective_permissions=[...]
  1. Billing (the system of record for payment, see Decisions) completes an order and notifies the hub, either by calling the hub’s incoming webhook endpoint or the hub polling billing — whichever direction is chosen, the result is the same write.
  2. The hub upserts an Entitlement with status: active, source: purchase, linked to the order_id.
  3. The hub emits entitlement.granted on its own webhook system, for any downstream app that wants to react immediately (e.g. pre-provision a workspace) rather than wait for the user to show up.
  4. The user’s hub dashboard now lists the app as available the next time it calls GET /v1/users/{id}/entitlements.
  5. The user launches the app. The app verifies the user’s standing against the hub — either by decoding the JWT issued at redirect, or calling the live effective-permissions endpoint — per the Trust Model.

Admin turns an app off for a user

The “kill switch” flow — a support agent or admin disabling one user’s access to one app, without touching anything else about their account.

  1. PATCH /v1/entitlements/{id} with {"status": "disabled", "disabled_reason": "..."}.
  2. The hub writes an Audit Event capturing the actor, before/after state, and reason.
  3. The hub emits entitlement.revoked.
  4. Any app subscribed to that webhook can invalidate an in-progress session immediately. An app relying only on JWT expiry will honor the change at the next token refresh — see Trust Model for the tradeoff.
  5. Nothing about the user’s Role assignments, Profile, or Settings for that app changes — re-enabling the entitlement restores exactly what was there before.

Role change

  1. An admin calls POST /v1/users/{id}/roles/{roleId} (assign) or DELETE (remove) — platform-scoped or app-scoped.
  2. effective_permissions for that user (and, if app-scoped, that app) changes immediately on the hub side.
  3. The hub emits role.assigned / role.removed.
  4. Downstream, this is picked up the next time the app checks — introspection call or next token refresh — not necessarily mid-session, unless the app subscribes to the webhook. This is a materially lower-urgency propagation requirement than an entitlement revocation: a user getting slightly stale permissions for a few minutes is a much smaller problem than a de-provisioned user keeping access.

New user, first app

  1. POST /v1/users creates the account; a default Profile and Settings record is created alongside it; the default platform Role (member) is assigned.
  2. The user acquires an app — either a purchase (see above) or an admin/invite grant (source: admin_grant on the Entitlement, no order_id).
  3. The first time the user opens that app, an AppProfile and AppSettings record is created lazily, seeded from the Application’s declared defaults — there’s no separate “provision this user in this app” step to orchestrate; reading or writing either resource for a user who doesn’t have one yet creates it with defaults.

A customer needs dedicated or regional infrastructure

Support-run today — see Decisions → Tenancy tiers for why this isn’t self-service yet.

  1. A customer (an Application owner) asks support for dedicated infrastructure, or a specific region for data residency.
  2. Support calls POST /v1/tenants/{id}/tier-change-requests — see API Reference → Tenancy.
  3. Support schedules a brief maintenance window, flips the Tenant’s status to migrating, and runs the snapshot/restore cutover described in Deployment Architecture → Tenancy tiers — every Entitlement, AppProfile, and AppSettings row carrying that tenant_id moves to the new infrastructure.
  4. tier/region/status are updated back to active; an Audit Event (tenant.tier_changed) records it.
  5. Nothing about the customer’s Applications, Entitlements, or any end-user’s access changes shape — this workflow only ever moves where the same rows live, never what they say.

An org admin narrows who an org-wide grant reaches

  1. An org admin (OrganizationMembership.role: org_admin) calls PATCH /v1/entitlements/{id} on their Organization’s org_seat Entitlement to an Application, setting member_scope: allowlist or denylist and member_overrides.
  2. The hub writes an Audit Event (entitlement.member_scope_changed) — not entitlement.disabled/entitlement.granted, since the grant’s own status didn’t change.
  3. Every affected member’s resolved_entitlement_status for that Application is recomputed at read time, not backfilled — there’s no per-member row to update. A member excluded this way keeps any separate, personally-sourced Entitlement to the same app untouched — see Access Control → Organization vs. User precedence.
  4. Downstream, this is picked up the same way a Role change is — next introspection call or token refresh, not necessarily mid-session unless the app subscribes to the matching webhook.

Settings resolution, in practice

When an app needs to know a setting’s value for a user (e.g. notification channel preference), the read order is always:

AppSettings override (this user, this app)
  → Settings override (this user, global)
    → Application's declared default for that key

The hub resolves this server-side — GET /v1/users/{id}/apps/{appId}/settings returns the fully-resolved object, not just the override layer, so callers never have to re-implement the fallthrough themselves.


Back to top

Substratal Apps Platform API — living specification. This site is the system of record; see git history for how it has changed over time.

This site uses Just the Docs, a documentation theme for Jekyll.