---
title: "Entitlements and features"
description: "Define product-plan features, enforce boolean and numeric entitlements safely and understand cached plan changes."
slug: "/docs/v1/security/entitlements-and-features"
section: "Security"
order: 17
audience: "all"
level: "advanced"
last_updated: "2026-10-18"
version: "v1"
related: ["/docs/v1/security/signed-responses","/docs/v1/security/threat-model"]
draft: false
---
Gate each [feature](/docs/v1/start/glossary#feature) on a valid [license](/docs/v1/start/glossary#license) result and the expected feature type, not on a plan name or truthy value alone.

## What you will learn
- Define plan features and read their signed values.
- Enforce numeric limits without coercion.
- Version feature names and handle changed plans.

## Prerequisites
- Complete [the quickstart](/docs/v1/start/quickstart).
- Understand [product plans](/docs/v1/start/core-concepts).
- Use the [guarded SDK configuration programs](/docs/v1/security/offline-mode#complete-sdk-configuration).

## Define product-plan features
1. Open Plans & features in your workspace.
2. Select the intended [product](/docs/v1/start/glossary#product) and create a product plan; the current screen has no general edit-plan form.
3. Add a stable feature key such as pdf_export to its comma-separated enabled feature keys.
4. Create the plan and issue a synthetic [Sandbox](/docs/v1/start/glossary#sandbox) license for that plan.
5. Activate or validate the license and inspect the verified result's features.
Expected result: the signed payload contains the plan's feature keys. A [platform subscription](/docs/v1/start/glossary#platform-subscription) such as Starter or Pro controls your KDPika workspace; it is not the product plan you sell to your buyers.

The server selects plan_features and joins license_features overrides for those same plan feature IDs. A configured value_json takes precedence over the boolean enabled value. A license-only override for a feature absent from the plan query does not automatically add a new response key. Empty features can serialize as [].

## Types and safe decisions
| Feature value | Intended application decision | Unsafe alternative |
| --- | --- | --- |
| pdf_export: true | Require a valid result and an actual boolean true | Unlock because the key exists |
| pdf_export: false | Keep export disabled | Convert any nonempty string to true |
| seat_count:5 | Require a nonnegative integer; compare requested seats against5 | Treat5 as an unlimited true flag |
| Missing/null | Use your documented restrictive default | Assume the highest plan includes it |
| String/object/list | Validate against your own feature schema | Accept arbitrary JSON as a numeric limit |

The backend supports typed JSON values stored for features; the current dashboard's plan editor primarily exposes boolean feature selection, and there is no documented public feature-management API in v1. Do not invent a numeric-value dashboard control or use unsupported SQL against the shared production database. For a numeric entitlement deployment, confirm your supported configuration workflow first.

## Read with each official SDK
The [complete configuration examples](/docs/v1/security/offline-mode#complete-sdk-configuration) are the single-source runnable examples for all four languages. They output safe summaries and never unlock a feature on an invalid result.

| SDK | Validity gate | Boolean / numeric access |
| --- | --- | --- |
| Python | result.valid | result.feature(name, False); result.feature_value(name, default); reject bool when expecting int |
| JavaScript | result.valid | result.feature(name, false); result.featureValue(name, default); Number.isSafeInteger for limits |
| PHP | result->valid | result->feature(name, false); result->featureValue(name, default); is_int for limits |
| C# | result.Valid | result.Feature(name, false); result.FeatureValue(name); JsonElement type and integer checks |

Current boolean helpers do not all share the same numeric truthiness behavior. For true boolean access, inspect an actual boolean feature; for counts, use the value helper and explicit type/range checks. The examples demonstrate pdf_export and a restrictive seat_count fallback. Missing seat_count returns 0, not an invented entitlement. Deactivation success is not an active license payload and must not unlock features.

## Version feature contracts
1. Write down the meaning, type and restrictive default of each key.
2. Keep existing keys stable while released clients depend on them.
3. Introduce a new key, such as export_format_v2, when the meaning or type changes.
4. Release clients that understand both versions under explicit rules.
5. Validate both old and new clients with representative signed payloads.
6. Remove the old feature only after your compatibility policy permits it.
Expected result: an old client never mistakes a new numeric/object value for unrestricted access. The platform does not implement an automatic feature-schema negotiation service.

## Changes to a customer's plan
The signed response is built from the license's current plan and its stored feature rows. An already cached response retains its old feature snapshot until online validation refreshes it or its time policy refuses it. If an approved supported configuration workflow changes those rows, affected licenses observe the change on their next successful online check. The current dashboard offers plan creation and state/archive actions, not a general feature/plan editor; do not infer a public edit workflow from the engine's database reads.

There is no general public v1 endpoint that changes a license's plan. Use only an actually supported dashboard workflow; do not present renew as a plan-change operation. Renewal changes expiry, not feature schema. Where a transfer is not supported, plan an explicitly authorized reissue/migration and manage old [activations](/docs/v1/start/glossary#activation); do not silently run database edits.

## Test your gates
1. Run each configuration sample against a license with pdf_export enabled.
2. Validate online and confirm valid=true and pdf_export=true.
3. Use a license with that feature disabled and confirm false.
4. Test an expired/refused result that still contains feature data; confirm false.
5. Test missing, negative, string and boolean seat_count values; confirm the restrictive0 fallback.
6. Test an integer limit 5 and refuse a sixth seat.
Expected result: validity and type checks both participate in the decision. Numeric server configuration remains a separate capability from demonstrating a safe reader.

## Expected result
Valid active results permit only correctly typed entitlements. Cached feature changes follow signed validation/grace deadlines.

## Common mistakes
- Gating on the display plan name.
- Calling a helper without checking valid.
- Coercing strings, booleans or null into numeric limits.

## Security notes
Feature data is not a substitute for server-side authorization of valuable remote actions. A patched local client can bypass local gates; keep important online operations behind your own authenticated backend.

## Troubleshooting
| Symptom | Cause | Action |
| --- | --- | --- |
| Feature change is not visible | Client is using an earlier signed cache | Validate online |
| Numeric feature unlocks everything | Boolean helper used for a count | Use an explicit integer comparison |
| Missing feature becomes enabled | Permissive default/coercion | Use a restrictive typed default |

## Next steps
Read [signed responses](/docs/v1/security/signed-responses) and [the threat model](/docs/v1/security/threat-model), then validate your own integration in Sandbox.

## Related
- [Verification examples](/docs/v1/security/verify-ed25519)
- [Offline mode](/docs/v1/security/offline-mode)
- [Security and disclosure](/security)
