Subscription states & paywall

A team's subscription lives in one of seven states. This page is the full state machine: what each state means, what's blocked, and how you move between them.

The states

StateMeaningPool writes blocked?Duration
trial14-day trial, starts on team creationNoUntil trial_ends_at
activePaid plan, currentNoUntil current_period_end
past_dueInvoice failed, Stripe is retrying the cardNo (degraded)Until grace_period_ends_at
canceledSubscription canceled, in the grace windowNo (degraded)Until grace_period_ends_at
graceGeneric grace state (e.g., post-trial)No (degraded)Until grace_period_ends_at (5 days)
expiredPast grace period, no recovery without re-subscribeYesIndefinite
noneNo subscription at allYesUntil a plan or trial is started

"Degraded" means the team can read the pool but not mutate it — they see cards but can't push, pull, claim, or reassign. "Blocked" means pool writes are rejected and the UI shows a paywall banner.

The normal lifecycle

none → trial → grace → expired
              ↓
              active ↔ past_due → grace → expired

Create a team (owner flow)

→ subscription enters trial with trial_ends_at = now + 14d, grace_period_ends_at = now + 19d

Day 14

→ trial ends → status becomes grace → 5-day window begins

Day 19

→ grace ends → status becomes expired → pool writes now blocked

At any point, owner/admin hits Upgrade

→ Stripe checkout → webhook creates subscription → status becomes active (or trialing during Stripe's own trial if configured)

During active, a card fails

→ Stripe marks invoice payment_failed → webhook → status becomes past_due → Ideafy starts a 5-day grace window

During past_due, a new card works

→ webhook invoice.payment_succeeded → status back to active, window cleared

Owner cancels from the portal

→ webhook customer.subscription.deleted → status canceled → grace window starts. You keep full access until your current paid period ends (Stripe doesn't charge for unused time), then it's 5 more days of degraded access, then expired

The grace period

Grace is 5 days. It exists for three reasons:

  1. Payment failure isn't always the user's fault. Expired card, bank glitch, card on-hold. Giving a 5-day window avoids punishing people for their bank's problems
  2. Cancellations are reversible. 5 days lets you walk back a canceled subscription without your team losing pool access
  3. Weekends. If your trial ends on Friday, you have the weekend plus the week to deal with it

During grace, Ideafy shows a banner: "Subscription expired — upgrade to continue pool operations." Reads still work. You can still browse the pool, open cards, see history. Writes are blocked.

What none means

none happens to a team that was created without a subscription starting. In practice this is rare — the owner flow starts a trial automatically. none mostly shows up when:

  • A team is created through an internal migration or import that skipped the trial-start step
  • The trial-start flow fails for some reason and the team ends up without a subscription record

Fix: Settings → Billing → Start trial (if available) or Upgrade directly.

How the paywall is enforced

The paywall is enforced in the API layer, not in the UI. A middleware called requirePaidAccess runs on every pool write endpoint:

  • Reads the team's subscription row
  • Computes the status
  • Returns { ok: true } if status is active or trial
  • Returns { ok: false, status } otherwise
  • Failing endpoints return HTTP 402 with a subscription_required error code

This means bypassing the UI doesn't bypass the paywall. Calling the API directly, or calling through the MCP server's pool tools, hits the same check.

Pool reads don't go through the middleware — they're always allowed.

What you see as a user

  • active / trial: no banner, everything works
  • past_due / canceled / grace: orange banner at the top of the board with "X days left in grace period" and an Upgrade / Update payment button
  • expired / none: red banner with "Pool access blocked — upgrade to continue" and an Upgrade button. Pool write operations return 402 with an error toast

The Refresh status button

If you just completed a Stripe checkout and the app still shows a blocked state, there may be a few seconds of webhook delay. Settings → Billing → Refresh status forces a re-read of the subscription row from Supabase. Usually clears transient lag.


Prev: Stripe checkout & portal Next: General settings Up: User guide index

Last updated: 2026-04-13