License Operator Configuration

The binary reads a single YAML file (defaults to config/config.yaml, or /app/config/config.yaml inside the container). Every key can be overridden with environment variables by prefixing the path with LICENSEOPERATOR_ and upper‑casing nested keys (for example, LICENSEOPERATOR_STRIPE_API_KEY). This section shows a representative configuration followed by notes for each block.

server:
  addr: ":8082"
  public_base_url: "https://licenseoperator.example.com"

security:
  - product_key: "auth"
    private_key_pem: |
      -----BEGIN RSA PRIVATE KEY-----
      FAKEKEY-REPLACE-ME
      -----END RSA PRIVATE KEY-----
    public_key_pem: |
      -----BEGIN PUBLIC KEY-----
      FAKEKEY-REPLACE-ME
      -----END PUBLIC KEY-----

internal_auth:
  jwt_secret: "internal-jwt-secret"
  issuer: "licenseoperator"
  audience: "licenseoperator-dashboard"
  token_ttl: 5m

stripe:
  api_key: "sk_live_fake"
  webhook_secret: "whsec_fake"
  ensure_webhook: true
  root_app_url: "https://dashboard.example.com"
  success_base_url: "https://dashboard.example.com/billing/success"
  frontend_base_path: "/billing"
  portal_return_url: "https://dashboard.example.com/billing/portal"
  sync_products_on_startup: true
  default_subscription_slug: "alpha-subscription"
  default_one_off_slug: "alpha-one-off"
  products:
    - slug: "alpha-subscription"
      name: "Authlance Alpha Subscription"
      description: "Annual subscription for self-hosted Authlance."
      type: "subscription"
      pricing_mode: "recurring"
      plan: "alpha"
      product_key: "auth"
      active: true
      currency: "usd"
      unit_amount: 120000
      billing_interval: "year"
      billing_interval_count: 1
      lookup_key: "alpha-subscription"
      metadata:
        tier: "alpha"
    - slug: "alpha-one-off"
      name: "Authlance Alpha One-Off"
      description: "Single-purchase license valid for 12 months."
      type: "one_off"
      pricing_mode: "one_time"
      plan: "alpha"
      product_key: "auth"
      active: true
      currency: "usd"
      unit_amount: 90000
      billing_interval: "one_time"
      term_duration_days: 365
      lookup_key: "alpha-one-off"
      coupons:
        - code: "LAUNCH"
          active: true
          behavior: "product_override"
          max_per_group: 1
          max_total: 100
  non_subscription_checkout:
    enabled: true
    coupon_code: "ONEOFF"
    price_id: "price_FAKE_ID"
    product_id: "prod_FAKE_ID"
    plan: "alpha"
    term_years: 1
    max_licenses: 500
    max_per_group: 1

nats:
  url: "nats://nats:4222"
  subject_license_issued: "licenseoperator.license.issued"

mysql:
  dsn: "user:pass@tcp(mysql:3306)/licenseoperator?parseTime=true&loc=UTC"
  max_open_conns: 25
  max_idle_conns: 10

retention:
  keep_days: 30
  delete_only_trials: true
  schedule_cron: "0 3 * * *"

product_display:
  description: "Self-hosted Authlance license (alpha)."
  features:
    - "Docker Compose install"
    - "Dedicated Stripe portal"

license_policy:
  default_plan: "alpha"
  default_term_days: 365
  default_price_locked: true

trials:
  enabled: true
  duration_hours: 8

license:
  file_path: "/app/config/authlance.lic"
  expected_domain: "example.com"
  grace_days: 7
  refresh_interval: "30m"

Section reference

server

  • addr – Bind address for the HTTP API (format accepted by Go’s HTTP server, e.g., :8082).
  • public_base_url – Used for stripe ensure-webhook if you omit --public-url.

security

An array of RSA key pairs. The product_key in the request determines which private key signs the license payload. Add one entry per product line you support. At least one product key is required since it is how you identify the product for offline verification.

internal_auth

