overleaf/overleaf · error · InvalidInstitutionalEmailError

InvalidInstitutionalEmailError

Error message

InvalidInstitutionalEmailError

What it means

InvalidInstitutionalEmailError is thrown when the affiliations API rejects a new institutional affiliation with HTTP 422, meaning the supplied email address is not a valid institutional (university-domain) email. It wraps the upstream error via withCause so the original API message is preserved.

Source

Thrown at services/web/app/src/Features/Institutions/InstitutionsAPI.mjs:229

  try {
    await _affiliationRequestFetchNothing({
      method: 'POST',
      path: `/api/v2/users/${userId.toString()}/affiliations`,
      body: {
        email,
        university,
        department,
        role,
        confirmedAt,
        entitlement,
        rejectIfBlocklisted,
      },
      defaultErrorMessage: "Couldn't create affiliation",
    })
  } catch (error) {
    if (error.info?.status === 422) {
      throw new InvalidInstitutionalEmailError(error.message).withCause(error)
    }
    throw error
  }

  if (!university) {
    return
  }

  // have notifications delete any ip matcher notifications for this university
  try {
    await NotificationsBuilder.promises
      .ipMatcherAffiliation(userId.toString())
      .read(university.id)
  } catch (err) {
    // log and ignore error
    logger.err({ err }, 'Something went wrong marking ip notifications read')
  }
}

View on GitHub (pinned to 28ad3b03b7)

Solutions

  1. Have the user enter an email on a domain owned by the institution
  2. Check the institution's confirmed email-domain settings in the institutions admin data
  3. Inspect error.cause / error.message for the specific 422 reason from the affiliations API
  4. If the domain is legitimately institutional, update the institution's email-domain allowlist

Example fix

// before
await addAffiliation(institutionId, { email: user.email })
// after
if (!user.email.endsWith('@university.edu')) {
  throw new InvalidInstitutionalEmailError(`${user.email} is not institutional`)
}
await addAffiliation(institutionId, { email: user.email })
Defensive patterns

Strategy: validation

Validate before calling

function isLikelyInstitutionalEmail(email, allowedDomains) {
  const domain = email.split('@')[1]?.toLowerCase()
  return !!domain && allowedDomains.includes(domain)
}
if (!isLikelyInstitutionalEmail(email, institutionDomains)) throw new Error('not institutional')

Type guard

function has422Info(e) {
  return typeof e === 'object' && e !== null && e.info?.status === 422
}

Try / catch

try {
  await addAffiliation(institutionId, { email, role, department })
} catch (error) {
  if (error instanceof InvalidInstitutionalEmailError) {
    notifyUser('Please use your university email address')
  } else throw error
}

Prevention

When it happens

Trigger: Calling addAffiliation with an email that the affiliations backend refuses with status 422 (e.g. free-mail domains like gmail.com, malformed address, or a domain not recognized as the university's).

Common situations: User typed a personal email instead of their university address; institution's claimed email domains are misconfigured in the institutions service; SSO/metadata changes invalidated previously accepted domains.

Related errors


AI-assisted analysis of overleaf/overleaf@28ad3b03b7 (2026-09-03). Data as JSON: /api/errors/254751a27ec526c7. Report an issue: GitHub.