Plan tiers, usage limits, per-tenant overrides, and enforcement in Sintesis.
Sintesis tracks resource consumption per tenant and can enforce configurable limits on storage, AI tokens, and WhatsApp messages. Plans are defined globally in subscription_plans and linked to tenants through tenant_subscriptions. Per-tenant overrides let super-admins customize limits without changing the global plan definition.
Plans are defined in the subscription_plans table:
CREATE TABLE public.subscription_plans ( plan_key text PRIMARY KEY, name text NOT NULL, description text, storage_limit_bytes bigint, -- NULL = unlimited ai_token_budget bigint, -- NULL = unlimited whatsapp_message_budget bigint, -- NULL = unlimited metadata jsonb DEFAULT '{}'::jsonb, created_at timestamptz NOT NULL DEFAULT now());
As of migration 0068, the limit columns on both seeded plans are set to NULL, meaning no hard limits are currently enforced at the plan tier level. Limits are only active when explicitly set through per-tenant overrides. This state was intentional while billing tiers are being wired up.
The fetchTenantPlan function in lib/subscription-plans.ts computes the effective limits for a tenant by combining plan-level values with per-tenant overrides. Override columns take precedence:
A null value for any limit field means unlimited. When fetchTenantPlan cannot find a subscription row for the tenant, it falls back to the global starter plan definition.
The function queries tenant_api_expenses filtered by tenant_id and the current period start/end dates. If no row exists yet (the tenant has had zero usage this period), it returns a zeroed snapshot.
Usage deltas are written atomically using the increment_tenant_api_usage Postgres function, which upserts the monthly row and checks limits in the same transaction:
The application calls this via incrementTenantUsage(supabase, tenantId, delta, limits) in lib/tenant-usage.ts. The limits argument is obtained from fetchTenantPlan so that per-tenant overrides are always respected.
Super-admins can set custom limits for a specific tenant by updating the override columns on tenant_subscriptions. Overrides apply on top of (and take precedence over) the plan-level values.
-- Give a tenant a 10 GB storage cap regardless of planUPDATE public.tenant_subscriptionsSET storage_limit_bytes_override = 10737418240WHERE tenant_id = '<tenant-id>';-- Remove the override (revert to plan-level limit)UPDATE public.tenant_subscriptionsSET storage_limit_bytes_override = NULLWHERE tenant_id = '<tenant-id>';
Set an override to NULL to remove it. The effective limit then falls back to the plan-level value, which is also NULL (unlimited) for the current seeded plans.
When a limit is non-null and the new cumulative value would exceed it, increment_tenant_api_usage raises a Postgres exception before committing the update:
Exception code
Description
storage_limit_exceeded
Storage bytes would exceed p_storage_limit
ai_limit_exceeded
AI tokens used would exceed p_ai_token_limit
whatsapp_limit_exceeded
WhatsApp messages would exceed p_whatsapp_limit
In lib/tenant-usage.ts, these exceptions are caught and re-thrown as JavaScript Error objects with the Postgres exception code attached as error.code. Calling code should catch these errors and surface an appropriate message to the user.
The admin expenses section (/admin/expenses and /admin/expenses/all) provides a view of usage and costs per tenant. Both routes are restricted to users with allowedRoles: ["admin"] in the route access configuration.
Current tenant
/admin/expenses shows the active tenant’s current period usage summary.
All tenants
/admin/expenses/all is a super-admin view listing usage across every tenant for comparison and billing review.