License configuration
StoatFlow checks that a license key is present at startup (a missing key still fails fast), then runs the activating validation ~5 minutes after start on a background thread — and re-checks it on a periodic heartbeat. Until that first validation the license reports PENDING; the app serves traffic normally, and on a validation failure it shuts down gracefully. You give it two things: your license key and — for production — an environment label. This page covers every way to provide them, for local development and CI/CD.
The license key
Your key is the signed string from your onboarding email — it starts with key/. The runtime resolves it by looking for an explicit key first, then a key file — and within each, an environment variable beats a system property (which is what an application.yaml value becomes):
- An explicit key —
STOATFLOW_LICENSE_KEY(environment variable), else thestoatflow.license.keysystem property (whichapplication.yaml'sstoatflow.license.keyfeeds). - A key file —
STOATFLOW_LICENSE_FILE(environment variable), else thestoatflow.license.filesystem property (fed byapplication.yaml'sstoatflow.license.file), else the default~/.stoatflow/license.key.
So an explicit key always wins over a key file, regardless of where each came from.
application.yaml values are applied below environment variables and -D system properties — so an env var always wins over YAML. This lets you commit a YAML default and override it per environment without touching the file.Pick whichever fits — by source:
# Highest precedence. Good for containers, CI, and one-off local runs.
export STOATFLOW_LICENSE_KEY="key/...your key from the onboarding email..."
# Set once, every local app picks it up — no env var needed.
# Must be chmod 600 (owner-only) on Linux/macOS, or startup is refused.
mkdir -p ~/.stoatflow
printf '%s' 'key/...your key...' > ~/.stoatflow/license.key
chmod 600 ~/.stoatflow/license.key
# Runtime module only. Interpolate from an env var so the literal key
# never lands in source control.
stoatflow:
license:
key: ${STOATFLOW_LICENSE_KEY}
The environment label
The environment is a label for the deployment a license seat is bound to — e.g. prod, prod-eu-west, staging, dev. StoatFlow derives a machine fingerprint from it:
fingerprint = SHA-256(application-id || environment)
So the same application-id + the same environment is one seat; distinct environments are distinct seats. Set it with STOATFLOW_LICENSE_ENVIRONMENT, the stoatflow.license.environment system property, or stoatflow.license.environment in application.yaml.
- Local development (Trial / Developer tiers): if you don't set it, the environment defaults to
<your-username>-<hostname>— one seat per developer machine, no configuration needed. - Production: the Production tier requires an explicit environment — the engine refuses to run if it has to auto-generate one (the check fires at the deferred validation, ~5 minutes after start). Set it per deployment (
prod,prod-eu-west, …) so each gets its own node-locked seat.
Local development
The simplest local setup is the key file — write it once and every StoatFlow app you run locally is licensed, with no env vars and an auto-derived per-machine environment:
mkdir -p ~/.stoatflow
printf '%s' 'key/...your developer key...' > ~/.stoatflow/license.key
chmod 600 ~/.stoatflow/license.key
Prefer per-run configuration? Export the key (and optionally the environment) in your shell:
export STOATFLOW_LICENSE_KEY="key/...your developer key..."
# Optional — defaults to <username>-<hostname> if unset:
export STOATFLOW_LICENSE_ENVIRONMENT="dev-$(whoami)"
CI/CD
Use your CI/CD-tier key (it carries a higher machine cap suited to short-lived build agents). Store it as an encrypted secret — never in the repo.
On GitHub Actions, StoatFlow auto-detects the run and sets the environment to cicd-<run id> for you, so each run gets a fresh, ephemeral seat — you only need to provide the key:
# .github/workflows/your-workflow.yml
jobs:
integration-test:
runs-on: ubuntu-latest
env:
STOATFLOW_LICENSE_KEY: ${{ secrets.STOATFLOW_CI_LICENSE }}
# Optional — auto-derived as cicd-${GITHUB_RUN_ID} on GitHub Actions:
# STOATFLOW_LICENSE_ENVIRONMENT: cicd-${{ github.run_id }}
steps:
- uses: actions/checkout@v4
# ... build + run StoatFlow tests ...
On other CI systems (GitLab CI, Jenkins, …) there's no auto-detection — set the environment explicitly to something unique-per-run so concurrent builds don't contend for one seat:
export STOATFLOW_LICENSE_KEY="$STOATFLOW_CI_LICENSE"
export STOATFLOW_LICENSE_ENVIRONMENT="cicd-${CI_PIPELINE_ID:-$(date +%s)}"
Deferred validation & CI/testcontainers
The activating license validation — the call that registers a machine against your license — runs ~5 minutes after start(), not at startup. Startup only checks the key is present (offline, no network). The consequence for tests:
- A test instance (testcontainers, integration tests, CI jobs) that starts and stops within ~5 minutes makes zero license-server API calls and consumes zero machine activations — the usual start-app-per-test-class pattern is free, no matter how often CI runs.
- During that window the license status reports
PENDING: readiness is UP, the app processes normally, and thestoatflow.license.validgauge reads0until validation succeeds (see Metrics). - An instance that outlives the window validates and consumes one activation for its fingerprint. Long-running soak/perf tests therefore each take a seat — the CI/CD tier's higher machine cap covers this; seats from ended runs are reaped automatically by the license server.
All license settings
Every setting, with its environment variable, system property, and application.yaml key (under stoatflow.license). Environment variable wins over system property wins over YAML.
application.yaml (stoatflow.license.*) | Environment variable | System property | Default | Purpose |
|---|---|---|---|---|
key | STOATFLOW_LICENSE_KEY | stoatflow.license.key | — | The signed license key (key/…). |
file | STOATFLOW_LICENSE_FILE | stoatflow.license.file | ~/.stoatflow/license.key | Path to a file containing the key. |
environment | STOATFLOW_LICENSE_ENVIRONMENT | stoatflow.license.environment | <username>-<hostname> (auto) | Seat label; required for Production. |
cache-dir | STOATFLOW_LICENSE_CACHE_DIR | stoatflow.license.cache-dir | ~/.stoatflow/license-cache/ | Where the offline-grace cache is stored. |
verbose-banner | STOATFLOW_LICENSE_VERBOSE_BANNER | stoatflow.license.verbose-banner | false | Log a detailed license banner at startup. |
Troubleshooting
- Startup fails: license key missing. The presence check hard-fails fast at
start(). Check the key is set (precedence above). - App shuts down ~5 minutes after start: license invalid / expired / revoked. The deferred validation rejected the key — the log carries
[LICENSE] deferred validation failed (code=…). Check the key is current; renew via your onboarding contact. license key file has insecure permissions. The key file must be owner-only —chmod 600 ~/.stoatflow/license.key.- Production refuses to run without an environment. Set
STOATFLOW_LICENSE_ENVIRONMENT(orstoatflow.license.environment) explicitly — Production won't auto-generate one. The refusal fires at the deferred validation (~5 minutes after start), once the tier is known. - Brief Keygen outage. The runtime keeps serving from its offline cache for the grace window, then fails on the next check. Normal restarts within the window are fine.
- Lost your key or token? We re-issue rather than retrieve — reply to your onboarding email and we'll rotate it.
Next steps
- Architecture — how the engine runs your topology.
- Questions about your license tier or seats? Get in touch.