# License keys

> Issuing keys, domain binding, expiry, and what happens when a key is missing.

Section: Commercial. Source: https://ortexcharts.com/docs/licensing

## What a Key Is

A license key is a string in three dot-separated parts:

```text
OC1.<base64url payload>.<base64url signature>
```

The payload is JSON. The signature is Ed25519 over the payload string, made with ORTEX's
private signing key, whose public half is embedded in `@ortex-charts/core`. Keys cannot be
forged without that private key, which never leaves ORTEX's secret store.

```ts
interface LicensePayload {
  id: string;          // unique key id, for support and revocation
  customer: string;
  product: string;     // always "ortex-charts"
  tier: string;        // informational: "community", "pro", "business", "oem"
  features: string[];  // "whitelabel", "brand", "financial", "viz", "drawings", or "*"
  domains: string[];   // hostnames; "*.example.com" matches subdomains; "*" matches anything
  iat: number;         // issued at, Unix seconds
  exp: number;         // expiry, Unix seconds
}
```

## Installing One

Call `setLicenseKey` once, as early as you can, before any chart is created. Every check,
the signature included, runs before the call returns, so the state is settled by the next
line; the promise it returns is for the optional revocation list, and awaiting it is never
required. The signature is verified by the library's own Ed25519 implementation rather than
by the browser's WebCrypto, so nothing a page can replace or remove takes part in the check.

```ts
import { setLicenseKey } from "@ortex-charts/financial";

const status = await setLicenseKey(process.env.NEXT_PUBLIC_ORTEX_CHARTS_KEY!);
console.log(status.state, status.verified, status.message);
```

