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 forstripe ensure-webhookif 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– Whentrue, the service ensures the webhook exists at startup usingpublic_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 theproductsarray.enable_automatic_taxes– Toggles the Stripe Automatic Tax integration for hosted checkout sessions. Enabled by default; set tofalseif 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_namemetadata – Checkout sessions and invoices must include agroup_namevalue (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:
| Field | Description |
|---|---|
slug | Unique identifier used by API calls and defaults (default_*_slug). |
name | Human-readable label shown in checkout pages and dashboards. |
description | Optional marketing copy surfaced in the public product metadata endpoint. |
type | subscription or one_off; determines whether Stripe creates recurring prices. |
pricing_mode | recurring (subscriptions) or one_time (single purchase). |
plan | Authlance plan name stamped onto issued licenses. |
product_key | Links the product to a signing key in security. |
active | Boolean flag that toggles availability without deleting the entry. |
currency | ISO 4217 currency for the Stripe price (e.g., usd). |
unit_amount | Amount in the smallest currency unit (cents, etc.). |
billing_interval | month, year, or one_time depending on type. |
billing_interval_count | Multiplier for recurring prices (e.g., 1 year, 12 months). |
term_duration_days | Applies to one_off products to record how long the license stays valid. |
lookup_key | Stripe lookup key used by hosted checkout sessions. |
stripe_product_id / stripe_price_id | Optional pre-existing Stripe identifiers. When omitted, the stripe sync-products job creates prices for you. |
base_amount | Seat-aware baseline used for tiered pricing math (mirrors unit_amount for fixed price SKUs). |
tax_behavior | Stripe tax behavior (inclusive, exclusive, etc.). Defaults to exclusive. |
internal | Marks SKUs that should stay hidden from public product listings (admin-only bundles, etc.). |
config_managed | When true (default), the Stripe sync task treats the config as the source of truth. Set to false if Stripe owns the lifecycle. |
term_years | Helper used when projecting non-subscription terms from term_duration_days (for non-subscription checkout quotas). |
metadata | Arbitrary key/value map (tier tags, UI hints) forwarded to Stripe objects. |
coupons | Array of overrides. Each coupon supports code, active, behavior, max_per_group, max_total, and optional price overrides. |
max_license_total | Global issuance cap for a SKU. When set, checkout + manual issuance refuse to exceed this number. |
max_managed_products | Seat cap embedded into the signed license and enforced during checkout. Pairs with the “Seat Enforcement” section. |
pricing_tiers | Array describing tiered pricing (see below). Requires pricing_mode tiered. |
bundled_products | Defines 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 intostripe_product_id/stripe_price_idin the config so future runs stay in sync without re-creating resources.Important: Leave
stripe_product_idandstripe_price_idempty 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 runlicenseoperator 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:
| Field | Description |
|---|---|
upper_bound | Highest seat count that tier applies to (null for unlimited). |
amount | Fixed amount (in cents) for the tier when per_unit is false. |
factor | Multiplier applied to base_amount when per_unit is true. |
per_unit | Boolean selecting per-seat math vs. flat amount. |
Bundled products. Use bundles to issue multiple product keys from one purchase:
| Field | Description |
|---|---|
product_key | Which signing key to invoke for the bundled component. |
plan | Plan stamped onto the bundled license. |
quantity | Number of component licenses to mint (defaults to 1). |
managed_products | Seat 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 tolicenseoperator.license.issuedif omitted.subject_stripe_webhook_event– Queue subject used to buffer raw Stripe webhook payloads. Defaults tosubject.stripe.webhook.event.
mysql
dsn– Standard MySQL DSN (must includeparseTime=true). The service creates/uses theissued_licensesandstripe_price_cachetables 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 prefersdelete_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 to0 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.
| Key | Description |
|---|---|
enabled | Boolean flag that turns the trial API on/off. |
duration_hours | Default length of each trial license (integer hours). Users cannot exceed this without a custom override. |
max_per_group | Optional limit on how many concurrent trials a single group may hold. |
cooldown_hours | Optional 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 as30mor1h).
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.