koala73/worldmonitor · error · ConvexError

INVITE_ALREADY_USED

INVITE_ALREADY_USED

Error message

INVITE_ALREADY_USED

What it means

Structured ConvexError thrown by acceptBusinessInvite when the referenced grant's status is not pending — i.e. the single-use invite was already accepted or revoked. Redeeming a used invite token is rejected by design.

Solutions

  1. If already accepted, no action is needed — entitlement is already granted
  2. Otherwise request a fresh invite from the Business owner
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at convex/payments/businessSeats.ts:468 when the library encounters an invalid state.

Common situations: See trigger scenarios.


AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-08-21). Data as JSON: /api/errors/6b038c9b14950612. Report an issue: GitHub.

Appendix: source

Thrown at convex/payments/businessSeats.ts:464

 * grant to `accepted`, stamps `inviteeUserId`, and recomputes the invitee's
 * entitlement. Single-use: accepted/revoked/expired tokens are rejected.
 */
export const acceptBusinessInvite = mutation({
  args: { grantId: v.id("businessProGrants"), token: v.string() },
  handler: async (ctx, args) => {
    const userId = await requireUserId(ctx);
    const identity = await resolveUserIdentity(ctx);
    const inviteeEmail = identity?.email?.trim().toLowerCase();
    if (!inviteeEmail) {
      throw new ConvexError({ kind: "INVITEE_EMAIL_UNAVAILABLE" });
    }

    const grant = await ctx.db.get(args.grantId);
    if (!grant) {
      throw new ConvexError({ kind: "GRANT_NOT_FOUND" });
    }
    if (grant.status !== "pending") {
      throw new ConvexError({ kind: "INVITE_ALREADY_USED" });
    }
    const now = Date.now();
    if (grant.expiresAt <= now) {
      throw new ConvexError({ kind: "INVITE_EXPIRED" });
    }
    if (!(await verifyBusinessInviteToken(args.grantId, args.token))) {
      throw new ConvexError({ kind: "INVALID_INVITE_TOKEN" });
    }
    if (grant.inviteeEmail !== inviteeEmail) {
      throw new ConvexError({ kind: "INVITE_EMAIL_MISMATCH" });
    }
    if (!sameDomain(grant.inviteeEmail, inviteeEmail)) {
      throw new ConvexError({ kind: "INVITE_EMAIL_MISMATCH" });
    }
    if (!isCorporateDomain(inviteeEmail)) {
      throw new ConvexError({ kind: "INVITEE_DOMAIN_NOT_CORPORATE" });
    }

View on GitHub (pinned to 7d06c8633d)