portablemind
All guides
API / Developer4 min read

Onboard your own customers (multi-tenant)

Run a product where each customer sees only their own runs — built from the generic User/role/Team APIs and per-team run ownership.

apimulti-tenantscoping

Run a multi-tenant product on PortableMind where each of your customers signs in and sees only their own runs and deliverables — no data bleed between them.

Built on the same APIs as the accelerator portal; see its INTEGRATION_GUIDE §10–11 for the full chain.

The idea

There's no special "signup" endpoint — onboarding a customer is just the generic platform APIs (User, role, Team) chained behind a provisioning credential (an API key granted the specific capabilities). Each customer becomes a user, gets a scoped role, and is attached to their own team; runs are owned by that team.

The flow

  1. Keep the provisioning key server-side. Never ship it to the browser — put a tiny proxy in front (the reference app's server.js does exactly this and injects browser config at runtime).
  2. Provision a participant with the provisioning credential: create the user, assign a scoped participant role, and create/attach their team.
  3. Authenticate end-users with a JWT (not the shared API key) so each request runs as that user. Send it as Authorization: Bearer <jwt> with the Tenant-Id slug.
  4. Own runs by team. Launch executions with owning_team_id so they belong to the customer's team. A participant (scoped JWT) then sees only published + public templates and only their team's runs; staff see everything.
  5. Make the customer the approver. Set their party as a stage's approver so only they can approve their own gate — 403 "Only the designated approver may approve this gate." protects everyone else's.

Result

A clean multi-tenant product: customers self-serve within strict boundaries, you administer the catalog and policy, and isolation is enforced by the platform — not by hopeful filtering in your UI.

The provisioning chain is generic CRUD — the reference app's provisionParticipant() / ensureTeam() show it end to end, including a signed-in founder adding a teammate to their own team.