LicenseSeat 0.3.0
dotnet add package LicenseSeat --version 0.3.0
NuGet\Install-Package LicenseSeat -Version 0.3.0
<PackageReference Include="LicenseSeat" Version="0.3.0" />
<PackageVersion Include="LicenseSeat" Version="0.3.0" />
<PackageReference Include="LicenseSeat" />
paket add LicenseSeat --version 0.3.0
#r "nuget: LicenseSeat, 0.3.0"
#:package LicenseSeat@0.3.0
#addin nuget:?package=LicenseSeat&version=0.3.0
#tool nuget:?package=LicenseSeat&version=0.3.0
LicenseSeat C# SDK
Official C# SDK for the LicenseSeat licensing platform. Add license validation to your app in minutes.
Building a Unity game? We have a dedicated Unity SDK with full IL2CPP, WebGL, iOS, and Android support. No DLLs. Just install via Unity Package Manager and go!
Quick Start
Install:
dotnet add package LicenseSeat
Use:
using LicenseSeat;
var client = new LicenseSeatClient(new LicenseSeatClientOptions
{
ApiKey = "your-api-key",
ProductSlug = "your-product"
});
// Activate a license
var license = await client.ActivateAsync("XXXX-XXXX-XXXX-XXXX");
// Check entitlements
if (client.HasEntitlement("pro-features"))
{
// Enable pro features
}
That's it. You're licensed.
Features
| Feature | Description |
|---|---|
| License Activation | Activate, validate, and deactivate licenses |
| Entitlements | Feature gating with usage limits and expiration |
| Offline Mode | Ed25519 signature verification when offline |
| Auto-Validation | Background validation at configurable intervals |
| Events | React to license changes in real-time |
| DI Support | First-class ASP.NET Core integration |
Platform Support
| Platform | Package | Install |
|---|---|---|
| .NET (Console, ASP.NET, WPF, MAUI) | NuGet | dotnet add package LicenseSeat |
| Godot 4 | NuGet | dotnet add package LicenseSeat |
| Unity | UPM | See Unity section |
Installation
NuGet (.NET, Godot)
dotnet add package LicenseSeat
Requirements: .NET Standard 2.0+ (.NET Framework 4.6.1+, .NET Core 2.0+, .NET 5+)
Unity
Unity has a dedicated SDK with WebGL, iOS, and Android support. Don't use NuGet for Unity - use the Package Manager instead.
Option 1: Git URL (Recommended)
- Open Window → Package Manager
- Click + → Add package from git URL...
- Paste:
https://github.com/licenseseat/licenseseat-csharp.git?path=src/LicenseSeat.Unity
Option 2: manifest.json
Add to Packages/manifest.json:
{
"dependencies": {
"com.licenseseat.sdk": "https://github.com/licenseseat/licenseseat-csharp.git?path=src/LicenseSeat.Unity"
}
}
Option 3: OpenUPM
openupm add com.licenseseat.sdk
Pin to a version:
https://github.com/licenseseat/licenseseat-csharp.git?path=src/LicenseSeat.Unity#v0.2.0
Usage Examples
Basic Client
using LicenseSeat;
var client = new LicenseSeatClient(new LicenseSeatClientOptions
{
ApiKey = "your-api-key",
ProductSlug = "your-product"
});
// Activate a license (binds to this device)
var license = await client.ActivateAsync("LICENSE-KEY");
Console.WriteLine($"Activated: {license.Key}");
Console.WriteLine($"Status: {license.Status}");
Console.WriteLine($"Plan: {license.PlanKey}");
// Validate a license (check if it's valid without activating)
var result = await client.ValidateAsync("LICENSE-KEY");
if (result.Valid)
{
Console.WriteLine("License is valid!");
Console.WriteLine($"Active Seats: {result.License?.ActiveSeats}/{result.License?.SeatLimit}");
}
else
{
Console.WriteLine($"Invalid: {result.Code} - {result.Message}");
}
// Check entitlements
if (client.HasEntitlement("premium"))
{
// Unlock premium features
}
// Get current status
var status = client.GetStatus();
Console.WriteLine($"Status: {status.StatusType}");
// Deactivate when done (frees up a seat)
await client.DeactivateAsync();
Static API (Singleton)
Perfect for desktop apps where you want global access:
using LicenseSeat;
// Configure once at startup
LicenseSeat.LicenseSeat.Configure("your-api-key", "your-product", options =>
{
options.AutoValidateInterval = TimeSpan.FromHours(1);
});
// Use anywhere in your app
await LicenseSeat.LicenseSeat.Activate("LICENSE-KEY");
if (LicenseSeat.LicenseSeat.HasEntitlement("premium"))
{
// Premium features
}
var status = LicenseSeat.LicenseSeat.GetStatus();
var license = LicenseSeat.LicenseSeat.GetCurrentLicense();
// Cleanup on exit
LicenseSeat.LicenseSeat.Shutdown();
ASP.NET Core Dependency Injection
// Program.cs
builder.Services.AddLicenseSeatClient("your-api-key", "your-product");
// Or with full options:
builder.Services.AddLicenseSeatClient(options =>
{
options.ApiKey = "your-api-key";
options.ProductSlug = "your-product";
options.AutoValidateInterval = TimeSpan.FromMinutes(30);
});
// Your controller or service
public class LicenseController : ControllerBase
{
private readonly ILicenseSeatClient _client;
public LicenseController(ILicenseSeatClient client) => _client = client;
[HttpPost("activate")]
public async Task<IActionResult> Activate([FromBody] string licenseKey)
{
var license = await _client.ActivateAsync(licenseKey);
return Ok(new { license.Key, license.Status });
}
[HttpGet("status")]
public IActionResult GetStatus()
{
var status = _client.GetStatus();
return Ok(new { status.StatusType, status.Message });
}
}
Event Handling
// Subscribe to license events
client.Events.On(LicenseSeatEvents.LicenseValidated, _ =>
Console.WriteLine("License validated!"));
client.Events.On(LicenseSeatEvents.ValidationFailed, _ =>
Console.WriteLine("Validation failed!"));
client.Events.On(LicenseSeatEvents.EntitlementChanged, _ =>
Console.WriteLine("Entitlements updated!"));
client.Events.On(LicenseSeatEvents.LicenseActivated, license =>
Console.WriteLine($"Activated: {((License)license).Key}"));
client.Events.On(LicenseSeatEvents.LicenseDeactivated, _ =>
Console.WriteLine("License deactivated"));
Offline Validation
var client = new LicenseSeatClient(new LicenseSeatClientOptions
{
ApiKey = "your-api-key",
ProductSlug = "your-product",
OfflineFallbackMode = OfflineFallbackMode.NetworkOnly,
MaxOfflineDays = 7 // Allow 7 days offline
});
// Validate - falls back to cached offline token if network fails
var result = await client.ValidateAsync("LICENSE-KEY");
if (result.Offline)
{
Console.WriteLine("Validated offline with cached license");
}
The SDK automatically fetches and caches Ed25519-signed offline tokens after activation. When offline:
- Validates token signature cryptographically
- Checks token expiration (
exptimestamp) - Detects clock tampering
- Returns cached entitlements
Godot 4
using Godot;
using LicenseSeat;
public partial class LicenseManager : Node
{
private LicenseSeatClient _client;
public override void _Ready()
{
_client = new LicenseSeatClient(new LicenseSeatClientOptions
{
ApiKey = "your-api-key",
ProductSlug = "your-product"
});
}
public async void ValidateLicense(string licenseKey)
{
var result = await _client.ValidateAsync(licenseKey);
if (result.Valid)
GD.Print("License is valid!");
else
GD.Print($"Invalid: {result.Code}");
}
public override void _ExitTree() => _client?.Dispose();
}
Unity
using UnityEngine;
using LicenseSeat;
public class LicenseController : MonoBehaviour
{
private LicenseSeatManager _manager;
void Start()
{
_manager = FindObjectOfType<LicenseSeatManager>();
// Subscribe to events
_manager.Client.Events.On(LicenseSeatEvents.LicenseValidated, _ =>
Debug.Log("License validated!"));
}
public void ActivateLicense(string licenseKey)
{
StartCoroutine(_manager.ActivateCoroutine(licenseKey, (license, error) =>
{
if (error != null)
{
Debug.LogError($"Failed: {error.Message}");
return;
}
Debug.Log($"Activated: {license.Key}");
}));
}
}
Unity SDK Features:
- Pure C# - No native DLLs, works everywhere
- IL2CPP Ready - Automatic link.xml injection
- WebGL Support - Uses UnityWebRequest
- Editor Tools - Settings window, inspectors
- Samples - Import from Package Manager
Full Unity docs: src/LicenseSeat.Unity/README.md
Configuration
| Option | Default | Description |
|---|---|---|
ApiKey |
— | Your LicenseSeat API key (required) |
ProductSlug |
— | Your product identifier (required) |
ApiBaseUrl |
https://licenseseat.com/api/v1 |
API endpoint |
AutoValidateInterval |
1 hour | Background validation interval (0 = disabled) |
MaxRetries |
3 | Retry attempts for failed requests |
RetryDelay |
1 second | Base delay between retries |
OfflineFallbackMode |
Disabled |
Offline validation mode |
MaxOfflineDays |
0 | Offline grace period (0 = disabled) |
MaxClockSkew |
5 minutes | Clock tamper tolerance |
HttpTimeout |
30 seconds | Request timeout |
Debug |
false |
Enable debug logging |
API Reference
LicenseSeatClient Methods
| Method | Description |
|---|---|
ActivateAsync(licenseKey) |
Activate a license on this device |
ValidateAsync(licenseKey) |
Validate a license (check if valid) |
DeactivateAsync() |
Deactivate the current license |
HasEntitlement(key) |
Check if an entitlement is active |
CheckEntitlement(key) |
Get detailed entitlement status |
GetStatus() |
Get current license status |
GetCurrentLicense() |
Get the cached license |
TestAuthAsync() |
Test API connectivity |
ValidationResult Properties
| Property | Type | Description |
|---|---|---|
Valid |
bool |
Whether the license is valid |
Code |
string? |
Error code if invalid |
Message |
string? |
Error message if invalid |
Offline |
bool |
True if validated offline |
License |
License? |
License data |
ActiveEntitlements |
List<Entitlement>? |
Active entitlements |
Warnings |
List<ValidationWarning>? |
Any warnings |
License Properties
| Property | Type | Description |
|---|---|---|
Key |
string |
The license key |
Status |
string? |
License status (active, expired, etc.) |
ExpiresAt |
DateTimeOffset? |
When the license expires |
PlanKey |
string? |
Associated plan |
SeatLimit |
int? |
Maximum allowed seats |
ActiveSeats |
int |
Currently used seats |
ActiveEntitlements |
List<Entitlement>? |
Active entitlements |
Error Handling
try
{
var license = await client.ActivateAsync("INVALID-KEY");
}
catch (ApiException ex) when (ex.Code == "license_not_found")
{
Console.WriteLine("License key not found");
}
catch (ApiException ex) when (ex.Code == "seat_limit_exceeded")
{
Console.WriteLine($"All {ex.Details?["seat_limit"]} seats are in use");
}
catch (ApiException ex)
{
Console.WriteLine($"API Error: {ex.Code} - {ex.Message}");
Console.WriteLine($"Status: {ex.StatusCode}");
Console.WriteLine($"Retryable: {ex.IsRetryable}");
}
Common error codes:
license_not_found- Invalid license keylicense_expired- License has expiredlicense_suspended- License is suspendedseat_limit_exceeded- All seats are in usedevice_not_activated- Device not activated for this licenseinvalid_api_key- Invalid API key
Documentation
Full API documentation: licenseseat.com/docs
Development
Prerequisites
- .NET SDK 9.0+
Commands
# Build
dotnet build
# Test (unit tests)
dotnet test
# Test with coverage
dotnet test --collect:"XPlat Code Coverage"
# Package
dotnet pack --configuration Release --output ./artifacts
Testing
The SDK has two test suites:
Unit Tests
Unit tests run offline and test internal SDK logic:
dotnet test tests/LicenseSeat.Tests
Integration Tests (Stress Tests)
Integration tests run against the live LicenseSeat API and validate the complete SDK functionality:
dotnet run --project tests/StressTest
What's tested:
| Category | Tests |
|---|---|
| Client Operations | Create, authenticate, validate, activate, deactivate |
| Static API | Singleton pattern for desktop apps |
| Dependency Injection | ASP.NET Core integration |
| Error Handling | Invalid keys, wrong product, invalid API key |
| Stress Tests | Concurrent validations, parallel client creation |
| Offline Cryptography | Ed25519 signature verification, tamper detection |
| User Journey | 15 real-world customer scenarios |
User Journey Scenarios:
- First-time user activation
- Entitlement/feature gating
- Auto-validation in background
- Offline token caching
- Network outage handling
- Tampered token detection
- Clock tampering detection
- Expired token handling
- Device switching
- Seat limit enforcement
- Invalid license key handling
- Wrong product slug (security)
- Invalid API key (security)
- Event-driven UI updates
- Graceful shutdown
Running with your own credentials:
Set environment variables before running:
export LICENSESEAT_API_KEY="your-api-key"
export LICENSESEAT_PRODUCT_SLUG="your-product"
export LICENSESEAT_LICENSE_KEY="your-license-key"
dotnet run --project tests/StressTest
Note: Integration tests require a valid LicenseSeat account and license. Tests may fail if the license seat limit is reached.
Development Workflow
Before Pushing / Submitting a PR
Run these checks before pushing any changes:
# 1. Build and run unit tests
dotnet build
dotnet test
# 2. Ensure Unity SDK is in sync with main SDK
./scripts/validate-unity-sync.sh
# If out of sync, run:
./scripts/sync-unity-core.sh --replace-symlinks
# 3. Validate Unity package structure
./scripts/validate-unity-package.sh
Important: The Unity SDK shares core files with the main SDK. After modifying any files in
src/LicenseSeat/, you must sync them to the Unity package using the sync script.
Before Releasing
- Ensure all CI checks pass
- Run the full validation suite:
dotnet build --configuration Release dotnet test --configuration Release ./scripts/validate-unity-sync.sh ./scripts/validate-unity-package.sh - Update version numbers (see Release Steps below)
- Update CHANGELOG.md
Releasing
This repo contains two packages:
- NuGet:
LicenseSeatfor .NET/Godot - Unity:
com.licenseseat.sdkfor Unity via UPM
Release Steps
Update versions:
# src/LicenseSeat/LicenseSeat.csproj <Version>1.0.0</Version> # src/LicenseSeat.Unity/package.json "version": "1.0.0" # src/LicenseSeat.Unity/CHANGELOG.md ## [1.0.0] - YYYY-MM-DDValidate:
./scripts/validate-unity-sync.sh ./scripts/validate-unity-package.shTag and release:
git add -A && git commit -m "Release v1.0.0" git tag v1.0.0 && git push origin main v1.0.0 gh release create v1.0.0 --title "v1.0.0" --generate-notesAutomatic: CI publishes to NuGet. Unity is available via Git tag:
https://github.com/licenseseat/licenseseat-csharp.git?path=src/LicenseSeat.Unity#v1.0.0
OpenUPM (One-Time)
- Go to openupm.com/packages/add
- Submit:
https://github.com/licenseseat/licenseseat-csharp - OpenUPM auto-detects
src/LicenseSeat.Unity/package.json
After approval, new tags are automatically published.
NuGet Trusted Publishing
This repo uses NuGet Trusted Publishing (OIDC, no API keys).
Setup:
- Add
NUGET_USERvariable in repo settings - Create
nuget-publishenvironment - Configure trusted publishing policy on nuget.org
Contributing
- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing - Commit changes:
git commit -m 'Add amazing feature' - Push:
git push origin feature/amazing - Open a Pull Request
License
MIT - see LICENSE
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.2)
- Microsoft.Extensions.Options (>= 8.0.2)
- Portable.BouncyCastle (>= 1.9.0)
- System.Text.Json (>= 8.0.6)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.