> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wyolet.com/llms.txt
> Use this file to discover all available pages before exploring further.

# License

> What a license enables, how to install one, and what happens when it expires

Relay is Apache-2.0, and routing, the catalog, key pooling, batch, usage tracking and payload logging all work without a license. A small set of features is gated behind a signed license file.

Verification is offline. The release public key is compiled into the binary; Relay never contacts a license server, and an airgapped deployment verifies a license the same way a connected one does.

## What is gated

| Feature | Without a license |
| - | - |
| `custom-roles`: creating or updating your own `Role` | The [built-in roles](https://docs.wyolet.com/concepts/auth.md#built-in-roles) only |

[OIDC sign-in](https://docs.wyolet.com/concepts/sso.md) and [SCIM provisioning](https://docs.wyolet.com/concepts/scim.md) are not gated. A license may carry other feature names; a name the running version does not gate enables nothing.

A gated action without a license is refused with `403` and the error `license_required`, through the API and through `apply`. Nothing else about the deployment changes.

## Installing a license

Three sources, in precedence order:

| Source | How |
| - | - |
| `RELAY_LICENSE_FILE` | Path to the license file; the usual choice for a Kubernetes Secret mounted as a file |
| `RELAY_LICENSE` | The license value inline |
| Control API | `PUT /api/license` with `{"value": "<license>"}` |

The environment wins over the stored value, so a deployment that sets either variable cannot be overridden from the UI. `PUT /api/license` verifies the value before storing it: a value that does not verify is refused with `400` and the previous license stays in effect. A stored license reaches every pod through the settings change notification, so installing or renewing one needs no restart. `POST /api/reload` verifies it again.

Read the license in effect with `GET /api/license`. Under `RELAY_AUTHZ=rbac` it needs `read` on `license`, which among the built-in roles only `admin` grants; under `single` any authenticated caller may read it. `GET /api/version` carries the same block for authenticated callers.

```json theme={null}
{
  "licensed": true,
  "customer": "acme",
  "expiresAt": "2027-01-01T00:00:00Z",
  "features": ["custom-roles"],
  "grace": false
}
```

## Expiry

An expired license keeps working for 30 days. Through that window `grace` reads `true` and gated features stay on, so a renewal gap does not interrupt a running gateway.

After the grace window the deployment runs as community: gated actions are refused with `license_required`, and a boot logs that the license is unusable. Custom roles that already exist keep their bindings working and can still be deleted. The deployment keeps booting and serving; a lapsed license never takes a gateway down.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.