FlowiseAI/Flowise · error · InternalFlowiseError

Subscription is canceled

Error message

Subscription is canceled

What it means

UsageCacheManager.getSubscriptionDetails returns cached subscription details, falling back to a live Stripe retrieve on cache miss. If the retrieved subscription status is 'canceled', it throws InternalFlowiseError 401 before caching or returning. This is the cache layer's mirror of StripeManager's cancellation guard, so quota/feature code that reads subscription details also enforces entitlement.

Source

Thrown at packages/server/src/UsageCacheManager.ts:105

    }

    public async getSubscriptionDetails(subscriptionId: string, withoutCache: boolean = false): Promise<Record<string, any>> {
        const stripeManager = await StripeManager.getInstance()
        if (!stripeManager || !subscriptionId) {
            return UNLIMITED_QUOTAS
        }

        // Skip cache if withoutCache is true
        if (!withoutCache) {
            const subscriptionData = await this.getSubscriptionDataFromCache(subscriptionId)
            if (subscriptionData?.subsriptionDetails) {
                return subscriptionData.subsriptionDetails
            }
        }

        // If not in cache, retrieve from Stripe
        const subscription = await stripeManager.getStripe().subscriptions.retrieve(subscriptionId)
        if (subscription.status === 'canceled') throw new InternalFlowiseError(StatusCodes.UNAUTHORIZED, 'Subscription is canceled')

        // Update subscription data cache
        await this.updateSubscriptionDataToCache(subscriptionId, { subsriptionDetails: stripeManager.getSubscriptionObject(subscription) })

        return stripeManager.getSubscriptionObject(subscription)
    }

    public async getQuotas(subscriptionId: string, withoutCache: boolean = false): Promise<Record<string, number>> {
        const stripeManager = await StripeManager.getInstance()
        if (!stripeManager || !subscriptionId) {
            return UNLIMITED_QUOTAS
        }

        // Skip cache if withoutCache is true
        if (!withoutCache) {
            const subscriptionData = await this.getSubscriptionDataFromCache(subscriptionId)
            if (subscriptionData?.quotas) {
                return subscriptionData.quotas

View on GitHub (pinned to abe4a8601a)

Solutions

  1. Have cancellation webhooks write a canceled marker into the cache so subsequent lookups short-circuit without throwing.
  2. Catch the 401 in callers and degrade to a disabled/free entitlement set.
  3. Filter canceled subscription IDs out of periodic usage jobs.
  4. Keep cached canceled status long-lived so the throw only happens once.

Example fix

// before
const subscription = await stripeManager.getStripe().subscriptions.retrieve(subscriptionId)
if (subscription.status === 'canceled') throw new InternalFlowiseError(StatusCodes.UNAUTHORIZED, 'Subscription is canceled')

// after: cache + return a canceled detail object instead of throwing
if (subscription.status === 'canceled') {
    const details = stripeManager.getSubscriptionObject(subscription)
    await this.updateSubscriptionDataToCache(subscriptionId, { subsriptionDetails: details })
    return details
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-check cached/live status to avoid the throw inside getSubscriptionDetails
const cached = await cacheManager.getSubscriptionDataFromCache(subscriptionId)
if (cached?.subsriptionDetails?.status === 'canceled') {
    // degrade without triggering the live-retrieve throw
    return cached.subsriptionDetails
}

Type guard

function isSubscriptionActive(details: { status?: string } | undefined | null): boolean {
    return !!details && details.status !== 'canceled'
}

Try / catch

try {
    const details = await cacheManager.getSubscriptionDetails(subscriptionId)
} catch (err) {
    if (err instanceof InternalFlowiseError && err.statusCode === StatusCodes.UNAUTHORIZED) {
        // subscription canceled: degrade to a disabled state
        return { status: 'canceled', active: false }
    }
    throw err
}

Prevention

When it happens

Trigger: Any code path calling getSubscriptionDetails (directly or via quota/feature resolution) for a subscription Stripe reports canceled, after a cache miss forces the live retrieve.

Common situations: Background usage-sync job running against a just-canceled subscription. New deployment with an empty cache so the first request for a canceled sub hits Stripe. Cache eviction (TTL) expiring a previously-active entry after cancellation.

Related errors


AI-assisted analysis of FlowiseAI/Flowise@abe4a8601a (2026-08-12). Data as JSON: /api/errors/681f6793f01a773c. Report an issue: GitHub.