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.
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
- account.status.Forbidden
- account.status.WorkspaceNotFound
- account.status.WorkspaceNotFound
- AccountNotFound
- core.status.ObjectNotFound
AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29).
Data as JSON: /api/errors/b7be6699a1273807.
Report an issue: GitHub.
Appendix: 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)