License Operator Overview

The License Operator is the licensing and billing brain of the Authlance platform.
It manages license issuance, coordinates Stripe-powered purchases, publishes JetStream events for downstream provisioning, and exposes REST APIs suitable for storefronts, back-office dashboards, and automation scripts.

It ships with both a long-running server and a CLI for headless operations.


Core Capabilities

  • License issuance
    Generates signed RSA license envelopes for trials, manual grants, and subscription purchases. Licenses may be persisted or returned on demand.

  • Stripe automation
    Creates hosted checkout sessions, listens to checkout.session.completed and invoice.payment_succeeded events, and issues licenses based on Stripe metadata.
    Non-subscription flows are supported through coupons, quotas, and multi-year terms.

  • Product catalog
    Public endpoints (/authlance/license/product and /authlance/license/products) expose storefront-ready metadata including features, coupons, and pricing tiers.

  • Internal admin APIs
    Authenticated endpoints allow issuing trials, retrieving group licenses, exporting transaction history, verifying payments, and downloading stored artifacts.

  • Event emission
    Stripe-hosted purchases emit a licenseoperator.license.issued NATS JetStream message (triggered from the webhook handler) that carries the signed payload plus Stripe identifiers. Manual API or CLI issuance stays silent, so downstream consumers should only expect events for paid purchases.

  • CLI workflows
    The licenseoperator binary supports:

    • serve – run the server
    • license issue – direct/manual issuance
    • stripe ensure-webhook – create/update webhooks
    • stripe sync-products – seed Stripe with product/price definitions

Architecture at a Glance

[ Dashboard / Storefront ]
          |
          v
[ License Operator ]  <--->  [ Stripe API / Webhooks ]
          |
          v
[ MySQL ]      [ NATS JetStream ]
                      |
                      v
              [ Email / Provisioning ]

Typical Request Flows

1. Self-Service Purchase

  1. Dashboard calls
    POST /authlance/license/payments/api/v1/checkout-session
  2. Customer completes payment on Stripe’s hosted checkout
  3. Stripe fires webhook to
    POST /authlance/license/stripe/webhook
  4. The webhook handler:
    • Validates signature
    • Enforces group_name requirements
    • Issues the license
    • Persists it
    • Emits the licenseoperator.license.issued event (webhook issuances only)

2. Manual Issuance (Admin or Automation)

  • API: POST /authlance/license/internal/issue
  • CLI:
    licenseoperator license issue \
      --group acme \
      --email owner@acme.com \
      --plan alpha \
      --product-key auth \
      --expires 2026-01-01T00:00:00Z \
      --persist
    

3. Trial Management

POST /authlance/license/trial/{group}/issue:

  • Enforces one trial per group
  • Applies cooldown logic
  • Uses default trial length unless explicitly overridden

Operational Requirements

Configuration

All configuration lives in config/config.yaml and can be overridden by environment variables (LICENSEOPERATOR_*).

You must provide:

  • Server bind address
  • MySQL connection string
  • Stripe API key + webhook secret
  • At least one RSA signing key pair
  • NATS JetStream connectivity
  • JWT secret for internal API scopes

Secrets to manage

  • RSA private keys (PEM)
  • Stripe secret key & webhook secret
  • MySQL DSN
  • JWT secret for internal admin APIs

External Systems Required

  • Stripe (billing and webhooks)
  • MySQL 8+
  • NATS JetStream cluster

Command Reference

Run the server

licenseoperator serve --config /app/config/config.yaml

Issue a license

licenseoperator license issue \
  --group acme \
  --email owner@acme.com \
  --plan alpha \
  --product-key auth \
  --expires 2026-01-01T00:00:00Z \
  --persist

Ensure Stripe webhook

licenseoperator stripe ensure-webhook \
  --config /app/config/config.yaml \
  --public-url https://licenseoperator.example.com

Sync product catalog to Stripe

licenseoperator stripe sync-products --config /app/config/config.yaml

All commands accept --config and honor LICENSEOPERATOR_ environment variables.


Admin & Payments APIs

The License Operator exposes two protected API surfaces in addition to the public product catalog and Stripe webhook. Both require valid dashboard-issued JWTs (internal_auth configuration) and the server’s own license must be valid for the admin routes to respond.

Product & Coupon Administration

