---
title: "Licensing"
description: "Learn how to configure license.conf, activate an Enterprise Stack license, and keep the deployment in good standing."
stack: "Enterprise Stack (commercial) — version 1.1.1"
stack_version: "1.1.1"
stack_comparison: https://docs.walt.id/community-vs-enterprise.md
canonical_url: https://docs.walt.id/enterprise-stack/setup/licensing
generated: 2026-10-01
---
# Licensing

The walt.id Enterprise Stack is licensed software. A license from walt.id is required to run it.

This page shows you how to get a license, configure activation, start the stack, and stay in good standing. Field-level reference for `license.conf` is in [License configuration](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/license.md).

**Note:**

The production Enterprise API image trusts only walt.id's compiled-in license issuer. You cannot point it at a customer-controlled license service.

## Getting a License

If you are not a customer yet, [contact walt.id](https://walt.id/contact).

Once you are a customer, walt.id provides:

- Access to the private Enterprise Stack images
- A **license** for each environment you run

Licenses are issued as **development** or **production**:

- **Development** — local setup, evaluation, and non-production environments. Requires `dev-mode` in `_features.conf`.
- **Production** — live deployments. `dev-mode` must be disabled.

Your agreement also determines which capabilities are included. Features outside your license are not available, even if they are enabled in `_features.conf`.

walt.id issues the license as one of:

- An **online OpenID credential offer** (`openid-credential-offer://...`) for connected environments
- An **offline `.waltlicense` bundle** plus a matching installation key for air-gapped environments
- A **raw signed license credential** (SD-JWT VC) for controlled migration or recovery

## Prerequisites

Before you activate, ensure you have:

- A license from walt.id for this environment
- MongoDB available to the Enterprise API (license state is stored there)
- `config/license.conf` mounted with the rest of the [configuration files](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/overview.md)
- `LICENSE_STATE_ENCRYPTION_KEY` set on every replica (at least 32 characters)
- For **online** licenses: outbound access from the Enterprise API to walt.id's license service (`licenseServerUrl`, typically `https://license.walt.id`)
- For **development** licenses: `dev-mode` enabled in `_features.conf`
- For **production** licenses: `dev-mode` **not** enabled

**Error:**

Share one encryption key across every replica of a deployment and back it up with MongoDB. A missing, too-short, or rotated key fails startup or makes stored license state unreadable. Live dual-key rotation is not supported.

## Activate the License

Put `license.conf` in the API `config` directory (the [enterprise quickstart](https://github.com/walt-id/waltid-enterprise-quickstart) already includes this file). Set `licenseServerUrl` to the URL walt.id gives you, then use **exactly one** seed option.

**Option: Online**

### Online (connected) activation

1. Ask walt.id for an online license for this environment.
2. Copy the returned OpenID credential offer.
3. In `license.conf`, set the license service URL and uncomment `seedCredential`:

```hocon
licenseServerUrl = "https://license.walt.id"
seedCredential = "openid-credential-offer://..."
```

4. Set `LICENSE_STATE_ENCRYPTION_KEY` on the process (see [License configuration](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/license.md)).
5. Start the Enterprise API. On first startup the stack redeems the offer, binds the credential to this installation, and stores both the credential and the installation key in MongoDB.

The offer is single-use. If activation fails after the offer was consumed, request a **fresh** offer from walt.id. Do not paste the same offer into another deployment.

**Option: Offline**

### Offline or air-gapped activation

1. Follow the offline issuance process walt.id gives you. That produces a `.waltlicense` bundle bound to an installation key that **stays in your environment**.
2. Mount both files into the API container (typical paths below).
3. In `license.conf`, uncomment **both** file fields and leave `seedCredential` unset:

```hocon
licenseServerUrl = "https://license.walt.id"
seedCredentialFile = "/license/offline-license.waltlicense"
installationKeyFile = "/license/installation-key.json"
```

4. Set `LICENSE_STATE_ENCRYPTION_KEY` on every replica.
5. Start the Enterprise API. The stack loads the bundle, verifies it is an offline license bound to that installation key, and persists the state in MongoDB.

**Error:**

  Never send `installation-key.json` to walt.id or to the license server. If the configured key does not match the fleet key already stored in MongoDB, startup fails.

**Option: Raw credential**

### Raw credential (migration or recovery)

Use this only when walt.id (or a previous activation) has already given you a signed license SD-JWT VC.

```hocon
licenseServerUrl = "https://license.walt.id"
seedCredential = "eyJ..."
```

Do not set `seedCredentialFile` at the same time. After a successful start, MongoDB holds the bound credential and later restarts do not need the raw string in config.

### Docker Compose (quickstart)

1. Edit `config/license.conf` with the seed option above.
2. Pass the encryption key into the API service:

```yaml
waltid-enterprise:
  environment:
    - LICENSE_STATE_ENCRYPTION_KEY={your-32-plus-character-secret}
```

3. Start the stack as in [Local Development](https://docs.walt.id/enterprise-stack/setup/local-development.md).

### Docker

```bash
docker run -p 3000:3000 \
  -v $(pwd)/config:/config \
  -e LICENSE_STATE_ENCRYPTION_KEY="{your-32-plus-character-secret}" \
  waltid/waltid-enterprise-api
```

### Kubernetes

Mount `license.conf` with the other config files. Store the encryption key (and optionally the online offer) in a Secret named `waltid-enterprise-license`. See [License configuration](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/license.md) and [Kubernetes Deployment](https://docs.walt.id/enterprise-stack/setup/deployment/kubernetes-deployment.md).

## Confirm Activation

After the API is up, sign in as a [super admin](https://docs.walt.id/enterprise-stack/administration/access-and-permissions/super-admin/authenticate.md) and call the license status endpoint.

**Note:**

**Local development:** the API is available at `http://enterprise.localhost:3000`. Use your deployment host in production.

**Option: CURL**

**Endpoint:** `GET /license/status`

##### Example Request

```bash
curl -X 'GET' \
  'http://enterprise.localhost:3000/license/status' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer {yourToken}'
```

**Header Parameters**

- **Authorization**: _String_ (required) - Bearer token for a super admin session. Format: `Bearer {yourToken}`.

---

##### Example Response

```json
{
  "restriction": "ACTIVE",
  "licenseId": "lic_abc123",
  "organizationId": "org_abc123",
  "licenseType": "prod",
  "offline": false,
  "features": ["*"],
  "usageLimits": {},
  "rateLimitPerHour": null,
  "disabledFeatures": [],
  "haltOnStartup": false,
  "expiresAt": 1893456000,
  "expiresInSeconds": 7776000,
  "expiringSoon": false,
  "warningMessage": null
}
```

**Response Fields:**

- **restriction**: _String_ - Operational state. `ACTIVE` means the stack is licensed. `GRACE` means an online license has expired but the stack is still serving traffic while it tries to renew. `RESTRICTED` and `UNLICENSED` block new operations.
- **licenseId**: _String_ - Identifier of the bound license.
- **organizationId**: _String_ - Organization the license was issued to.
- **licenseType**: _String_ - `dev` or `prod`.
- **offline**: _Boolean_ - `true` when this is an air-gapped license.
- **warningMessage**: _String_ - Present when the license is missing, restricted, in grace, or close to expiry.

🎉 You've activated the Enterprise Stack license when `restriction` is `ACTIVE`.

Related super-admin endpoints:

| Method | Path | Purpose |
|---|---|---|
| `GET` | `/license/status` | Current restriction, expiry, and warnings |
| `GET` | `/license/usage` | Unsigned usage snapshot for monitoring |
| `GET` | `/license/usage-report` | Signed usage report (SD-JWT VC) |
| `POST` | `/license/usage-report/export` | Signed bundle for offline delivery to walt.id |

## After Activation

The stack stores the bound credential and installation key in MongoDB, encrypted with `LICENSE_STATE_ENCRYPTION_KEY`.

- Later restarts reuse that persisted state. You do not redeem the online offer again.
- All replicas of one deployment must share the same MongoDB and the same encryption key.
- Do not scale the replica count above the licensed fleet size. Extra pods stay alive for `/livez` and license status, but other routes return `503`.
- A newer mounted offline `.waltlicense` bundle (later `issuedAt`) is treated as an explicit renewal.

## Staying Licensed

Connected deployments periodically check in with walt.id. That check confirms the license is still valid, reports usage against your agreement, and lets walt.id disable a license or individual features if the contract requires it. Online heartbeats are unsigned OpenID4VP with `redirect_uri:<response_uri>` (the license server omits Verifier2 `clientId`).

Allow outbound access from the Enterprise API to `licenseServerUrl`.

If the stack cannot reach walt.id for a short time, it keeps running while the cached credential is still valid or within grace (currently 24 hours after expiry for online licenses). After a longer outage, new operations are blocked until connectivity is restored or the license is renewed. Work already in progress is allowed to finish.

walt.id can also remotely restrict a license. When that happens, the API returns `503` with `Enterprise API is unavailable because the license is not active.` except for:

- `GET /livez`
- Login and logout
- `GET /license/status`
- `POST /license/usage-report/export`

## Offline and Air-Gapped Environments

If your environment cannot reach walt.id, an **offline license** can be issued instead.

- The stack does not need ongoing connectivity to stay licensed
- Usage is still recorded locally
- You send a signed usage report to walt.id on the schedule they give you (`POST /license/usage-report/export`)
- As usage approaches the contractual limits in the license, the stack tightens automatically (rate limits, then some features, then a block on new billable operations)

If an offline license expires or its usage allowance is exhausted, new operations are blocked until walt.id issues a renewal. Mount the new bundle and restart (or roll) the API so the newer `issuedAt` is picked up.

## Troubleshooting

| Symptom | What to check |
|---|---|
| Startup fails with a blank seed | Set exactly one of `seedCredential` or `seedCredentialFile` on first start. |
| Startup fails because both seed options are set | Comment out one of them. |
| `LICENSE_STATE_ENCRYPTION_KEY is required` / key too short | Set a value of at least 32 characters on every replica. |
| `License state decryption failed` | The encryption key does not match the key that wrote MongoDB state. Restore the original key; do not rotate it in place. |
| Offer redemption fails / "already consumed" | Request a fresh online offer from walt.id. |
| `A DEV license requires dev-mode` | Add `dev-mode` to `enabledFeatures` in `_features.conf`, or use a production license. |
| `A PROD license cannot run with dev-mode enabled` | Remove `dev-mode` from `_features.conf`. |
| `503` on API routes | Call `GET /license/status`. If `RESTRICTED` or `UNLICENSED`, contact walt.id. Also confirm replica count is within the licensed fleet size. |
| Feature returns `403` even though it is enabled in `_features.conf` | The active license must also allow that feature. walt.id can disable features remotely. |
| Offline startup rejects the file | Confirm the file is an offline license, the `.waltlicense` bundle checksum is intact, and `installationKeyFile` matches the key the bundle was bound to. |

If the stack reports that it is unlicensed or restricted and the table above does not resolve it, contact walt.id.

## What You Should Do

1. Get a license from walt.id ([contact us](https://walt.id/contact)).
2. Configure `license.conf` and `LICENSE_STATE_ENCRYPTION_KEY` for each deployment.
3. Start the API and confirm `GET /license/status` returns `ACTIVE`.
4. For connected deployments, allow outbound access to walt.id's license service.
5. For air-gapped deployments, export usage reports on the schedule walt.id gives you.

## Next Steps

- [License configuration](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/license.md) — `license.conf` fields and the encryption key
- [Local Development](https://docs.walt.id/enterprise-stack/setup/local-development.md) — run the stack locally
- [Kubernetes Deployment](https://docs.walt.id/enterprise-stack/setup/deployment/kubernetes-deployment.md) — production-style deploy
- [Features](https://docs.walt.id/enterprise-stack/setup/configurations/config-files/features.md) — `_features.conf` and `dev-mode`
