Skip to content

Seat Limits

Seats are a limit, not a meter.

Your app already knows how many active members are in a workspace. Guapocado should tell your app how many are allowed.

import { defineBilling } from "@guapocado/sdk";
export default defineBilling({
entitlements: {
seats: { type: "limit" },
sso: { type: "feature" },
},
products: [
{
key: "team",
pricing: {
mode: "recurring",
type: "flat",
amount: 2900,
currency: "usd",
frequency: "month",
},
entitlements: {
sso: false,
seats: {
included: 5,
expansion: {
allowed: true,
unit: 1,
amount: 1000,
currency: "usd",
},
},
},
},
{
key: "business",
pricing: {
mode: "recurring",
type: "flat",
amount: 9900,
currency: "usd",
frequency: "month",
},
entitlements: {
sso: true,
seats: {
included: 25,
expansion: {
allowed: true,
unit: 1,
amount: 800,
currency: "usd",
},
},
},
},
],
});
import { createGuapocadoClient } from "@guapocado/sdk";
export async function inviteMember({
orgId,
email,
}: {
orgId: string;
email: string;
}) {
const customerId = `org_${orgId}`;
const guap = createGuapocadoClient({
apiKey: process.env.GUAPOCADO_API_KEY!,
customerId,
});
const seats = await guap.limit("seats");
const activeMembers = await db.member.count({
where: { orgId, status: "active" },
});
const pendingInvites = await db.invite.count({
where: { orgId, status: "pending" },
});
if (activeMembers + pendingInvites >= seats.limit) {
throw new Error("Seat limit reached");
}
return db.invite.create({
data: { orgId, email },
});
}

Count the thing your product actually reserves. Some products count only active members. Others count active members plus pending invites.

If the plan allows expansion, store purchased seats:

await guap.limits.configure("seats", {
purchased: 3,
autoExpansionEnabled: false,
});

Then limit("seats") returns the included seats plus purchased seats.

const seats = await guap.limit("seats");
seats.included; // 5
seats.purchased; // 3
seats.limit; // 8

If your product lets customers automatically buy seats when inviting members, turn on auto expansion:

await guap.limits.configure("seats", {
autoExpansionEnabled: true,
});

Then your app can implement a rule like:

if (activeMembers >= seats.limit) {
if (!seats.expansionAllowed || !seats.autoExpansionEnabled) {
throw new Error("Seat limit reached");
}
await guap.limits.configure("seats", {
purchased: seats.purchased + 1,
autoExpansionEnabled: true,
});
}

Keep the final business rule in your app because only your app knows what an active member, pending invite, or billable member means.