/authlance/license/admin/products (and nested routes) allow authenticated sysadmins to manage catalog metadata without editing configuration files:

  • GET /authlance/license/admin/products – paginated list with optional inclusion of inactive/internal SKUs (requires sysadmin role).
  • POST /authlance/license/admin/products – create a product definition, optionally with coupons or pricing tiers.
  • PUT /authlance/license/admin/products/{slug} – update product settings and seat caps.
  • GET /authlance/license/admin/products/seat-usage – report current seat/coupon consumption to reconcile limits.
  • /authlance/license/admin/products/{slug}/coupons – list/create/update/delete coupon overrides tied to a product.
  • /authlance/license/product-keys – enumerate configured signing keys to help dashboard flows map SKUs to keys.

These endpoints honor the same limits documented in the configuration section (for example max_managed_products or max_license_total) and will refuse mutations if the operator’s license guard reports an invalid status.

Payments & Reporting

Routes rooted at /authlance/license/payments power checkout orchestration and Stripe reporting:

  • POST /authlance/license/payments/product-details – public helper that returns Stripe price data by lookup key for storefronts.
  • POST /authlance/license/payments/api/v1/checkout-session – authenticated creation of hosted checkout sessions; enforces coupon rules, managed_products limits, and the configured default slugs.
  • POST /authlance/license/payments/api/v1/customer-portal – Spins up a Stripe billing-portal session for the authenticated customer.
  • POST /authlance/license/payments/api/v1/session-id – Exchanges a checkout session ID for the resulting subscription ID.
  • GET /authlance/license/payments/api/v1/reports/payments – Paginated payment history filtered by date range, product, or organization.
  • GET /authlance/license/payments/api/v1/reports/payments/export – CSV export of the same data.
  • GET /authlance/license/payments/api/v1/verify-payment/{licenseId} (and /licenseoperator/verify-payment/{licenseId}) – Verifies that a license has a matching Stripe payment record for audit purposes.

All routes under /api/v1 require the JWT scopes configured via internal_auth, and the reporting endpoints depend on the Stripe payment persistence described in the README.


License Limits and Seat Enforcement

For the license operator, a "seat" represents the number of active Stripe products a customer is entitled to manage.
Each synced, active Stripe product consumes exactly one seat.

How seats behave:

  • Disabling a product in Stripe frees a seat
  • Adding a product consumes a seat immediately
  • Purchases stamp the seat count onto the signed license

What the License Operator backend enforces

1. Checkout validation

  • Requires a non-negative seat count
  • Enforces product-level maxima via max_managed_products
  • Enforces coupon-level minima/maxima
  • Stores the final value in Stripe metadata (managed_products)

2. License issuance

  • Copies seat count into the signed payload (Seats)
  • Ensures all persisted records match what the customer purchased

3. Active-product enforcement (downstream — your software must handle this)

The License Operator does not enforce live seat usage.
It only validates limits at purchase time and embeds the purchased seat count into the signed license envelope.

Your application is responsible for enforcing seats at runtime.

This means that your product, dashboard, or custom storefront must:

  • Count how many of your own seats/features/modules are currently active for the customer
  • Compare that number with the seats value found inside the signed license
  • Prevent the customer from activating additional products/features once they reach that limit

If you do not implement this check, customers will be able to exceed their purchased entitlements.

In other words:

The License Operator guarantees correct licensing at purchase time;
your application must enforce those limits at runtime.

Runtime enforcement usually happens inside:

  • Your Auth container
  • Your feature modules
  • Your admin dashboard
  • Any provisioning or activation logic that turns on a product/feature for a tenant

4. Total license caps

Products with max_license_total enforce:

  • Issuance caps for one-off/perpetual SKUs
  • Prevention of overselling exclusive packages

Guarantees

With these rules:

  1. Customers cannot exceed the maximum seat count per SKU
  2. Coupons cannot grant unauthorized higher tiers
  3. Every signed license contains an immutable seat count
  4. Downstream services enforce entitlements consistently

Who Benefits from a License Operator License

  • Teams selling multiple Authlance-based SKUs who need unified billing + licensing
  • Revenue ops teams needing Stripe-driven entitlement enforcement
  • SaaS vendors entering marketplace/partner ecosystems where product caps matter
  • Early-stage platforms without an internal entitlement service but requiring cryptographically signed licenses

See Also