{"record":{"id":"42226b71ef897b48","repo":"hcengineering/platform","slug":"not-an-owner-of-workspace","errorCode":null,"errorMessage":"Not an owner of workspace","messagePattern":"Not an owner of workspace","errorType":"http","errorClass":null,"httpStatus":401,"severity":"error","filePath":"services/backup/backup-api-pod/src/server.ts","lineNumber":194,"sourceCode":"      isAdmin = decoded.extra?.admin === 'true'\n    } catch (err: any) {\n      res.status(401).end('Unauthorized')\n      return\n    }\n    let wsInfo: WorkspaceIds | undefined = wsInfoCache.get(workspaceId)\n    const accountClient = getClient(config.AccountsUrl, token)\n\n    if (wsInfo === undefined) {\n      try {\n        const info = await accountClient.getLoginWithWorkspaceInfo()\n        const winfo = info.workspaces[workspaceId]\n        if (!isAdmin) {\n          if (winfo === undefined) {\n            res.status(401).end('Invalid workspace')\n            return\n          } else {\n            if (winfo.role !== AccountRole.Owner) {\n              res.status(401).end('Not an owner of workspace')\n              return\n            }\n          }\n        }\n        const wssInfo = await accountClient.getWorkspaceInfo()\n        wsInfo = {\n          url: wssInfo.url,\n          dataId: wssInfo.dataId,\n          uuid: workspaceId\n        }\n        wsInfoCache.set(workspaceId, wsInfo)\n      } catch (err: any) {\n        res.status(401).end('Invalid workspace')\n        return\n      }\n    }\n\n    const dataId = wsInfo.dataId ?? (wsInfo.uuid as unknown as WorkspaceDataId)","sourceCodeStart":176,"sourceCodeEnd":212,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/services/backup/backup-api-pod/src/server.ts#L176-L212","documentation":"The API returns 401 'Not an owner of workspace' when the token is valid and the workspace exists, but the authenticated user's role in that workspace is not AccountRole.Owner and the token does not carry admin='true'. The backup download endpoint is restricted to owners (or admins) even for workspace members.","triggerScenarios":"GET /api/backup/<workspaceUuid>/<file> with a token whose account membership has role Member/Guest rather than Owner, and extra.admin !== 'true'.","commonSituations":"A regular team member tries to download backup archives, an owner downgraded their own role, or the account service reports a stale role after a recent promotion.","solutions":["Request the backup using a token from a workspace Owner account","Have an Owner promote your role to Owner in workspace settings","If you operate the service, issue a token with extra.admin='true' for automation","Re-login to refresh membership info if your role was recently changed"],"exampleFix":"// before\n// token of a Member role user\nconst token = memberToken\n// after\nconst token = ownerToken // token of an AccountRole.Owner user","handlingStrategy":"validation","validationCode":"const winfo = info.workspaces[workspaceUuid]\nif (!isAdmin && winfo?.role !== AccountRole.Owner) {\n  throw new Error('Backup download requires Owner role')\n}","typeGuard":"function isOwnerOrAdmin(winfo: { role: AccountRole } | undefined, isAdmin: boolean): boolean {\n  return isAdmin || winfo?.role === AccountRole.Owner\n}","tryCatchPattern":"try { ... } catch (e) {\n  if (e.message === 'Not an owner of workspace') throw new Error('Request an Owner to run the backup download')\n  throw e\n}","preventionTips":["Check winfo.role in workspace info before calling backup endpoints","Use Owner-issued tokens for backup automation","Issue admin='true' extra tokens only for trusted service accounts","Re-authenticate after role changes"],"tags":["auth","http-401","permissions","workspace-owner"],"backgroundTag":"workspace-access-denied","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}