hcengineering/platform · error · PlatformError
platform.status.Forbidden
platform.status.Forbidden
Error message
Forbidden
What it means
checkJoin looks up the invitation via getWorkspaceInvite(db, inviteId); when no invite exists for that id it throws platform.status.Forbidden. The server intentionally returns Forbidden rather than NotFound so it does not leak which invite ids are valid.
Source
Thrown at server/account/src/operations.ts:1096
* Given an invite and a token, checks if the user has already joined the workspace and updates the role if necessary.
* Returns the workspace login information if the user has already joined. Otherwise, throws an error.
*/
export async function checkJoin (
ctx: MeasureContext,
db: AccountDB,
branding: Branding | null,
token: string,
params: { inviteId: string }
): Promise<WorkspaceLoginInfo> {
const { inviteId } = params
if (inviteId == null || inviteId === '') {
throw new PlatformError(new Status(Severity.ERROR, platform.status.BadRequest, {}))
}
const invite = await getWorkspaceInvite(db, inviteId)
if (invite == null) {
throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
}
const { account: accountUuid } = decodeTokenVerbose(ctx, token)
const emailSocialId = await db.socialId.findOne({
type: SocialIdType.EMAIL,
personUuid: accountUuid,
verifiedOn: { $gt: 0 }
})
const email = emailSocialId?.value ?? ''
const workspaceUuid = await checkInvite(ctx, invite, email)
const workspace = await getWorkspaceById(db, workspaceUuid)
if (workspace === null) {
ctx.error('Workspace not found in checkJoin', { workspaceUuid, email, inviteId })
throw new PlatformError(new Status(Severity.ERROR, platform.status.WorkspaceNotFound, { workspaceUuid }))
}
const role = await db.getWorkspaceRole(accountUuid, workspace.uuid)View on GitHub (pinned to 63e28dc964)
Solutions
- Request a fresh invite link from a workspace admin, since the referenced invite no longer exists.
- Verify the inviteId matches an entry in the workspace invite store (getWorkspaceInvite) before calling.
- Confirm you are calling the same environment where the invite was created.
- Handle this status in the UI with an 'invite is no longer valid' message rather than retrying.
Example fix
// before
await ops.checkJoin(ctx, token, { inviteId }) // invite revoked
// after
const invite = await getWorkspaceInvite(db, inviteId)
if (!invite) return { error: 'invite-no-longer-valid', requestNewInvite: true }
await ops.checkJoin(ctx, token, { inviteId }) Defensive patterns
Strategy: try-catch
Try / catch
try {
return await ops.checkJoin(ctx, token, { inviteId })
} catch (e) {
if (isStatus(e, platform.status.Forbidden)) {
// treat as unknown/revoked invite: offer 'request new invite' UX
return null
}
throw e
} Prevention
- Do not cache invite links; always fetch the latest one from the workspace admin API.
- Give invites expiry handling in the UI (warn before the retention window ends).
- Never guess or fabricate inviteIds.
When it happens
Trigger: Calling checkJoin with an inviteId whose invite row was deleted (invite revoked or expired cleanup job); a typo'd or fabricated inviteId; an invite from a different database/environment.
Common situations: Users clicking stale invite links after the admin revoked them; invites pruned by retention/expiry jobs; testing with inviteIds copied from another environment; invites invalidated by a workspace deletion that left links circulating.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- core.status.ObjectNotFound
- Integration not found: ${JSON.stringify(integrationKey)}
- platform.status.Forbidden
- platform.status.ResourceNotFound
- Lead not found, _id: ${ref}
AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29).
Data as JSON: /api/errors/b7be6699a1273807.
Report an issue: GitHub.