hcengineering/platform · error

Workspace owners only

Error message

Workspace owners only

What it means

withOwnerAsync rejects with 401 'Workspace owners only' when the caller's loginInfo.role is present but is not AccountRole.Owner. Authentication and workspace scoping succeeded, but the endpoint requires workspace Owner privileges.

Source

Thrown at services/payment/pod-payment/src/middleware.ts:95

    return
  }
  if (req.params.workspace != null && req.token.workspace !== req.params.workspace) {
    res.status(401).json({ message: 'Workspace mismatch' }).end()
    return
  }
  if (req.token.account !== systemAccountUuid && req.token.extra?.admin !== 'true') {
    const accountClient = getAccountClient(req.headers.authorization?.split(' ')[1])
    const loginInfo = req.loginInfo ?? (await accountClient.getLoginInfoByToken())
    if (loginInfo == null) {
      res.status(403).json({ message: 'Missing auth info' }).end()
      return
    }
    if (!('role' in loginInfo)) {
      res.status(401).json({ message: 'Missing workspace role' }).end()
      return
    }
    if (loginInfo.role !== AccountRole.Owner) {
      res.status(401).json({ message: 'Workspace owners only' }).end()
      return
    }
  }

  next()
}

View on GitHub (pinned to 63e28dc964)

Solutions

  1. Ask the actual workspace Owner to perform the action, or have the Owner grant ownership (transfer ownership) in the account service
  2. Check your effective role via the account service and confirm which role the endpoint requires
  3. Update the client UI to hide owner-only actions for non-owners
  4. If the requirement changed (admins should also be allowed), revisit the middleware logic and use withAdmin or a role check accordingly

Example fix

// before
// member calls owner-only endpoint
await api.delete(`/ws/${wsId}/payment-method`)
// after
if (myWorkspaceRole === 'Owner') {
  await api.delete(`/ws/${wsId}/payment-method`)
}
Defensive patterns

Strategy: validation

Validate before calling

const loginInfo = await accountClient.getLoginInfoByToken()
if (loginInfo == null || !('role' in loginInfo) || loginInfo.role !== AccountRole.Owner) {
  throw new Error('Owner privileges required for this endpoint')
}

Type guard

function isOwner(info: unknown): info is { role: AccountRole.Owner } {
  return typeof info === 'object' && info != null && 'role' in info && (info as { role: AccountRole }).role === AccountRole.Owner
}

Try / catch

try {
  const res = await callOwnerApi()
} catch (err) {
  if (err.response?.status === 401 && err.response.data?.message === 'Workspace owners only') {
    // show 'owner permission required' — do not retry
  }
}

Prevention

When it happens

Trigger: Any non-owner workspace member (Admin, Member, etc.) calling an owner-protected payment route, e.g. attempting to delete billing settings or manage payment methods for a workspace they don't own.

Common situations: Team members trying to change billing/payment settings; a user who believes they are the owner but is actually an admin or member; ownership transferred to someone else; front-end showing owner-only controls without checking the user's actual role.

Related errors


AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29). Data as JSON: /api/errors/21614f8b1d6b4eef. Report an issue: GitHub.