Skip to main content

Guides

Free Trials

Offer time-limited trial licenses that automatically expire and convert to paid plans.

Template used
This guide uses the trial policy template: 1 device, 14 days, no offline access, isTrial: true.

1. Issue a Trial License

When a new user signs up, issue a trial license automatically. The trial policy template limits usage to one device for 14 days with no offline access.

Python

create_trial.py
# Licenses are a management-plane resource, not exposed on the SDK client
# — create them via the REST API (Management API key, licenses:write scope).
import httpx

api_key = "lk_live_your_key_here"
headers = {"Authorization": f"Bearer {api_key}"}

trial_license = httpx.post(
    "https://www.licentric.com/api/v1/licenses",
    headers=headers,
    json={
        "productId": "YOUR_PRODUCT_ID",
        "policyId": "YOUR_TRIAL_POLICY_ID",
        "userEmail": "[email protected]",
        "metadata": {"source": "signup_form"},
    },
).json()["data"]

# The trial policy template sets:
#   maxMachines=1, durationDays=14,
#   isTrial=true, offlineAllowed=false

TypeScript

create-trial.ts
const trialLicense = (
  await (
    await fetch("https://www.licentric.com/api/v1/licenses", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${apiKey}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        productId: "YOUR_PRODUCT_ID",
        policyId: "YOUR_TRIAL_POLICY_ID",
        userEmail: "[email protected]",
        metadata: { source: "signup_form" },
      }),
    })
  ).json()
).data;

2. Automatic Expiration

Trial licenses expire automatically after the configured duration (14 days by default). No cron jobs or manual intervention required. When a trial expires, validation returns the EXPIRED code.

3. Check Trial Status

Show users how many days remain in their trial and prompt them to upgrade before it expires.

Python

check_trial.py
from datetime import datetime, timezone

def check_trial_status(license_key: str):
    """Check if a license is a trial and show days remaining."""
    result = client.validate(key=license_key)

    if not result.valid:
        if result.code == "EXPIRED":
            print("Your trial has expired. Upgrade to continue.")
            show_upgrade_prompt()
            return False
        print(f"License invalid: {result.code}")
        return False

    # Check trial metadata (get_license needs the Management API key, not the
    # license key — it's a separate authenticated call)
    if not result.license:
        return True
    license = client.get_license(result.license.id)
    if license.policy.is_trial and license.expires_at:
        expires_at = datetime.fromisoformat(license.expires_at)
        days_left = (expires_at - datetime.now(timezone.utc)).days
        print(f"Trial: {days_left} days remaining")

    return True

TypeScript

check-trial.ts
async function checkTrialStatus(licenseKey: string) {
  const result = await client.validate({ key: licenseKey });

  if (!result.valid) {
    if (result.code === "EXPIRED") {
      showUpgradePrompt();
      return false;
    }
    return false;
  }

  // getLicense needs the Management API key, not the license key — it's a
  // separate authenticated call, typically made from your own backend.
  if (!result.license) {
    return true;
  }
  const license = await client.getLicense(result.license.id);
  if (license.policy?.isTrial && license.expiresAt) {
    const daysLeft = getDaysUntil(license.expiresAt);
    showTrialBanner(`Trial: ${daysLeft} days remaining`);
  }

  return true;
}

4. Convert Trial to Paid

When a user upgrades, create a new license with the paid policy and revoke the trial. This cleanly separates trial and paid entitlements.

convert.py
def convert_trial_to_paid(
    trial_license_email: str,
    paid_product_id: str,
    paid_policy_id: str,
    trial_license_id: str
):
    """Convert a trial license to a paid license.

    There is no "change this license's policy" operation — policyId is fixed
    at creation. The supported path is: create a new paid license, then
    revoke the trial.
    """
    paid_license = httpx.post(
        "https://www.licentric.com/api/v1/licenses",
        headers=headers,
        json={
            "productId": paid_product_id,
            "policyId": paid_policy_id,
            "userEmail": trial_license_email,
        },
    ).json()["data"]

    # Revoke the trial
    httpx.post(
        f"https://www.licentric.com/api/v1/licenses/{trial_license_id}/revoke",
        headers=headers,
    )

    return paid_license
Stripe integration
Combine trials with Stripe Checkout for a seamless trial-to-paid conversion. When the Stripe subscription activates, Licentric automatically provisions the paid license and revokes the trial.
Trial abuse prevention
The trial template limits licenses to 1 device with fingerprinting enabled. This prevents the same machine from obtaining multiple trial licenses. For stricter enforcement, set fingerprint uniqueness to PER_ACCOUNT.