Teams and subscription billing
My customer paid for team seats. Why can't they invite another member?
Stripe shows five paid seats. Your Members screen shows four people. Someone tries to invite a teammate and gets seat_limit.
Before digging through webhook logs, open the pending invitations. Four members plus one invitation can account for all five seats. The person who hasn't clicked Accept yet is the person missing from your count.
This is one of those billing problems where every individual screen can be telling the truth while the customer still has no idea what's happening.
The fifth seat might be an invitation
If you send an invitation, you need a seat available when the person accepts it. Helpful Mods Teams reserves that seat when the invitation is created.
Here's the five-seat workspace:
| Record | Count |
|---|---|
| Paid seats | 5 |
| Current members | 4 |
| Pending invitations | 1 |
| Seats available for another invitation | 0 |
Once the invitation is accepted, the count becomes five members and zero pending invitations. If it's revoked or expires, that reservation becomes available again.
Show pending invitations beside the member count. It saves the customer from opening a billing ticket to ask where their last seat went. It also gives them an obvious fix: revoke the invitation they no longer need, then invite the right person.
Teams checks capacity again when an invitation is accepted. That matters if the workspace bought fewer seats after sending it. An invitation from last week can't carry last week's capacity around forever.
Check what Stripe actually sold
Open the subscription attached to this workspace and look at its price, quantity and billing status. The quantity is the interesting part of a per-seat subscription. A plan called "Team" could have a quantity of one, five or twenty; your application needs to use the number the customer bought.
Stripe explains the model in its subscription quantities guide.
Then check the installation's own limit. Teams has a maxSeats setting. If Stripe has ten seats and the installation allows five, you've found a configuration mismatch. Changing the subscription alone won't change that setting.
I like to put these numbers next to each other while debugging: Stripe quantity, configured maximum, current members, pending invitations. A screenshot saying "Payment successful" leaves most of the useful information out.
Find the last change your app processed
If the counts still don't explain it, follow the subscription change into the application. Did Stripe deliver the event? Did the worker process it? Which subscription state did the app save?
In a standalone Teams installation, these commands give you the application's side of the story:
access {"orgId":"YOUR_SAVED_ORG_ID"}
members {"orgId":"YOUR_SAVED_ORG_ID"}
invitations {"orgId":"YOUR_SAVED_ORG_ID"}
The access result includes active, billingStatus, seats, members, pendingInvitations and overSeatLimit. Run the commands through the included CLI or your authenticated API connection.
When a change is waiting to be reconciled, check the persistent worker and its saved job. Teams' reconcile command takes the saved checkoutId and a stable requestKey, then queues the work. Follow that job through to the updated access record. Repeatedly creating new checkouts makes the original problem harder to follow.
Stripe's subscription webhook guide is useful here, especially when a subscription changed successfully in Stripe and the app is still showing the old state.
Read the invitation error too
A full workspace isn't the only way an invitation can fail:
| Error | Where I'd look |
|---|---|
seat_limit | Paid capacity, current members and reserved invitations. |
403 forbidden | The caller's workspace role and the account the invitation belongs to. |
invitation_expired | The invitation's expiry; send a fresh one if the person still needs access. |
Standalone Teams binds an invitation to a stable targetActorId. Your backend maps the recipient to that account. Foundation Kit supplies the account mapping and invitation screens as part of the connected application.
That distinction becomes useful when someone opens an invitation in a browser signed into their other account. You can have plenty of seats and still be asking the wrong identity to accept the invitation.
Paid Membership and Teams brings the workspace roles, seat reservations, invitations and Stripe reconciliation into your existing app. Foundation Kit includes it with accounts and workspace screens already connected.