Licensing
Capabilities, license tiers, and the license service
This page is the runtime reference for how a license shapes Doconut's behavior: what each license state allows, how capabilities are granted, and how to inspect all of it from code. (For loading a license, see Getting Started → License Setup.)
License states and what they do
| State | Opens documents? | Watermark | Capabilities |
|---|---|---|---|
| Full (paid) license (within its subscription/update window) | Yes | None | The ones purchased (Has* flags) |
| Temporary license (Developer/NFR — the evaluation license, before its expiry date) | Yes | None | All — blanket grant (the eval funnel) |
| Expired (a temporary license past its expiry date, or a paid subscription past its renewal date) | Yes | Evaluation watermark | None granted |
| No license file at all | Yes | Evaluation watermark | None granted |
| Found but rejected (invalid signature, tampered, blacklisted, or a build newer than the license's version window) | No — LicenseException on every open | — | — |
Two consequences worth internalizing:
- A missing license is not a temporary license. With no license file, pages render watermarked and no capability (search, annotation, plugins) is active. The full-featured, unwatermarked experience requires an actual Temporary license (get one).
- Only a rejected license blocks opening. Calendar/subscription expiry degrades to watermarked rendering; but deploying a build newer than the license's version window is rejected and fails fast with a
LicenseException, so you catch it at deploy — not by silently shipping watermarked output. That exception (fromOpenDocumentAsync) carries the reason fromLicense.RejectionMessage.
Capabilities
Doconut gates optional functionality by capability. Today those are the opt-in Converter and DICOM plugins plus the built-in Search and Annotation features, each granted by your license. The base viewer is not a capability — it is the prerequisite, surfaced as IsViewerLicensed:
bool viewerLicensed = lic.IsViewerLicensed; // non-TRIAL license present AND covering this build's versionThe single gate the whole pipeline uses is IsCapabilityGranted:
bool searchGranted = lic.IsCapabilityGranted(LicenseCapability.Search);Its logic, verbatim from the implementation: an active (non-expired) Temporary license grants every capability — including capabilities that don't have a dedicated purchase flag yet; a real purchased license grants exactly its Has* flags and is default-deny for anything unmapped. (A legacy TRIAL-type file is not an entitlement — it is treated as unlicensed: watermarked, nothing granted.)
Temporary licenses
Normal Search and Annotation use their existing capabilities directly.
Temporary (Developer/NFR) licenses are marked by an ExpiryNote tag and carry a hard expiry date:
| Member | Meaning |
|---|---|
IsTemporary | An ExpiryNote tag is present (detection is by tag presence — never match the note's text, it's dynamic) |
ExpiryNote | Informational text embedded in the license |
LicenseExpiry | The hard expiry date (DateTime?) |
Expiry semantics differ by tier: a temporary license expires on its LicenseExpiry date only (its subscription window is ignored; a temporary license missing the date is treated as expired, fail-safe). Every other license expires on its subscription/update-window date.
IDoconutLicenseService reference
Inject IDoconutLicenseService (namespace Doconut.Core.Interfaces; registered as a singleton — the license is parsed once at startup):
| Member | Meaning |
|---|---|
License | Full DoconutLicenseInfo snapshot (below) |
IsTrial | A legacy TRIAL-type file (treated as unlicensed — not an entitlement) |
IsTemporary / ExpiryNote / LicenseExpiry | Temporary-license markers |
IsExpired | Expired per the tier rules above |
IsVersionValid | This build falls within the license's support window |
IsViewerLicensed | Non-TRIAL file found and version covered — base prerequisite |
IsCapabilityGranted(cap) | The single capability gate |
HasSearch HasAnnotation HasConverter HasDicom | Released per-capability purchase flags — use IsCapabilityGranted for the temporary-aware answer |
IsValidForDomain(host) | Host coverage; V4 licenses use the wildcard *.* |
LicenseRejectionReason | Non-empty ⇒ opening is blocked with this message |
DoconutLicenseInfo exposes the display-friendly snapshot for admin panels: Owner, Email, LicenseType, Domain, IsLicenseFileFound, IsVersionValid, IsTrial, IsExpired, the Has* flags, the temporary-license fields, and RejectionMessage.
// Feature-flag your UI from the same source of truth the SDK uses
app.MapGet("/api/features", (IDoconutLicenseService lic) => Results.Ok(new
{
viewer = lic.IsViewerLicensed || lic.IsTemporary,
search = lic.IsCapabilityGranted(LicenseCapability.Search),
annotation = lic.IsCapabilityGranted(LicenseCapability.Annotation),
conversion = lic.HasConverter,
dicom = lic.HasDicom,
}));Health check
AddDoconut() registers a "doconut" health check that reports license/expiry state through the standard ASP.NET Core mechanism:
app.MapHealthChecks("/health");Wire it into your monitoring and an expiring subscription becomes an alert instead of a support ticket.
Version upgrades
A license covers a version window. Updating the NuGet package to a build newer than that window makes the license invalid for this build: IsVersionValid turns false and opening is blocked with a LicenseException (the reason is in License.RejectionMessage) — deliberately fail-fast, so a stale or unlicensed build is caught at deploy rather than silently watermarking production output. Renew the subscription, replace the .lic, and restart the application so AddDoconut() loads the new license.
Was this page helpful?