JWT settings for the internal API. Dashboard-issued tokens must match the secret, issuer, and audience configured here. token_ttl controls how long service-generated tokens remain valid (used by select CLI helpers).

stripe

A stripe product defines the purchasable items and pricing plans. The License Operator supports both subscription-based and one-off licenses, which you can mix in the same catalog. Each product must refer to a security product_key defined above so the issued license matches the purchased item.

  • api_key / webhook_secret – Standard Stripe credentials.
  • ensure_webhook – When true, the service ensures the webhook exists at startup using public_base_url.
  • root_app_url, success_base_url, frontend_base_path, portal_return_url – Used to build checkout links and billing portal redirects.
  • sync_products_on_startup – When enabled, seeds Stripe products/prices based on the products array.
  • enable_automatic_taxes – Toggles the Stripe Automatic Tax integration for hosted checkout sessions. Enabled by default; set to false if you handle taxes manually.
  • default_subscription_slug / default_one_off_slug – Fallback product slugs used by payments endpoints when a request omits the slug.
  • products – Declarative catalog describing subscription or one-off offers. Supported fields include descriptive text, lookup keys, metadata, pricing tiers, coupon definitions, and term duration (for one-off products).
  • group_name metadata – Checkout sessions and invoices must include a group_name value (or Stripe customer metadata must define it) for licenses to be issued. This requirement is always enforced to keep ownership tracking consistent.

Product definitions accept the following keys:

FieldDescription
slugUnique identifier used by API calls and defaults (default_*_slug).
nameHuman-readable label shown in checkout pages and dashboards.
descriptionOptional marketing copy surfaced in the public product metadata endpoint.
typesubscription or one_off; determines whether Stripe creates recurring prices.
pricing_moderecurring (subscriptions) or one_time (single purchase).
planAuthlance plan name stamped onto issued licenses.
product_keyLinks the product to a signing key in security.
activeBoolean flag that toggles availability without deleting the entry.
currencyISO 4217 currency for the Stripe price (e.g., usd).
unit_amountAmount in the smallest currency unit (cents, etc.).
billing_intervalmonth, year, or one_time depending on type.
billing_interval_countMultiplier for recurring prices (e.g., 1 year, 12 months).
term_duration_daysApplies to one_off products to record how long the license stays valid.
lookup_keyStripe lookup key used by hosted checkout sessions.
stripe_product_id / stripe_price_idOptional pre-existing Stripe identifiers. When omitted, the stripe sync-products job creates prices for you.
base_amountSeat-aware baseline used for tiered pricing math (mirrors unit_amount for fixed price SKUs).
tax_behaviorStripe tax behavior (inclusive, exclusive, etc.). Defaults to exclusive.
internalMarks SKUs that should stay hidden from public product listings (admin-only bundles, etc.).
config_managedWhen true (default), the Stripe sync task treats the config as the source of truth. Set to false if Stripe owns the lifecycle.
term_yearsHelper used when projecting non-subscription terms from term_duration_days (for non-subscription checkout quotas).
metadataArbitrary key/value map (tier tags, UI hints) forwarded to Stripe objects.
couponsArray of overrides. Each coupon supports code, active, behavior, max_per_group, max_total, and optional price overrides.
max_license_totalGlobal issuance cap for a SKU. When set, checkout + manual issuance refuse to exceed this number.
max_managed_productsSeat cap embedded into the signed license and enforced during checkout. Pairs with the “Seat Enforcement” section.
pricing_tiersArray describing tiered pricing (see below). Requires pricing_mode tiered.
bundled_productsDefines additional product keys/plans granted alongside the primary SKU (see below).

When you let the operator create the catalog (empty Stripe account, sync_products_on_startup: true), Stripe returns brand-new product and price IDs. Capture those IDs from the sync logs or the Stripe dashboard and copy them back into stripe_product_id / stripe_price_id in the config so future runs stay in sync without re-creating resources.

