Significant-Gravitas/AutoGPT · warning · HTTPException

Invitation has expired

Error message

Invitation has expired

What it means

Raised by POST /api/invitations/{token}/accept when the current UTC time is past the invitation's expiresAt (created as now + INVITATION_TTL_DAYS at create time). The token is time-limited by design. HTTP 400.

Source

Thrown at autogpt_platform/backend/backend/api/features/orgs/invitation_routes.py:157

@router.post(
    "/{token}/accept",
    summary="Accept invitation",
    tags=["invitations"],
    dependencies=[Security(requires_user)],
)
async def accept_invitation(
    token: str,
    user_id: Annotated[str, Security(get_user_id)],
) -> dict:
    invitation = await prisma.orginvitation.find_unique(where={"token": token})
    if invitation is None:
        raise NotFoundError("Invitation not found")
    if invitation.acceptedAt is not None:
        raise HTTPException(400, detail="Invitation already accepted")
    if invitation.revokedAt is not None:
        raise HTTPException(400, detail="Invitation has been revoked")
    if invitation.expiresAt < datetime.now(timezone.utc):
        raise HTTPException(400, detail="Invitation has expired")

    # Verify the accepting user's email matches the invitation
    accepting_user = await prisma.user.find_unique(where={"id": user_id})
    if accepting_user is None:
        raise HTTPException(401, detail="User not found")
    if accepting_user.email.lower() != invitation.email.lower():
        raise HTTPException(
            403,
            detail="This invitation was sent to a different email address",
        )

    # Add user to org (idempotent — handles race condition from concurrent accepts)
    try:
        await org_db.add_org_member(
            org_id=invitation.orgId,
            user_id=user_id,
            is_admin=invitation.isAdmin,
            is_billing_manager=invitation.isBillingManager,

View on GitHub (pinned to 9c8bb5550f)

Solutions

  1. Ask the admin to send a fresh invitation — expired tokens cannot be extended.
  2. If you administer the backend and truly need a longer window, adjust INVITATION_TTL_DAYS and regenerate; otherwise leave the default.
  3. Client: compare the expiry shown in the invitation payload (GET pending) with current time before offering the Accept button.
  4. Check server clock synchronization (NTP) if you believe the invitation should still be valid.
Defensive patterns

Strategy: validation

Validate before calling

const inv = await api.get('/api/invitations/pending').then(r => r.data.find(i => i.token === token));
if (inv && new Date(inv.expiresAt) < new Date()) {
  show('This invitation has expired. Ask for a new one.');
}

Try / catch

try {
  await api.post(`/api/invitations/${token}/accept`);
} catch (e) {
  if (e.status === 400 && e.detail === 'Invitation has expired') { show('Invitation expired'); return; }
  throw e;
}

Prevention

When it happens

Trigger: User clicks an invitation link older than the TTL (INVITATION_TTL_DAYS), or a system clock skew makes now() appear past expiry; accepting a link days after the email arrived.

Common situations: User leaves invitation emails unread in the inbox; onboarding emails queued in spam for days; long-lived bookmarked links; server clock drift.

Related errors


AI-assisted analysis of Significant-Gravitas/AutoGPT@9c8bb5550f (2026-08-14). Data as JSON: /api/errors/d88085587638d8e6. Report an issue: GitHub.