hcengineering/platform · error · ApiError

Couldn't find workspace with the provided token

Error message

Couldn't find workspace with the provided token

What it means

The pod-export service authenticates every request by calling getLoginInfoByToken() with the token extracted from headers/query, then validates the result with isWorkspaceLoginInfo(). When the account service does not recognize the token or returns a payload that is not valid workspace login info, the request is rejected with this 401 ApiError. It means the caller did not present a usable workspace-scoped token.

Source

Thrown at services/export/pod-export/src/server.ts:236

  req: Request,
  res: Response,
  wsIds: WorkspaceIds,
  token: string,
  socialId: PersonId,
  next: NextFunction
) => Promise<void>

const handleRequest = async (
  fn: AsyncRequestHandler,
  req: Request,
  res: Response,
  next: NextFunction
): Promise<void> => {
  try {
    const token = retrieveToken(req.headers, req.query)
    const wsLoginInfo = await getAccountClient(token).getLoginInfoByToken()
    if (!isWorkspaceLoginInfo(wsLoginInfo)) {
      throw new ApiError(401, "Couldn't find workspace with the provided token")
    }
    if (wsLoginInfo.socialId === undefined) {
      throw new ApiError(401, 'Social ID is missing')
    }
    const wsIds = {
      uuid: wsLoginInfo.workspace,
      dataId: wsLoginInfo.workspaceDataId,
      url: wsLoginInfo.workspaceUrl
    }
    await fn(req, res, wsIds, token, wsLoginInfo.socialId, next)
  } catch (err: unknown) {
    next(err)
  }
}

const wrapRequest = (fn: AsyncRequestHandler) => (req: Request, res: Response, next: NextFunction) => {
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
  handleRequest(fn, req, res, next)

View on GitHub (pinned to 63e28dc964)

Solutions

  1. Generate a fresh workspace token and retry the request with it.
  2. Verify the token is sent in the header (or query) key retrieveToken expects and is not truncated by shell/URL encoding.
  3. Confirm the service is configured with the correct AccountsUrl for your environment (dev/prod) so the token is validated against the right account service.
  4. Log wsLoginInfo (without secrets) to see whether the account call returns an error object rather than workspace info.

Example fix

// before
curl -H 'Authorization: Bearer OLD_TOKEN' /export?format=json
// after
curl -H 'Authorization: Bearer NEWLY_ISSUED_WORKSPACE_TOKEN' /export?format=json
Defensive patterns

Strategy: validation

Validate before calling

if (typeof token !== 'string' || token.length === 0) { throw new Error('Refusing to call export without a workspace token') }

Type guard

function isWorkspaceLoginInfo(v: unknown): v is { socialId: string | undefined; workspace: string; workspaceDataId: string; workspaceUrl: string } {
  return typeof v === 'object' && v !== null && 'workspace' in v && typeof (v as any).workspace === 'string'
}

Try / catch

try {
  await exportWorkspace(params)
} catch (e) {
  if (e instanceof ApiError && e.status === 401) { await refreshToken(); return exportWorkspace(params) }
  throw e
}

Prevention

When it happens

Trigger: POST/GET to an export endpoint where retrieveToken(req.headers, req.query) yields a token that is expired, revoked, belongs to a non-workspace account, or is absent/malformed so getAccountClient(token).getLoginInfoByToken() returns data failing isWorkspaceLoginInfo.

Common situations: Using an expired workspace token in a script; copying the wrong token type (e.g. a personal/service token instead of a workspace token); passing the token as a query param that got URL-truncated; environment pointing the account client at the wrong AccountsUrl.

Related errors


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