License configuration

Configure your StoatFlow license key for local development and CI/CD — environment variables, system properties, key files, and application.yaml.

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):

  1. An explicit keySTOATFLOW_LICENSE_KEY (environment variable), else the stoatflow.license.key system property (which application.yaml's stoatflow.license.key feeds).
  2. A key fileSTOATFLOW_LICENSE_FILE (environment variable), else the stoatflow.license.file system property (fed by application.yaml's stoatflow.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..."

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 the stoatflow.license.valid gauge reads 0 until 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 variableSystem propertyDefaultPurpose
keySTOATFLOW_LICENSE_KEYstoatflow.license.keyThe signed license key (key/…).
fileSTOATFLOW_LICENSE_FILEstoatflow.license.file~/.stoatflow/license.keyPath to a file containing the key.
environmentSTOATFLOW_LICENSE_ENVIRONMENTstoatflow.license.environment<username>-<hostname> (auto)Seat label; required for Production.
cache-dirSTOATFLOW_LICENSE_CACHE_DIRstoatflow.license.cache-dir~/.stoatflow/license-cache/Where the offline-grace cache is stored.
verbose-bannerSTOATFLOW_LICENSE_VERBOSE_BANNERstoatflow.license.verbose-bannerfalseLog a detailed license banner at startup.
Heartbeat cadence and offline-grace are set by your license tier, not by configuration. They govern how quickly a license change takes effect and how long the engine tolerates a Keygen outage — so they're tuned per tier rather than left to each deployment. If your environment needs a longer offline-grace window (intermittent connectivity, restricted egress), talk to us and we'll set it on your license.

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 (or stoatflow.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