Plan and Add-on Entitlements
Not every feature is on for every tenant. Core value movement (stablecoin payments, escrow, wallets) is always available, but capabilities like treasury, full compliance screening, and the tokenization module are add-ons that a tenant unlocks. A tenant's effective feature set is the union of what its plan includes and any overlay grants layered on top. When a request hits a feature it does not have, the API returns a single, predictable error so your integration can react. This page covers how entitlements resolve, how to read them, how to redeem an activation code, and the feature_not_enabled contract.
How Entitlements Resolve
A tenant's effective feature set is a union of two sources. There is no precedence to reason about: a feature is active if either source grants it.
| Source | What it is | How it changes |
|---|---|---|
| Plan baseline | The features included in the tenant's assigned plan. | Changes when the tenant's plan changes. |
| Overlay grants | Per-tenant add-on grants layered on top of the plan, from a redeemed activation code or a direct grant by XKOVA staff. | Changes when a code is redeemed, or a grant is added or revoked. |
An overlay grant that duplicates a plan feature is a no-op: the plan already grants it. Revoking an overlay grant only drops the add-on, not a feature the plan itself includes. To drop a plan feature, change the plan.
The Feature Catalog
Feature gating is binary and module-level. There is one feature key per top-level capability, not a read/write split. Either the tenant has the key, or it does not. The add-on keys are:
| Feature key | Gates |
|---|---|
treasury | The treasury surface: multi-signature wallets, signers, and N-of-M approved transfers. See Treasury. |
revenue_controls | Revenue and fee controls, including the revenue treasury link and fee schedules. See Fee Schedules. |
screening_full | The upper-tier compliance screening provider used during the pre-sign pipeline. See Screening and Sanctions. |
byo_oidc | Bring-your-own OIDC identity provider configuration for member sign-in. |
production_workspace | The operator-approval leg of live production access. The grant alone does not mint or enable live keys (see the note below). |
rwa_tokenization | The ERC-721 and ERC-1155 tokenization module and the entire real-world-asset metadata surface. See Real-World Assets. |
rwa_tokenization.production_workspace feature is one leg of live production access, not the
whole gate. Enabling live keys additionally requires an active commercial billing subscription and an approved
production-access review. Redeeming a code that grants production_workspace clears the operator
approval lever only; the other legs are checked at the point a live key is issued.Reading Effective Entitlements
Fetch the caller-tenant's plan and full feature catalog with a single read. The call is tenant-scoped: it reports the plan and, for every feature in the catalog, whether it is active for this tenant and how it became active. The inactive features are returned too, so a settings surface can show the add-ons a tenant has not unlocked.
The response carries a plan object (key and name) and a
features array. Each feature row reports its key, name,
description, category, an active boolean, a granted_via
provenance value, and a granted_at timestamp.
granted_via | Meaning | granted_at |
|---|---|---|
plan | Active because the tenant's plan includes it. | null (plan grants are implicit, with no per-tenant timestamp). |
activation_code | An overlay grant from a redeemed activation code. | ISO-8601 grant time. |
admin_grant | An overlay grant set directly by XKOVA staff. | ISO-8601 grant time. |
null | The feature is not active for this tenant. | null. |
This read is what the console uses to drive sidebar gating and the Plan and Add-ons settings section. See Settings and Branding for the operator view.
Redeeming an Activation Code
An add-on is unlocked by redeeming an activation code. Redemption is an admin staff action: it requires the
entitlement.redeem permission, a step-up MFA session (AAL2), and it is audited. The request body
carries only the plaintext code. XKOVA hashes the code before lookup and never logs the
plaintext.
A code references a named grant template, a fixed bundle of feature keys it unlocks. The templates are:
| Template | Unlocks |
|---|---|
all_addons | Every add-on feature. |
treasury_only | treasury. |
rwa_only | rwa_tokenization. |
prod_unlock | production_workspace. |
The response reports what changed. granted_features lists the keys this redemption newly
activated, already_active lists keys the tenant already held (nothing to do), and
redemption_id is the audit row id to quote in a support ticket.
Two idempotency properties matter here, and they are different:
- The redemption is idempotent at the feature-set layer. Re-granting a feature the tenant
already has is a no-op, reported in
already_activerather than raising an error. - The code itself is one-time-use across all tenants. Once a code has been redeemed, a
second redemption of that same code fails with
activation_code_already_redeemed. To grant the same features again, issue a fresh code.
Codes may be scoped to a single tenant. A scoped code presented by a different tenant is rejected with
activation_code_tenant_mismatch, kept distinct from the not-recognized case so support can tell
a mistyped code from one issued to someone else. Codes issued via the redeem endpoint cannot be created there;
code issuance is an operator function, not a public API surface.
The feature_not_enabled Contract
When a request reaches a surface gated by an add-on the tenant does not have, the API returns
403 feature_not_enabled. The check runs after authentication and before the handler, so the
request never touches the gated resource. The error body's details.feature carries the exact
feature key that was missing, so your integration (or the console) can render a targeted "redeem a code to
unlock X" prompt rather than a generic denial.
feature_not_enabled is not retryable. Retrying the same call changes nothing; the tenant must
first gain the feature (redeem a code, or have staff grant it). This is distinct from an authorization role
gap: a role failure is role_denied (you are not allowed to perform the action), while
feature_not_enabled means the capability is not turned on for the tenant at all.
The codes you may see across the read, redeem, and gated-call paths:
| Code | Status | When |
|---|---|---|
feature_not_enabled | 403 | The tenant lacks the entitlement a gated
surface requires. details.feature names the key. |
activation_code_invalid | 422 | The code did not match any known row (a hash lookup miss), or it references a grant template that no longer exists. |
activation_code_expired | 422 | The code carries an expiry in the past. |
activation_code_already_redeemed | 409 | The code was already redeemed; codes are one-time-use. |
activation_code_tenant_mismatch | 403 | The code was issued for a different tenant. |
role_denied | 403 | The caller is not an admin staff member, so cannot redeem. |
step_up_required | 403 | The admin caller does not hold a step-up MFA (AAL2) session. |
See the error catalog for the full envelope shape and correlation IDs.
Related
- Authorization and RBAC: how permissions and roles decide who may act, the layer above the entitlement check.
- Settings and Branding: the Plan and Add-ons view where operators read entitlements and redeem codes.
- Treasury, Real-World Assets, and Screening and Sanctions: surfaces that individual add-ons unlock.