🔧 48015 backend [ billing ] Unlimited Free plan#270
Conversation
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit baaa949. Configure here.
| STRIPE_WEBHOOK_IP_WHITELIST = [] | ||
|
|
||
| DEFAULT_MAX_USERS = 5 | ||
| DEFAULT_MAX_USERS = 1000 |
There was a problem hiding this comment.
Existing free accounts keep old limits
High Severity
Raising DEFAULT_MAX_USERS and production MAX_INVITES only changes defaults for new rows. Existing freemium accounts keep stored max_users / max_invites, so GET /v2/accounts/plan and invite limits still reflect the old caps. No data migration updates those accounts.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit baaa949. Configure here.
| if obj.billing_plan == BillingPlanType.PREMIUM: | ||
| max_users = obj.get_paid_users_count() | ||
| elif obj.billing_plan == BillingPlanType.FREEMIUM: | ||
| max_users = settings.DEFAULT_MAX_USERS |
There was a problem hiding this comment.
Admin hardcodes free plan limit
Low Severity
Non-premium admin saves now always set max_users to a literal 1000 instead of settings.DEFAULT_MAX_USERS. That duplicates the new default and can drift if the setting changes again.
Reviewed by Cursor Bugbot for commit baaa949. Configure here.


Problem
Free-plan teams were subject to stricter usage limits than paid customers in several backend flows.
Fix / Solution
billing_sync.ProjectBillingPermissionfrom payment endpoints so the Payments API is no longer blocked at the project level.BillingPlanType.PAYMENT_PLANS.billing_syncRelease notes
Free plan accounts now support up to 1000 users and 1000 invites. Billing remains controlled per account.
Changes
API
Accounts / plan
GET /v2/accounts/plan— Freemium accounts now returnmax_users = 1000(viaDEFAULT_MAX_USERS).Tenants
POST /tenants— Stripe subscription purchase on tenant create is driven by accountbilling_synconly (global billing flag removed).PATCH /tenants/{id}— Stripe subscription description update is driven by accountbilling_synconly.DELETE /tenants/{id}— Stripe cancel / user-count sync is driven by accountbilling_synconly.Payment
POST /payment/purchase—ProjectBillingPermissionremoved; endpoint no longer returns 403 when global billing is disabled.POST /payment/subscription/cancel—ProjectBillingPermissionremoved.GET /payment/confirm—ProjectBillingPermissionremoved.GET /payment/card-setup—ProjectBillingPermissionremoved.GET /payment/products—ProjectBillingPermissionremoved.GET /payment/default-payment-method—ProjectBillingPermissionremoved.GET /payment/customer-portal—ProjectBillingPermissionremoved.POST /payment/stripe/webhooks—ProjectBillingPermissionremoved; onlyStripeWebhookPermissionapplies.Authentication / signup
billing_sync=True, regardless of the removed global billing flag.Throttling
TokenThrottle/ApiKeyThrottle(viaBaseAuthThrottle) — authenticated users on free plan are no longer rate-limited on throttled endpoints whereskip_for_paid_accounts = True.Admin
max_users = 1000.Web-client
No changes in this branch.
Test cases
API
GET /v2/accounts/planbilling_plan = free; callGET /v2/accounts/plan.max_users = 1000;billing_plan = free;is_subscribed = false.GET /v2/accounts/plan.max_usersmatches account value; subscription fields populated.GET /v2/accounts/planwithout token.POST /payment/purchasebilling_sync = truesuccess_url,products(one item withcode,quantity).billing_sync = truesuccess_url,cancel_url,products.POST /payment/purchasewithout token.POST /payment/purchaseas non-account-owner.POST /payment/purchasefrom a tenant account.DisallowForTenantPermission).billing_sync = falsePOST /payment/purchasewith valid payload.MSG_BL_0018(billing disabled for account).productsarray.MSG_BL_0002).productswith non-existent pricecode.MSG_BL_0003).success_urlor send invalid URL.success_url.products[].quantity = 0.quantity(min_value=1).POST /payment/subscription/cancelbilling_sync = truePOST /payment/subscription/cancelwith empty body.billing_sync = falseMSG_BL_0018.PATCH /tenants/{id}{ "tenant_name": "new name" }.tenant_nameupdated in response.tenant_name(required for update).PATCH /tenants/{id}without token.MasterAccountPermission).BillingPlanPermission).ExpiredSubscriptionPermission).MasterAccountAccessPermission).tenant_name: "".tenant_name: null.update_subscription_descriptionraises exception.DELETE /tenants/{id}billing_sync = trueDELETE /tenants/{id}without token.ExpiredSubscriptionPermission).MasterAccountAccessPermission).GET /payment/productsGET /payment/products.GET /payment/card-setupsuccess_urland optionalcancel_url.setup_linkreturned.success_url.success_urlorcancel_url.GET /payment/customer-portalcancel_url.linkreturned.cancel_url.GET /payment/default-payment-methodlast4,brand) returned.GET /payment/confirmbilling_sync = truetokenquery param.billing_sync = falsetoken.tokenquery param.MSG_BL_0001for invalid/missing token).tokenvalue.POST /payment/stripe/webhooksTenant creation (
POST /tenants) — billing sync behaviorbilling_sync = truebilling_sync = falsebilling_plan = free,max_users = 1000.billing_sync = truebilling_plan = null(requires purchase).Invite / throttling (indirect)
BaseAuthThrottle(authenticated users skip throttling whenskip_for_paid_accounts = True).max_userslimit; previously blocked at 5.Web-client
No web-client changes in this branch. Optional smoke checks for pages that display plan limits from the API:
maxUsersfromGET /v2/accounts/plan.max_users = 1000(may differ from hardcoded frontend constant until a separate frontend change).Note
High Risk
Payment endpoints and Stripe/subscription logic are no longer gated by the global BILLING flag; throttling no longer applies to free-plan authenticated users on default token throttles, which can affect abuse surface and billing consistency across deployments.
Overview
Raises free-plan capacity and shifts billing from a global feature flag to per-account
billing_sync.Limits:
DEFAULT_MAX_USERSgoes from 5 to 1000; productionMAX_INVITESfrom 100 to 1000. Django admin setsmax_usersto 1000 for all non-Premium plans (no separate Freemium branch using the old default).Billing gates: Removes
ProjectBillingPermissionand stops combiningsettings.PROJECT_CONF['BILLING']withbilling_syncin account, tenant, invite, transfer, signup, and convert flows—Stripe and seat updates run whenbilling_syncis true on the account. Signup defaultsbilling_syncfromPROJECT_CONF['BILLING']; new accounts withbilling_sync=Falsestill get Freemium. Stripe subscription state sync inStripeServiceonly runs when the plan is inBillingPlanType.PAYMENT_PLANS.API behavior: Payment routes are no longer blocked when global billing is off; cancel/purchase still enforce
billing_syncwhere applicable.BaseAuthThrottleskips rate limits for all authenticated users whenskip_for_paid_accountsis true (not only paid accounts).Tests drop global-
BILLINGmocks and scenarios that assumed project-level billing disabled.Reviewed by Cursor Bugbot for commit 37967ba. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Remove global BILLING feature flag gate from all billing and payment logic
ProjectBillingPermissionfrom all payment endpoints (purchase, card setup, webhooks, subscription cancel, customer portal, etc.), making billing APIs accessible without a project-level BILLING flag.settings.PROJECT_CONF['BILLING']checks in account, invite, transfer, and tenant services with direct checks onaccount.billing_sync, so billing behavior is now driven per-account rather than globally.DEFAULT_MAX_USERSfrom 5 to 1000 andMAX_INVITESfrom 100 to 1000 in settings.py, and sets non-PREMIUM accounts tomax_users=1000in the admin handler.BaseAuthThrottle.skip_conditionin throttling.py to skip throttling for all authenticated users whenskip_for_paid_accounts=True, regardless of payment status.billing_sync=Falseno longer inherit plan details from the master account; they receive default limits and no active trial.Macroscope summarized 37967ba.