Skip to content

Plans Catalog and Management

The Plans module exposes two route groups under /api/plans.

Public Catalog

  • GET /api/plans
  • GET /api/plans/:id

These routes are marked with @PublicRoute() and only return plans that are:

  • isPublic = true
  • isActive = true
  • not soft deleted

Public and management list queries sort recommended plans first, then fall back to creation order.

The public response omits internal management-only fields such as internalAlias, isPublic, isActive, and timestamps.

Management Routes

  • GET /api/plans/manage
  • GET /api/plans/manage/:id
  • POST /api/plans/manage
  • PUT /api/plans/manage/:id
  • DELETE /api/plans/manage/:id

These routes currently require @AuthUser(). They do not yet enforce a more specific backoffice role because the current API does not have a dedicated shared authorization pattern for that boundary.

Validation Rules

  • internalAlias must be lowercase and slug-like (letters, numbers, hyphens)
  • color must be a 6-digit hex string
  • prices must be non-negative numbers
  • subscription plans require a subscriptionConfig
  • one-time plans require subscriptionConfig = null
  • each resource limit requires quantity, limitType, and alertThreshold
  • quantity is stored in the resource's canonical unit — bytes for STORAGE, integer count for every other resource key; -1 means "unlimited"
  • alertThreshold is constrained to 0..100

Soft Delete Lifecycle

DELETE /api/plans/manage/:id performs a soft delete by setting deleted_at on the main plans.plans row.

Consequences:

  • deleted plans disappear from both public and management reads
  • pricing and resource-limit rows remain attached for historical consistency
  • future subscription records can still keep foreign-key references to retired plans without hard-deleting catalog history

Response Shape

The API response is section-oriented to match the product configuration UI:

  • top-level identity fields
  • pricing object for billing rules
  • resources array for limit configuration

Each resources entry is shaped as { resourceKey, quantity, limitType, alertThreshold }, and resource keys must be unique inside the array.

This keeps the public catalog stable while still reflecting the underlying split-table persistence model.