AI AdminPanel Documentation

Subscriptions

A subscription is the link between an owner (customer or organization) and the service plan that defines what they can do. Subscriptions are the panel's source of truth for billing-cycle state, distinct from operational state:

  • customers.status — operational (are this customer's services allowed to run?).
  • subscriptions.status — entitlement (is the plan they pay for currently valid?).

These are orthogonal on purpose. An active subscription on a suspended customer means "we took their money but shut them off"; a suspended subscription on an active customer means "their trial expired, cap them at read-only". The panel does not collapse the two.

Migrated from plan columns. Before v2.4 Step 5 the panel stored the plan directly on the owner (customers.plan_id, organizations.plan_id, organizations.trial_expires_at). Those columns were dropped and the data moved to a dedicated subscriptions table. The domain structs keep the transport-only planId fields so existing API consumers keep working; handlers hydrate them from the subscription repository on read.

Fields

FieldDescriptionRequired
customerId or organizationIdExactly one owner reference. Enforced by a DB check constraint.Yes (one of)
planIdThe plan the owner is subscribed to. Must match the owner's kind — customer-kind plans for customer owners, resource-kind for org owners.Yes
statusactive, suspended, or expired. Transitions are audited (subscription.suspend / .resume / .expire).Yes — defaults to active
expiresAtOptional ISO timestamp. Used by the trial-expiry worker to flip status.No
externalIdFree-form reference into your billing system — Stripe subscription id, Paddle id, keygen.sh license id, etc. The panel doesn't bill; it only stores the pointer.No
metadataFree-form JSONB. Use for any extra state your integration needs.No

Managing a subscription

UI. Each customer detail page and each organization edit modal has an inline Subscription section. It shows the current plan, status, expiry, and external reference. Admins with the subscriptions.manage permission get a Manage subscription button that opens a drawer to change the plan, flip status, edit expiry / external ID / metadata.

API. GET | POST | PUT | DELETE /api/v1/subscriptions[/:id]. Filter the list by customerId= or organizationId=. The handler enforces plan-kind pairing and records audit events on every status change.

Atomic creation. POST /customers and POST /organizations write the owner row and its subscription inside one pgx transaction. If the subscription create fails, the whole thing rolls back (for customers) or transitions the org to Terminated (for organizations). You cannot end up with an active owner that has no entitlement.

Permissions

Read access to subscriptions is granted implicitly by read access to the owner (customers or organizations). Mutations require the new subscriptions.manage permission, which is granted to the Admin and Reseller roles by default.

What subscriptions don't do (yet)

  • Billing. The panel never charges a card. externalId is the hand-off point to your billing system. External billing integration is on the v2.6 roadmap.
  • Add-ons. A subscription has exactly one plan. Optional upgrades / recurring one-offs are planned for v2.6.
  • Automatic enforcement of quota changes. If you change a subscription's plan to a smaller one and the owner is already over the new quota, the panel will not auto-scale them down. Over-quota warnings are planned for v2.6.
  • Dedicated list page. There is no /admin/subscriptions grid yet; the per-owner inline view is the canonical surface.