# Licensing & seats

> Per-seat pricing by billable role, signed license tokens, expiry, and revocation.

_Updated: 2026-06-07_

Paid plugins are licensed **per seat**, billed monthly or yearly. This page
explains what a seat is, what a license is, and how your instance enforces it.

## What counts as a seat

A seat is **a user who holds a *billable* role** in a plugin — not every user.

- **Billable** — operator- and approver-style roles that *do* the work
  (post a journal entry, approve a requisition, manage stock).
- **Free** — viewers, self-service users, and public/customer roles never consume
  a seat.

Each plugin **declares its own roles** and marks which are billable; the core
learns them when the plugin registers (see [How plugins work](how-plugins-work)).
`seats(plugin)` is simply the number of users with at least one billable role in
that plugin. A plugin's detail page on this site lists which roles are billable.

![A plugin detail page showing billable and free roles](/docs/ys-plugin-gl.png "Each plugin lists its billable (seat) roles and its free roles — here, General Ledger bills gl.accountant and gl.controller per seat, while gl.viewer is free.")

> [!NOTE]
> Seats are counted **per plugin**. A user who operates two plugins is a seat in
> each. This is why adding a plugin to an existing team is priced on that
> plugin's billable users, not your whole user base.

## What a license is

On payment, the vendor issues a short, signed **license token** bound to your
project. On the wire it looks like:

```text
lic1.<base64url(payload)>.<base64url(signature)>
```

The payload is deliberately tiny — `{ project, plugin, seats, roles, kid, iat, exp }`
— so it can be stored and passed around cheaply. Your instance verifies the
Ed25519 signature against the **pinned vendor public key** and enforces the seat
count and expiry locally. No call to the vendor is needed to *check* a license;
verification is offline and fast.

## Expiry, renewal, and grace

Your instance periodically re-checks the license with the vendor (a
*heartbeat*). This is how renewals, seat changes, and **revocations** propagate
without you doing anything:

- A renewed license rotates in automatically.
- A revoked or lapsed license gates the plugin **off** until it's restored —
  callers get a *402 Payment Required* with a hint pointing at the marketplace.
- A short grace window covers an in-flight renewal (e.g. a webhook still
  settling) so a payment retry over a weekend doesn't suspend production.

## Key rotation

The vendor can rotate its signing key; the token's `kid` (key id) records which
key signed it. New licenses are signed by the active key, and your instance
re-pins the current key over the heartbeat — so a rotation self-heals on the next
license refresh.

## Managing your subscription

Change seats, switch billing cycle, or cancel from **Manage billing** in either
the in-app marketplace or your [account](/account) on this site — both open the
vendor's secure billing portal. Details in [Managing billing](managing-billing).
