Skip to main content
CoreSDK
Reference

Licensing & Metering

How CoreSDK license tokens work, plan enforcement, usage metering, and air-gap-compatible key rotation.

Licensing & Metering

Phase 2 feature. Licensing and metering are not yet available. They ship in Phase 2. The API documented below reflects the planned design.

CoreSDK enforces plan limits and usage metering using PKI-signed license tokens (JWS, RS256/ES256). The engine verifies the token locally against an embedded public key — no outbound call is required at runtime. This means licensing works fully offline and in air-gapped environments.


How license tokens work

A license token is a JSON Web Signature (JWS) containing your plan entitlements:

{
  "iss": "https://licensing.coresdk.io",
  "sub": "org_acme",
  "iat": 1710000000,
  "exp": 1741536000,
  "plan": "team",
  "entitlements": {
    "max_tenants": 50,
    "max_rps": 5000,
    "features": ["feature_flags", "audit_trails", "saml", "scim"]
  }
}

The token is signed with CoreSDK's RS256 private key. The embedded public key in the engine binary verifies the signature locally on every startup — no network call is made. If the token is expired or the signature is invalid, the engine refuses to start.


Configuring the license token

license:
  token: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
CORESDK_LICENSE_TOKEN=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...

In SDK code:

use coresdk_engine::CoreSDK;

let sdk = Engine::from_env()?
    .license_token(std::env::var("CORESDK_LICENSE_TOKEN")?)
    .build()
    .await?;
import os
from coresdk import CoreSDKClient, SDKConfig

sdk = CoreSDKClient(SDKConfig(
    sidecar_addr="[::1]:50051",
    tenant_id=os.environ.get("CORESDK_TENANT_ID", "acme-corp"),
    fail_mode="closed",  # Phase 2: license enforcement
))
sdk, err := coresdk.New(coresdk.Config{
    LicenseToken: os.Getenv("CORESDK_LICENSE_TOKEN"),
})
const sdk = await CoreSDK.create({
  licenseToken: process.env.CORESDK_LICENSE_TOKEN!,
});

Plan enforcement

When a request would exceed a plan limit, CoreSDK rejects it with an RFC 9457 error before the handler runs:

{
  "type": "https://errors.coresdk.io/tenant/plan-limit",
  "title": "Plan Limit Exceeded",
  "status": 403,
  "detail": "Your plan allows 50 tenants. Current count: 50.",
  "trace_id": "01HX7KQMB4NWE9P6T2JS0RY3ZV"
}

Limits enforced at runtime:

LimitDescription
max_tenantsMaximum number of active tenants
max_rpsRequests per second (approximate; token bucket per sidecar)
featuresFeature gates — requests to disabled features return 403

Usage metering

CoreSDK emits usage counters as OTel metrics, exported via OTLP alongside your application metrics:

MetricTypeDescription
coresdk.license.requests_totalCounterAll requests, labelled by tenant and plan
coresdk.license.tenants_activeGaugeCurrently active tenant count
coresdk.license.feature_blocked_totalCounterRequests blocked by feature gate
coresdk.license.days_until_expiryGaugeDays until the license token expires

These metrics feed into your existing dashboards. Set an alert on coresdk.license.days_until_expiry < 30 to get advance warning before a token expires.


Token rotation

Tokens have an exp claim. Rotate before expiry:

  1. Download a new token from the CoreSDK dashboard (SaaS) or generate one with the CLI (self-hosted).
  2. Update CORESDK_LICENSE_TOKEN in your environment or secret store.
  3. The sidecar picks up the new token on the next config sync without a restart. Tokens can also be hot-reloaded by sending SIGHUP to the sidecar process.
# Verify the new token before deploying
core license verify --token "eyJhbGci..."

# Output:
# Plan:       team
# Org:        acme
# Expires:    2027-03-19 (365 days)
# Tenants:    50 max
# Features:   feature_flags, audit_trails, saml, scim
# Signature:  valid (RS256)

Air-gapped rotation

In environments with no outbound access, new tokens are delivered as signed files:

# On a machine with internet access:
core license download --org acme --output license.jwk

# Transfer license.jwk to the air-gapped host (USB, internal artifact store, etc.)

# On the air-gapped host:
core license install --file license.jwk
# Writes token to /etc/coresdk/license.token
# Sidecar picks up on next SIGHUP or restart

The token file is itself a JWS — the engine verifies the signature before accepting it. A tampered or invalid file is rejected with a startup error.


Checking license status

core license status

# Output:
# Plan:             team
# Status:           active
# Expires:          2027-03-19 (365 days)
# Tenants in use:   12 / 50
# RPS limit:        5000
# Features enabled: feature_flags, audit_trails, saml, scim

Programmatically:

let status = sdk.license().status().await?;
println!("Days until expiry: {}", status.days_until_expiry);
println!("Tenants: {}/{}", status.tenants_active, status.tenants_max);
status = await sdk.license.status()
print(f"Days until expiry: {status.days_until_expiry}")
print(f"Tenants: {status.tenants_active}/{status.tenants_max}")
status, err := sdk.License().Status(ctx)
fmt.Printf("Days until expiry: %d\n", status.DaysUntilExpiry)
fmt.Printf("Tenants: %d/%d\n", status.TenantsActive, status.TenantsMax)
const status = await sdk.license.status();
console.log(`Days until expiry: ${status.daysUntilExpiry}`);
console.log(`Tenants: ${status.tenantsActive}/${status.tenantsMax}`);

Rust API

The coresdk-license crate exposes the verification and plan-check APIs used internally by the engine.

use coresdk_license::{LicenseVerifier, LicenseToken, Plan};

// Build a verifier with the embedded RS256 public key
let verifier = LicenseVerifier::new();

// Parse and verify a JWS-encoded license token
// No outbound call is made — verification is fully offline
let token: LicenseToken = verifier.verify_jws(&raw_jws)?;

// Inspect the plan tier
match token.plan {
    Plan::Free       => { /* community limits apply */ }
    Plan::Team       => { /* up to 50 tenants, 5,000 RPS */ }
    Plan::Enterprise => { /* unlimited tenants, custom RPS, HSM support */ }
}

// Check a specific entitlement gate
if token.entitlements.features.contains("audit_trails") {
    // audit trail sink is permitted
}

LicenseVerifier::verify_jws() validates the RS256 signature, checks the exp claim, and returns a typed LicenseToken. It never makes a network call — the public key is embedded in the binary at compile time.

Plan tiers

TierPlan variantMax tenantsMax RPS
FreePlan::Free3500
TeamPlan::Team505,000
EnterprisePlan::Enterpriseunlimitedcustom

Next steps

On this page