Platform
ScaiWave ScaiGrid ScaiCore ScaiBot ScaiDrive ScaiKey Models Tools & Services
Solutions
Organisations Developers Internet Service Providers Managed Service Providers AI-in-a-Box
Resources
Support Documentation Blog Downloads
Company
About Research Careers Investment Opportunities Contact
Log in

Service packs

A service pack is a curated bundle: one billable line that includes several (service, plan) pairs. Useful for "everything you need to evaluate ScaiLabs" (a trial bundle), regional add-on combos, or seat-priced enterprise bundles.

Data model#

carbon
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
service_packs
  slug, name, description, icon_url,
  billing_period (monthly | yearly | quarterly | one_time),
  base_price_cents, currency,
  discount_type (fixed_price | percentage),
  discount_percentage (optional, for percentage),
  trial_days, features (JSON),
  is_active, is_public, sort_order

service_pack_items
  pack_id, service_id, plan_id,
  override_price_cents (optional  null = inherit plan price),
  sort_order

pack_subscriptions   (per-tenant subscription rows for a pack)
  partner_id, tenant_id, pack_id,
  status, activated_at, trial_ends_at, cancelled_at, cancellation_reason

Pricing#

The pack's headline price is service_packs.base_price_cents. It is not the sum of constituent plan prices — packs typically apply a discount. The two discount_type values:

  • fixed_pricebase_price_cents IS the customer's monthly bill. The constituent plans are documentation, not the source of truth.
  • percentage — multiply the sum of constituent (or override) prices by (100 - discount_percentage) / 100. Use this when the bundle math should be transparent.

Per-item override_price_cents lets you tweak individual lines (e.g. "the ScaiVault plan is half-price in this bundle"). Setting it to null inherits the underlying plan's base_price_cents.

Subscription model#

When a tenant subscribes to a pack:

  1. A pack_subscriptions row is created.
  2. One subscriptions row is created per pack item, with pack_subscription_id pointing back.
  3. The child subscription rows share the pack's status and lifecycle.

When the pack is cancelled, the cancel cascades to every child subscription with pack_subscription_id = <pack_sub.id>.

Why both rows?#

The duplication isn't free, but it serves two real needs:

  1. Quota enforcement. Per-service quota checks (e.g. ScaiKey's "monthly active users") run against the subscriptions table joined to plans. If the only record of a tenant's access were the pack row, every quota check would have to JOIN through pack_items — slower and less indexable.
  2. Mixed subscriptions. A tenant can have both pack subscriptions AND standalone subscriptions to services NOT in any pack. The single subscriptions table makes this work without branching every query.

Events#

Pack lifecycle has its own event topics, separate from regular subscription events:

Topic Trigger
pack_subscription.activated.v1 Pack subscribe succeeds
pack_subscription.changed.v1 (Reserved for future — no live producer yet)
pack_subscription.cancelled.v1 Pack cancellation, immediate or scheduled

Child subscription rows do NOT independently emit subscription.activated.v1 / cancelled.v1 — subscribers see one event per pack action, with pack_includes[] enumerating the constituent services. This avoids double-counting on the CRM side.

Admin surface#

  • List packs: GET /admin/packs
  • Create pack: POST /admin/packs with items[]
  • Update pack: PUT /admin/packs/{id} (replaces items wholesale)
  • Deactivate pack: DELETE /admin/packs/{id} (soft — is_active=false)
  • Pack subscriptions across tenants: GET /admin/packs/subscriptions

The admin UI lives at /admin/packs. See Admin — service packs for the full request/response shapes.

Trial bundles#

The most common pattern: a trial-bundle pack that includes the trial plan of every service. €0 / 14-day trial / public. Tenants subscribing to it land on every service simultaneously at zero cost; when the 14 days lapse, the reaper flips each child subscription to cancelled (unless they upgraded individual services in the meantime).

Updated 2026-05-18 01:48:39 View source (.md) rev 2