Platform guides
02 · Members + roles

Add your team. Scope what they can do.

System roles (owner/admin/member) cover most teams. Custom roles let you grant 'invoice exporter' or 'support reader' without making someone an admin.


1

System roles

Three roles ship with every workspace:

  • owner — every permission in the catalog. Includes destructive verbs like workspace.delete. Cannot be deleted; can't be the only owner (sole-owner invariant — see Section 5).
  • admin — every operational permission. Excludes role-management (create/update/delete roles, assign roles to members) and destructive verbs like workspace.delete. Admins manage the day-to-day; owners shape who-can-do-what.
  • member — read-only across most surfaces + personal API keys.

2

Invite a member

POST /v1/workspaces/:slug/invites
curl https://api.platform-auth.productcraft.co/v1/workspaces/acme/invites \
  -H 'authorization: Bearer <cookie-or-pak>' \
  -H 'content-type: application/json' \
  -d '{
    "email":   "newhire@acme.com",
    "role":    "admin",
    "expires_in_hours": 168
  }'

Sends an invite email via Mail (when enabled on your workspace). Recipient signs up at auth.productcraft.co/signup and hits the invite code to join. After 168 hours the invite expires; revoke earlier via DELETE .../invites/:id.


3

Change a member's role

PATCH /v1/workspaces/:slug/members/:id/role
curl -X PATCH https://api.platform-auth.productcraft.co/v1/workspaces/acme/members/<account-id>/role \
  -H 'authorization: Bearer <cookie-or-pak>' \
  -H 'content-type: application/json' \
  -d '{ "role": "admin" }'

Owner-only — only system owners can change roles. Admins explicitly cannot (the sole-owner invariant requires the role graph itself to be owner-managed).


4

Custom roles

When the three system roles don't fit — e.g. you want a “billing-exporter” role that can read but not write, and can also export the message log:

POST /v1/workspaces/:slug/roles
curl https://api.platform-auth.productcraft.co/v1/workspaces/acme/roles \
  -H 'authorization: Bearer <cookie-or-pak>' \
  -H 'content-type: application/json' \
  -d '{
    "name": "billing-exporter",
    "description": "Read invoices + export message log. No write.",
    "policy": [
      {
        "effect":    "allow",
        "actions":   ["mail.message.read", "mail.message.body.read", "workspace.audit.read"],
        "resources": ["*"]
      }
    ]
  }'

5

The IAM-style policy model

The same shape as AWS IAM. Statements have effect (allow|deny), actions, resources, optional conditions. Explicit deny wins.

Action strings — <service>.<verb>:

workspace.update         auth.create          mail.send
workspace.delete         auth.audit.read      mail.suppression.manage
workspace.invite.create  waitlist.update             social.read
workspace.role.create    waitlist.export             social.flag
workspace.audit.read     ...                      ...

Resource URNs — pcft:<scope>[/<id>]:

*                                # workspace-wide
pcft:workspace/<workspaceId>     # the workspace itself
pcft:heimdall:app/<appId>        # per-Auth-app narrowing
pcft:agora:community/<commId>    # per-Social-community narrowing

Wildcards work in any segment — auth.* matches every Auth verb; pcft:heimdall:app/* matches every app in the workspace. Trailing * swallows the rest.

Caller-narrowing. When creating or updating a role, the caller's own policy must include every action they're trying to grant. An admin (whose policy excludes workspace.delete) can't hand the role they're editing a workspace.delete permission. Defence against self-escalation.


6

Sole-owner invariant

A workspace must always have at least one owner. The API refuses transitions that would leave the workspace ownerless:

  • Demoting an owner to admin/member when they're the sole owner → 409 WOULD_REMOVE_LAST_OWNER.
  • Removing an owner from membership when they're the sole owner → 409.
  • The sole owner can't leave the workspace via self-leave; they must promote someone else first OR delete the workspace entirely.

Edge case: deleting your account (not the workspace) when you're a sole owner. AccountService.delete auto-promotes the oldest admin (or oldest non-self member if no admins exist) to owner. Documented in the platform-auth-api CLAUDE.md — and S-10 in the API security review notes the unfortunate silent-promotion behaviour that we'll fix with a notification email.


7

Remove a member

DELETE /v1/workspaces/:slug/members/:id
curl -X DELETE https://api.platform-auth.productcraft.co/v1/workspaces/acme/members/<account-id> \
  -H 'authorization: Bearer <cookie-or-pak>'

Requires workspace.member.remove on the caller's role. Two refusal cases:

  • Sole owner — 409 (see Section 6).
  • Last member — 409 WOULD_LEAVE_WORKSPACE_EMPTY. Decommission the whole workspace instead via DELETE /v1/workspaces/:slug.

Self-leave is allowed without workspace.member.remove — members can always leave their own workspaces, subject to the same sole-owner + last-member invariants.