On the [hosted kit](https://ortexcharts.com/docs/install) this is already done: the kit file ends with a
`setLicenseKey` call carrying your key, so nothing in your page has to know about it.

### Where to Put the Key

The key is a public artifact. It ships to the browser, it is bound to your domains, and
anyone can read it out of your bundle. Treat it like a publishable API key rather than a
secret: an environment variable at build time is convenient and correct, and a secret store
is unnecessary.

```ts
void setLicenseKey(import.meta.env.VITE_ORTEX_CHARTS_KEY);   // Vite
void setLicenseKey(process.env.NEXT_PUBLIC_ORTEX_CHARTS_KEY!); // Next.js
```

What is secret is the **registry token** in your `.npmrc`, which grants access to the
package downloads. That one belongs in continuous-integration secrets and never in a commit.

## The States

```ts
import { licenseStatus, onLicenseChange, hasFeature, clearLicense } from "@ortex-charts/financial";

licenseStatus();               // { state, payload, message, verified }
hasFeature("whitelabel");      // true only on a valid license granting it
onLicenseChange((s) => console.log(s.state));
clearLicense();                // mostly for tests
```

| `state` | Meaning | Effect |
|---|---|---|
| `unlicensed` | No key has been set. | Gated: on localhost the chart draws with the [ORTEX mark](https://ortexcharts.com/docs/branding); on any other host it draws the licence notice instead of its content. |
| `pending` | Reserved; no longer occurs, since the signature is checked before `setLicenseKey` returns. | Treated as not valid. |
| `valid` | Everything checked out, signature included. | Features per the payload. |
| `invalid` | Malformed, wrong product, or a signature that failed. | Gated, as `unlicensed`. |
| `expired` | `exp` is in the past. | Gated, as `unlicensed`. |
| `domain` | The current hostname is not covered. | Gated, as `unlicensed`. |
| `revoked` | The key id is on a revocation list. | Gated, as `unlicensed`. |

`verified` reports that the Ed25519 signature was checked and passed. Since the check is the
library's own and runs on every platform, `valid` and `verified: true` now always go
together; there is no browser on which a payload is trusted unchecked.

### The Gate

Since 1.1.0 the licence is a gate. `licenseGate()` answers `{ blocked, reason }` for the
current state and hostname, and every chart asks it before it paints a frame:

- **On a development host** (`localhost`, `127.0.0.1`, `::1`, any `*.localhost`, or no
  hostname at all as in tests and server-side rendering) the gate is never closed. An
  unlicensed chart draws in full with the ORTEX mark on, so evaluation, unit tests and
  continuous integration need no key. The console stays quiet unless a chart asks to hide
  the mark, which writes one warning.
- **On any other host** a chart in any state but `valid` paints a notice on its
  own background, "ORTEX Charts. A licence key is required to display this chart", with the
  reason the check gave and the address of [/pricing](https://ortexcharts.com/pricing), and nothing else: no series,
  axes, legends, crosshair or tooltips. The moment a key verifies, every chart on the page
  repaints itself in full.

The gate decides from the key's payload, not from a remembered answer: on every frame it
checks the expiry against the clock and the domains against the hostname of the moment, and
it requires the signature to have been verified. A key that expires while the page is open
closes the gate at that second, and a key installed on one host does not carry to another.

```ts
import { licenseGate } from "@ortex-charts/financial";

licenseGate();                     // { blocked: false, reason: "" } on localhost
licenseGate("app.example.com");    // { blocked: true, reason: "No licence key set" } with no key
```

Treemaps, heatmaps and the other `@ortex-charts/viz` components paint the same notice.
Sparklines in `@ortex-charts/lite` are not gated.

## Domains

`domains` is a list of hostnames the key covers.

- An exact hostname matches itself: `app.example.com`.
- A wildcard matches the base and every subdomain: `*.example.com` covers `example.com`,
  `app.example.com` and `a.b.example.com`.
- `*` matches anything, and is what an OEM key usually carries.
- **`localhost` is always allowed**, along with `127.0.0.1`, `::1` and any `*.localhost`
  host, on every key. Development never fails a domain check.

```ts
import { domainAllowed } from "@ortex-charts/financial";

domainAllowed("app.example.com", ["*.example.com"]);   // true
domainAllowed("example.net", ["*.example.com"]);       // false
```

The check runs against `location.hostname`, at install and again on every frame, and there
is no way to tell the library it is somewhere else.

Preview deployments are the case that catches people out. A branch preview on
`pr-482.vercel.app` is not covered by `*.example.com`, so add the preview host as a wildcard
domain when the key is issued, or accept the licence notice on previews.

### Two Layers of Domain Enforcement

Hosted-kit customers are checked twice. The CloudFront function in front of the CDN compares
the request `Origin` or `Referer` against the domains registered on the kit and refuses to
serve the file to an unlisted host, and the key inside the file checks the hostname again in
the browser. Registry customers get the second check only.

## Expiry and Renewal

Keys are issued for the subscription term with a **14-day grace period** added on top, so a
renewal that is a few days late never flips a customer's chart to the mark. A renewal
re-signs onto the **same key id and the same kit id**, so your script tag and your key id do
not change from year to year — only the key string does, and hosted-kit customers do not
even see that.

When a key does expire, the state becomes `expired`, the gate closes and the chart paints the
licence notice instead of its content, naming the reason. There is no countdown and no
degradation before that date. The right to use the library ends with the term on every plan;
renewing issues a fresh key with no change to your code.

## Features

| Feature | What it grants |
|---|---|
| `whitelabel` | `branding.visible = false` is honored: the mark may be hidden. Every paid plan. |
| `brand` | The mark may be replaced with your own logo, text, caption and link. OEM agreements. |
| `financial` | Informational; the financial package is not gated. |
| `viz` | Informational; the viz package is not gated. |
| `drawings` | Informational; drawing tools are not gated. |
| `*` | Everything, including `whitelabel` and `brand`. |

Being precise about this: **only `whitelabel` and `brand` change behavior today.** The others
are recorded on the key so that the plan a customer is on is visible in the payload, and so
that a future gate has a name to use, but no feature check in the library disables a package.
A Pro key and an OEM key run the same code with the same capabilities; the mark is the
difference. What a key of any plan does is open the gate.

## Getting a Key

- **Pro and Business** keys are issued automatically. A purchase on ortexcharts.com signs a
  key, builds a hosted kit, registers your domains, issues a registry token and emails all
  of it within seconds of payment. There is no free key: evaluate on localhost and in the
  [playground](https://ortexcharts.com/playground), then buy with the sixty-day guarantee.
- **OEM and enterprise** keys are issued by hand from the same signer after a conversation.

Changing the domains on a key means re-issuing it, which happens automatically when the
domains on the subscription change; the kit is rebuilt at the same URL.

## Revocation

Every payload carries an `id`, and ORTEX can revoke a key before its expiry. What happens to
a running implementation depends on how the library reached it:

- **A hosted kit** stops being served within seconds: the kit's domains are removed from the
  edge store, so the next page load gets a 403 rather than the script. A page that already
  has the script loaded keeps its chart until reload; on reload there is no chart.
- **A registry install that passes `revocationList`** reads a small JSON document from the
  ORTEX CDN once after the key verifies. A revoked id found there drops the licence to
  `revoked`: the gate closes and the chart paints the licence notice instead of its content.
  The document is cached for a few minutes, so that is the delay.
- **A registry install that does not** keeps working until the key expires or the id ships
  in the library's built-in `REVOKED_KEY_IDS` in a later release, because the licence check
  makes no network call unless asked to.

```ts
setLicenseKey(key, { revocationList: "https://cdn.ortexcharts.com/v1/revoked.json" });
```

The list can only revoke, never grant, so an unreachable or malformed list changes nothing;
a chart never goes dark because ORTEX was unreachable. Hosted kits set the option
themselves. Whichever route, revocation and cancellation also revoke the registry token, so
`npm install` stops as well, and the licence itself ends at that moment: Section 7 of the
[licence](https://ortexcharts.com/legal/licence) applies.

## Testing Without a Key

On a development host the gate never closes, so evaluation on `localhost`, unit tests in
jsdom and continuous integration need no key: the chart draws in full with the ORTEX mark
on. `localhost` is always allowed, so local development with a real key never fails a domain
check either. The first real hostname is where a key is needed.

For tests that need a specific license state, install your real key and read the status
back; `clearLicense` returns the page to `unlicensed`:

```ts
import { clearLicense, setLicenseKey, licenseStatus } from "@ortex-charts/financial";

beforeEach(() => clearLicense());

it("hides the mark under a white-label key", async () => {
  await setLicenseKey(KEY);
  expect(licenseStatus().state).toBe("valid");
});
```

`setLicenseKey` takes no `publicKey`, `hostname` or `now` overrides. Earlier versions
accepted them for tests, and the first of them let a page trust a signing key of its own,
so they were withdrawn in 1.2.0. A key is verified against ORTEX's embedded signing key,
on the hostname the page is served from, at the time it is installed and again on every
frame.

## Related

- [The ORTEX mark](https://ortexcharts.com/docs/branding) — what showing and hiding it actually means.
- [Installation and delivery](https://ortexcharts.com/docs/install) — kits, registry tokens and source access.
- [Questions we are asked](https://ortexcharts.com/docs/faq) — expiry, telemetry and support.
