{"record":{"id":"12e5603c9b99dfef","repo":"hcengineering/platform","slug":"invalid-workspace","errorCode":null,"errorMessage":"Invalid workspace","messagePattern":"Invalid workspace","errorType":"http","errorClass":null,"httpStatus":401,"severity":"error","filePath":"services/backup/backup-api-pod/src/server.ts","lineNumber":190,"sourceCode":"        res.status(401).end('Unauthorized')\n        return\n      }\n      workspaceId = decoded.workspace\n      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","sourceCodeStart":172,"sourceCodeEnd":208,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/services/backup/backup-api-pod/src/server.ts#L172-L208","documentation":"The API returns 401 'Invalid workspace' when the token decodes fine, the workspace-info cache misses, and the account service's getLoginWithWorkspaceInfo() contains no membership entry for the workspace named in the URL, while the caller is not an admin. The service only lets non-admin users fetch backups of workspaces they belong to.","triggerScenarios":"GET /api/backup/<workspaceUuid>/<file> where workspaceUuid in the path is not a key of info.workspaces returned by the account client for that token, and decoded.extra?.admin !== 'true'.","commonSituations":"Typing or pasting the wrong workspace UUID in the URL, using a personal token for a workspace the user was removed from, or hitting the backup API before being added to the workspace.","solutions":["Verify the workspace UUID in the URL matches a workspace the token's account belongs to","Ask a workspace owner to add your account to the workspace","Use a token belonging to a member of the target workspace","If you should be an admin, ensure the token's extra payload includes admin='true'"],"exampleFix":"// before\nconst res = await fetch(`/api/backup/${wrongUuid}/backup.json.gz`)\n// after\nconst res = await fetch(`/api/backup/${myWorkspaceUuid}/backup.json.gz`, { headers: { Authorization: `Bearer ${token}` } })","handlingStrategy":"validation","validationCode":"const info = await accountClient.getLoginWithWorkspaceInfo()\nif (!isAdmin && info.workspaces[workspaceUuid] === undefined) {\n  throw new Error(`Not a member of workspace ${workspaceUuid}`)\n}","typeGuard":"function canAccessWorkspace(info: { workspaces: Record<string, unknown> }, wsId: string, isAdmin: boolean): boolean {\n  return isAdmin || info.workspaces[wsId] !== undefined\n}","tryCatchPattern":"try { ... } catch (e) {\n  if (e.message === 'Invalid workspace') console.error('Check workspace UUID and membership')\n  throw e\n}","preventionTips":["Copy workspace UUIDs from the account service, not by hand","Confirm membership before invoking backup downloads","Use a token of an account that belongs to the target workspace","List available workspaces from getLoginWithWorkspaceInfo first"],"tags":["auth","http-401","workspace-access"],"backgroundTag":"workspace-access-denied","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}