Owostack
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 code limit_exceeded.
  • Entity required: You must explicitly add an entity with addEntity() before using it in check() or track(). Attempting to track/check a non-existent entity will fail with entity_not_found error.
  • Seat removal: Removing an entity immediately frees the slot for reuse.
  • Metadata: Store custom data (role, permissions, etc.) in the metadata field 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:

On this page