{"record":{"id":"bd6209fd4063ffc4","repo":"Significant-Gravitas/AutoGPT","slug":"invitation-has-been-revoked","errorCode":null,"errorMessage":"Invitation has been revoked","messagePattern":"Invitation has been revoked","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"autogpt_platform/backend/backend/api/features/orgs/invitation_routes.py","lineNumber":155,"sourceCode":"\n\n@router.post(\n    \"/{token}/accept\",\n    summary=\"Accept invitation\",\n    tags=[\"invitations\"],\n    dependencies=[Security(requires_user)],\n)\nasync def accept_invitation(\n    token: str,\n    user_id: Annotated[str, Security(get_user_id)],\n) -> dict:\n    invitation = await prisma.orginvitation.find_unique(where={\"token\": token})\n    if invitation is None:\n        raise NotFoundError(\"Invitation not found\")\n    if invitation.acceptedAt is not None:\n        raise HTTPException(400, detail=\"Invitation already accepted\")\n    if invitation.revokedAt is not None:\n        raise HTTPException(400, detail=\"Invitation has been revoked\")\n    if invitation.expiresAt < datetime.now(timezone.utc):\n        raise HTTPException(400, detail=\"Invitation has expired\")\n\n    # Verify the accepting user's email matches the invitation\n    accepting_user = await prisma.user.find_unique(where={\"id\": user_id})\n    if accepting_user is None:\n        raise HTTPException(401, detail=\"User not found\")\n    if accepting_user.email.lower() != invitation.email.lower():\n        raise HTTPException(\n            403,\n            detail=\"This invitation was sent to a different email address\",\n        )\n\n    # Add user to org (idempotent — handles race condition from concurrent accepts)\n    try:\n        await org_db.add_org_member(\n            org_id=invitation.orgId,\n            user_id=user_id,","sourceCodeStart":137,"sourceCodeEnd":173,"githubUrl":"https://github.com/Significant-Gravitas/AutoGPT/blob/9c8bb5550f446ba5d3046b78896578742495b3cf/autogpt_platform/backend/backend/api/features/orgs/invitation_routes.py#L137-L173","documentation":"Raised by POST /api/invitations/{token}/accept when the invitation's revokedAt timestamp is set — an admin revoked the invitation before it was accepted. Revocation is a soft-delete; the row stays so this explicit 400 is returned instead of a bare 404. HTTP 400.","triggerScenarios":"Admin clicks Revoke while the invitee still holds the email link; invitee accepts an invitation that was revoked earlier (e.g., sent to the wrong address).","commonSituations":"Race between admin cleanup and user acceptance; stale email links after an admin reorganized memberships; revoked invitations whose emails were already delivered.","solutions":["Surface a clear 'invitation revoked, contact the org admin' message to the user.","Admin: create a new invitation if membership is still desired — revoked tokens cannot be reactivated.","Do not retry the accept; the state is terminal until a new invitation is issued."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await api.post(`/api/invitations/${token}/accept`);\n} catch (e) {\n  if (e.status === 400 && e.detail === 'Invitation has been revoked') {\n    show('This invitation was revoked. Contact the organization admin.'); return;\n  }\n  throw e;\n}","preventionTips":["Do not retry accept after a revoked response — request a new invitation","Admins: revoke and resend instead of editing live invitations","Communicate revocations to invitees out-of-band"],"tags":["invitations","revoked","state","http-400"],"backgroundTag":null,"analyzedSha":"9c8bb5550f446ba5d3046b78896578742495b3cf","analyzedAt":"2026-08-14T17:17:21.957Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}