Important: Leave stripe_product_id and stripe_price_id empty until the products already exist in Stripe. Starting the operator with IDs that point to non-existent resources causes the process to fail fast during startup. The safe workflow is to run licenseoperator stripe sync-products --config /path/to/config.yaml (with the IDs absent), let Stripe create the catalog, and then paste the generated IDs back into the config before launching the operator again.

You can trigger the sync and view the IDs at any time via the CLI:

licenseoperator stripe sync-products --config /path/to/config.yaml

The command logs lines such as product registered slug=alpha stripe_product_id=prod_123 stripe_price_id=price_456, which you can paste back into the config file.

Products can be later managed via dashboard (API or UI), if you want to define them upfront.

Pricing tiers. Each entry supports:

FieldDescription
upper_boundHighest seat count that tier applies to (null for unlimited).
amountFixed amount (in cents) for the tier when per_unit is false.
factorMultiplier applied to base_amount when per_unit is true.
per_unitBoolean selecting per-seat math vs. flat amount.

Bundled products. Use bundles to issue multiple product keys from one purchase:

FieldDescription
product_keyWhich signing key to invoke for the bundled component.
planPlan stamped onto the bundled license.
quantityNumber of component licenses to mint (defaults to 1).
managed_productsSeat count embedded into the bundled license (handy when the component is tiered and its seats are predetermined).

Bundled components inherit Stripe metadata and participate in the seat enforcement guarantees described in the overview.

nats

  • url – Connection string for the JetStream cluster.
  • subject_license_issued – Subject used when publishing new licenses. Defaults to licenseoperator.license.issued if omitted.
  • subject_stripe_webhook_event – Queue subject used to buffer raw Stripe webhook payloads. Defaults to subject.stripe.webhook.event.

mysql

  • dsn – Standard MySQL DSN (must include parseTime=true). The service creates/uses the issued_licenses and stripe_price_cache tables defined in the migrations.
  • max_open_conns / max_idle_conns – Pool sizing knobs for high-traffic environments.

retention

How long issued licenses are kept in the database before being purged.

  • keep_days – Legacy flag retained for compatibility (current cleanup prefers delete_only_trials).
  • delete_only_trials – When true (default), the scheduled cleanup removes only expired trial licenses.
  • schedule_cron – Cron expression evaluated in UTC to trigger the cleanup job (defaults to 0 3 * * *).

product_display

Short marketing copy plus a shared feature list injected into the public product API responses.

license_policy

Defaults applied during issuance when callers omit fields:

  • default_plan – Plan name assigned to manual issues and Stripe purchases lacking explicit plan metadata.
  • default_term_days – Used when neither Stripe nor the request specifies a term.
  • default_price_locked – Whether licenses should be marked as price locked when the request doesn’t specify it.

trials

Configure the built-in trial issuance endpoints.

KeyDescription
enabledBoolean flag that turns the trial API on/off.
duration_hoursDefault length of each trial license (integer hours). Users cannot exceed this without a custom override.
max_per_groupOptional limit on how many concurrent trials a single group may hold.
cooldown_hoursOptional window that must elapse before the same requester/group can receive another trial.

Trial endpoints enforce cool-down windows automatically when the fields are present—no additional settings needed.

license

Controls the embedded license guard:

  • file_path – Location of the operator’s own license file.
  • expected_domain – Optional value used to ensure the license matches the current deployment.
  • grace_days – Number of days to remain operational after expiry.
  • refresh_interval – How frequently the guard reloads the file (duration string such as 30m or 1h).

Managing secrets and overrides

  • Keep the configuration file outside of version control and mount it into the container at /app/config/config.yaml.
  • Inject sensitive values (Stripe keys, RSA private keys, JWT secret, database password) via a secret manager or environment overrides. Example: LICENSEOPERATOR_MYSQL_DSN, LICENSEOPERATOR_STRIPE_API_KEY, LICENSEOPERATOR_SECURITY_0_PRIVATE_KEY_PEM.
  • Restart the service after updating the file; configuration is only loaded at startup.