Guides
Organizations & Entities
Implement per-seat and per-organization pricing with entity-scoped metering
Organizations & Entities
Owostack supports per-seat and per-organization pricing through entities. You create entities to represent seats, workspaces, or any scoped resource, then track usage per-entity.
This guide shows a common setup:
- Plan includes 5 seats
- Each seat gets 1000 AI credits per month
1. Define features
import { metered, plan, Owostack } from "@owostack/core";
export const seats = metered("seats", { name: "Team Seats" });
export const aiCredits = metered("ai-credits", { name: "AI Credits" });2. Create the plan
export default new Owostack({
secretKey: process.env.OWOSTACK_SECRET_KEY!,
catalog: [
plan("team", {
name: "Team",
price: 2000,
currency: "USD",
interval: "monthly",
features: [
seats.limit(5, { reset: "monthly", overage: "block" }),
aiCredits.limit(1000, { reset: "monthly" }),
],
}),
],
});3. Create the organization
// Create or get the org customer
const org = await owo.customer({
email: "org@acme.com",
name: "Acme Corp",
});4. Start the subscription
await org.attach({
product: "team",
});5. Add a seat (entity)
Use addEntity() to reserve a seat. This validates against the seat limit and tracks the entity.
// Add team members - validates against 5-seat limit
try {
await owo.addEntity({
customer: "org@acme.com",
feature: "seats",
entity: "user_123", // Your user ID
name: "John Doe",
email: "john@acme.com",
metadata: { role: "admin" },
});
} catch (err) {
if (err.code === "limit_exceeded") {
// Show upgrade UI
console.log("Seat limit reached - upgrade to add more seats");
}
}6. Track AI credits per seat
Use entity parameter to scope usage to a specific seat:
await owo.track({
customer: "org@acme.com",
feature: "ai-credits",
entity: "user_123", // Deduct from this seat's quota
value: 20,
});To check remaining credits for a seat:
const credits = await owo.check({
customer: "org@acme.com",
feature: "ai-credits",
entity: "user_123",
});
console.log("Credits remaining:", credits.balance);7. List and manage seats
// List all active seats
const { entities } = await owo.listEntities({
customer: "org@acme.com",
feature: "seats",
});
// Remove a seat (frees the slot)
await owo.removeEntity({
customer: "org@acme.com",
feature: "seats",
entity: "user_123",
});Org-wide credits (shared pool)
For a shared credit pool across all seats, omit the entity parameter:
// Track against org-wide pool
await owo.track({
customer: "org@acme.com",
feature: "shared-credits",
value: 100,
// No entity = org-wide
});Complete example
import { Owostack } from "@owostack/core";
const owo = new Owostack({
secretKey: process.env.OWOSTACK_SECRET_KEY!,
});
async function setupOrg() {
// 1. Create org
const org = await owo.customer({
email: "org@acme.com",
name: "Acme Corp",
});
// 2. Subscribe to team plan
await org.attach({ product: "team" });
// 3. Add team members
const teamMembers = [
{ id: "user_1", name: "Alice", email: "alice@acme.com" },
{ id: "user_2", name: "Bob", email: "bob@acme.com" },
];
for (const member of teamMembers) {
try {
await owo.addEntity({
customer: org.email,
feature: "seats",
entity: member.id,
name: member.name,
email: member.email,
});
console.log(`Added ${member.name} as a seat`);
} catch (err) {
if (err.code === "limit_exceeded") {
console.error(`Cannot add ${member.name}: seat limit reached`);
}
}
}
// 4. Track usage per seat
await owo.track({
customer: org.email,
feature: "ai-credits",
entity: "user_1",
value: 100,
});
// 5. Check seat status
const seatStatus = await owo.check({
customer: org.email,
feature: "ai-credits",
entity: "user_1",
});
console.log(`User 1 has ${seatStatus.balance} credits remaining`);
}Notes
- Entity uniqueness: Entity IDs are unique per feature. The same ID can be reused across different features.
- Limit validation:
addEntity()validates synchronously against the feature limit. If the limit would be exceeded, it throws with codelimit_exceeded. - Entity required: You must explicitly add an entity with
addEntity()before using it incheck()ortrack(). Attempting to track/check a non-existent entity will fail withentity_not_founderror. - Seat removal: Removing an entity immediately frees the slot for reuse.
- Metadata: Store custom data (role, permissions, etc.) in the
metadatafield when adding entities. - Org vs individual: An org customer is the same as an individual customer in Owostack. Use entities to represent seats/workspaces within that customer.
API Reference
See the SDK reference for detailed documentation:
customer()- Create and manage customersaddEntity()- Add entities/seatsremoveEntity()- Remove entitieslistEntities()- List entitiescheck()- Check feature access (supports entity scope)track()- Track usage (supports entity scope)