Team & Settings

Billing, credits, and seats

Project88's V3 billing model — org plans govern features in org canvases, personal plans cover off-org routes, and seats govern credits.

Project88's billing model (V3) separates two questions that earlier versions conflated:

  • What features can I use? — answered by the plan of whichever org you're currently working in (or your personal plan, if you're on an off-org route).
  • How many LLM credits can I burn? — answered by your seat in that org (which caps your usage allocation), with personal credits as the off-org fallback.

The V3 model

ConceptWhere it livesGoverns
Org planorg_billing.plan_tierFeatures in an org canvas
Personal planuser_billing.personal_plan_tierFeatures off-org
Org seat (assigned tier)org_seats.assigned_tierCredit allocation + usage policy
Personal creditsuser_billing.credits_balanceOff-org LLM usage

The effective feature tier is resolved by resolveEffectiveTier({ orgPlan, personalTier }):

  • Inside an org canvas (/<org-slug>/...) the org plan wins. If your org is on pro, every member of the org gets pro features inside that org — regardless of seat row state.
  • Off-org routes (settings, profile, personal pages) fall back to your personal plan.

This is a deliberate change from V2, where a missing seat row could silently lock paid-org members out of features the org had already paid for. In V3, the org plan is the single source of truth for org-canvas feature gating, and seats are no longer in that path.

Credits and deduction

Credits are billed per user against whichever wallet is in scope:

  • Inside an org canvas, LLM usage is allocated against the user's org-seat allowance (capped by org_seats.assigned_tier).
  • Off-org, LLM usage deducts from the user's personal balance (user_billing.credits_balance).

The flow:

  1. The browser starts a chat completion via the chat-proxy Edge Function.
  2. The proxy streams the response back, including a final usage event.
  3. hosted.js (src/services/backends/hosted.js) catches the usage event and calls logUsage(), inserting a row into usage_logs and triggering credit deduction.
  4. Supabase emits a real-time update on user_billing (and org_billing / org_seats when applicable) via Postgres channels — REPLICA IDENTITY FULL is set on both org tables so the realtime filtering carries every column.
  5. The browser's billing store (src/store/billing.jsx) subscribes via a single hoisted BillingProvider (mounted once at the app root) and updates balances in memory.
  6. The CreditsDisplay pill in the top bar animates digit-by-digit to the new balance.

The whole loop is sub-second — you see credits tick down as the response finishes streaming.

get_effective_billing RPC

get_effective_billing(p_org_id uuid) is the single read path the client uses to populate BillingProvider. It bundles user_billing, the caller's org_seats row, and org_billing into one atomic JSONB response so the UI never renders against a partial snapshot.

usage_logs schema

From migration 002_managed_tables.sql:

  • id, org_id
  • provider, model
  • prompt_tokens, completion_tokens
  • created_at

Totals are derived by summing the two token columns at read time. The /status/* pages and any future usage dashboards read from this table.

Org seats and governance

Owners and admins control governance from Settings → Members (OrgMembersPanel.jsx) via three RPCs:

  • update_org_member_role — promote / demote between owner, admin, member
  • remove_org_member — kick a member out of the org
  • transfer_org_ownership — hand ownership to another member

When a member joins, an org_seats row is created with an assigned_tier capped by org_billing.plan_tier. Removing a member soft-deletes the seat; their personal plan and personal credits are unaffected.

org_seats rows are visible only to the seat holder and to org admins / owners (the SELECT policy splits self-read from admin-read-all), so coworkers cannot see each other's credit caps.

Seat management — invites, role changes, tier ceilings — runs through the manage-org-seats edge function, which enforces both org-admin auth and the per-tier ceiling against org_billing.plan_tier.

Plans

PlanNotes
freeDefault. Cap on monthly credits.
proHigher cap; richer tools and limits.
enterpriseCustom — talk to us.

Plan-driven feature checks all flow through planFeatures so every gate reads the same source of truth.

Stripe wiring

Project88's Stripe integration runs through two edge functions:

  • stripe-checkout — creates a Checkout Session for a new subscription or redirects an existing customer to the Stripe Customer Portal. Org-admin auth required.
  • stripe-webhook — receives subscription and invoice events. Signatures are verified; events are idempotent against the stripe_events table so retries are safe.

The client opens the portal via:

await api.billing.createPortalSession('<org-id>')

This is how members on a paid org manage payment methods, view invoices, or cancel — no separate billing page is needed.

What you pay for

  • Credits consumed on LLM calls — based on the model and token count. Project88 marks up a small percentage on top of the provider's wholesale rate to fund the platform.
  • Project88 plan — flat monthly subscription that includes a credit allowance. Pro plans get more credits per month.
  • Telnyx SMS — per-message rates billed directly to your Telnyx account.
  • Composio toolkits — Composio's own pricing applies for some toolkits.

Status pages

src/pages/status/ houses PaymentRequired, BillingSuccess, and related pages used during the upgrade and recovery flows. They're intentionally stand-alone routes that bypass the canvas shell so they work even when an org is over its limit.

Where to next

On this page