Entitlements (B2B)
Decide what each company you sell to is allowed to use, and ask in one call.
Entitlements answer one question from your own code: is this company allowed to use this? You declare what you sell, put each company on a B2B plan, and ask before you let something through.
This is separate from Roles and permissions, which answers a different question: whether a given person may do something. A member can have every role in the company and still be stopped because the company never bought the feature.
Off by default, with Organizations. Turn it on from Project → Organizations.
What you sell
A feature is one thing you sell, identified by a key you choose. It is either a switch or a number:
BOOLEANfor something a company either has or does not, such as single sign-on.LIMITfor something you count, such as seats. A limit is a whole number, ornullfor no limit.
await fetch(`https://api.mailmus.app/projects/${projectId}/b2b/features`, {
method: "POST",
headers: { Authorization: `Bearer ${secretKey}`, "Content-Type": "application/json" },
body: JSON.stringify({
key: "seats",
name: "Seats",
type: "LIMIT",
defaultEnabled: true,
defaultLimit: 3,
}),
});The default is what every company gets when nothing else says otherwise, so a company you have never touched still gets a sensible answer.
B2B plans
A B2B plan is what your customer buys from you. Group your features into one, then put a company on it.
// POST /projects/{projectId}/b2b/plans
// { "key": "pro", "name": "Pro", "features": [{ "featureKey": "seats", "enabled": true, "limit": 25 }] }
// PUT /projects/{projectId}/b2b/organizations/{organizationId}/plan
// { "planKey": "pro" }Send "planKey": null to take a company off every B2B plan. It keeps its members and its history, and falls back to your defaults.
One company, one exception
Sales promised a company something its B2B plan does not include. Grant it to that company alone, say why, and let it expire on its own:
// PUT /projects/{projectId}/b2b/organizations/{organizationId}/entitlements/seats
// { "enabled": true, "limit": 100, "reason": "Negotiated for the migration", "expiresAt": "2027-01-31T00:00:00Z" }Once it expires, the B2B plan takes over again, and the record of what was granted stays. Remove it earlier with DELETE on the same address.
Asking
One call, from your server, before you let something through:
const res = await fetch(
`https://api.mailmus.app/projects/${projectId}/b2b/entitlements/check`,
{
method: "POST",
headers: { Authorization: `Bearer ${secretKey}`, "Content-Type": "application/json" },
body: JSON.stringify({ featureKey: "seats", organizationExternalId: "cus_8291" }),
},
);
const { enabled, limit, source } = await res.json();Identify the company by organizationId, or by organizationExternalId if you prefer your own identifier.
The answer tells you where it comes from, in source:
source | Meaning |
|---|---|
OVERRIDE | An exception granted to this company, still in force. |
B2B_PLAN | The value from the company's B2B plan. |
DEFAULT | Your feature default, because nothing else applies. |
UNKNOWN_FEATURE | No feature with that key is declared. |
The first one that exists wins, in that order. A key you never declared is answered with enabled: false rather than an error, so an old deployment asking for a feature you have since renamed is refused quietly instead of breaking.
Following the changes
Putting a company on a B2B plan, granting an exception and removing one all appear on the organization's history, and reach your server as organization.plan_changed, organization.entitlement_overridden and organization.entitlement_override_removed. See Webhooks.