Stirling-Tools/Stirling-PDF · error · Error

Failed to update seat count: ${error.message}

Error message

Failed to update seat count: ${error.message}

What it means

Thrown by updateEnterpriseSeats when supabase.functions.invoke('manage-billing', {body:{new_seat_count,...}}) returns a non-null error. The manage-billing function creates a billing-portal session reflecting the requested seat change; a returned error means the function failed — invalid license key, the new seat count violates Stripe's minimums, no matching subscription, or missing STRIPE_SECRET_KEY. error.message is wrapped.

Source

Thrown at frontend/editor/src/proprietary/services/licenseService.ts:546

      throw new Error(
        "Supabase is not configured. Seat updates are not available.",
      );
    }

    const baseUrl = window.location.origin;
    const returnUrl = `${baseUrl}/settings/adminPlan?seats_updated=true`;

    const { data, error } = await supabase.functions.invoke("manage-billing", {
      body: {
        return_url: returnUrl,
        license_key: licenseKey,
        self_hosted: true,
        new_seat_count: newSeatCount,
      },
    });

    if (error) {
      throw new Error(`Failed to update seat count: ${error.message}`);
    }

    if (!data || !data.url) {
      throw new Error("No billing portal URL returned");
    }

    return data.url;
  },
};

/**
 * Map license type to plan tier
 * @param licenseInfo - Current license information
 * @returns Plan tier: 'free' | 'server' | 'enterprise'
 */
export const mapLicenseToTier = (
  licenseInfo: LicenseInfo | null,
): "free" | "server" | "enterprise" | null => {

View on GitHub (pinned to 9ef20dcab8)

Solutions

  1. Read error.message — Stripe errors surface the constraint (e.g. minimum seats).
  2. Validate new_seat_count against the current seat minimum before calling.
  3. Confirm manage-billing is deployed with STRIPE_SECRET_KEY.
  4. Re-verify the license key is still active before retrying.

Example fix

// before
const url = await licenseService.updateEnterpriseSeats(newCount, key);

// after
try {
  const url = await licenseService.updateEnterpriseSeats(newCount, key);
  window.location.href = url;
} catch (e) {
  showToast(e instanceof Error ? e.message : 'Seat update failed');
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (!Number.isInteger(newSeatCount) || newSeatCount < currentMinSeats) {
  setError(`Seat count must be at least ${currentMinSeats}`);
  return;
}
if (!licenseKey) { setError('License key required'); return; }

Type guard

export function isViableSeatCount(n: number, min: number): boolean {
  return Number.isInteger(n) && n >= min;
}

Try / catch

try {
  const url = await licenseService.updateEnterpriseSeats(newCount, key);
  window.location.href = url;
} catch (e) {
  showToast(e instanceof Error ? e.message : 'Seat update failed');
}

Prevention

When it happens

Trigger: new_seat_count below the subscription's minimum seats; license_key invalid/revoked; subscription not found in Stripe; manage-billing function missing secrets; proration rejected by Stripe.

Common situations: Admin tries to reduce seats below the active-user count; license was cancelled; Stripe product configured without per-seat pricing metadata; edge function not deployed.

Related errors


AI-assisted analysis of Stirling-Tools/Stirling-PDF@9ef20dcab8 (2026-08-13). Data as JSON: /api/errors/a790d2a0ceed08ad. Report an issue: GitHub.