What Happens to Your Software When a Subscription Ends?
See an expired trial, an ended subscription and a perpetual fallback license from the customer’s side, with a product-scoped Python validation example.
Open the same desktop app after three different deadlines. One customer has finished a trial. Another has stopped paying for a subscription. The third bought continued use of an eligible version, with updates for a limited period. The app should show three different outcomes.
1. The trial has expired
Your trial has ended. Your projects are still here. Enter a paid license to enable export.
In this example, the app keeps its window, license settings and project viewer available, but restricts the paid export action. That is the app developer’s policy; PermitCore does not delete files or choose which buttons disappear.
Issue a trial with a server-side ExpiresAt. For this strict cutoff, configure no post-expiry grace. Once expired, an online check returns isValid: false and errorCode: "Expired". Reinstalling the app does not change that key’s expiry. Issuing repeat trials needs a separate issuance policy.
2. The subscription has ended
Your subscription access has ended. Open your account to renew, or enter another valid license. Your local files have not been removed.
This example is after the license’s paid-through date and any applicable payment grace. A failed renewal can have a grace window: Store payment grace defaults to 14 days unless the applicable license override or Store setting changes it. During an allowed window, validation can still return isValid: true with isInGracePeriod: true. Show a payment notice rather than pretending access has already expired.
Read the verified license result, not a checkout redirect or a local billing timer. Provider cancellation handling can revoke a recurring license; clicking Cancel is not a promise of continued access until month-end. The Store reference explains the paid-through, grace and cancellation rules.
3. Updates have ended, but the eligible version still runs
You can keep using version 1.8. Your update period has ended. Renew updates to use version 2.0.
For this perpetual fallback example, the vendor deliberately provides an otherwise valid perpetual entitlement: no access expiry, plus an update cutoff. The eligible installed version validates successfully. A newer paid release is refused or offered as an upgrade according to the vendor’s policy.
PermitCore returns MaintenanceExpiresAt in the API response, but does not automatically calculate the last eligible release. Keep a trusted release catalog. One implementation sets MaxVersion to the eligible release, then passes the running version to validation. An eligible version passes; a later version returns VersionBlocked. Deliver the eligible installer and test project-format compatibility before offering a downgrade.
An expired or cancelled Store subscription does not automatically become a perpetual license. Fallback must be a separately designed entitlement and fulfillment policy. Revocation, wrong-product checks and device limits still apply. See the perpetual fallback guide.
The check inside the app
This Python SDK 2.1.1 example requires an online result and does not enable offline caching. Read key from secure local storage and use your product’s ID. Activate the device separately on first installation; validation does not consume a new seat.
from permitcore import PermitCoreClient
client = PermitCoreClient(
"https://api.permitcore.dev",
enable_offline_cache=False,
timeout=3,
)
result = client.validate(
key,
version=running_version,
expected_product_id=product_id,
)
if result.is_valid:
enable_licensed_features(result.features)
if result.is_in_grace_period:
show_notice("Payment needs attention; grace access is active.")
elif result.error_code == "RequestFailed":
show_retry("Could not verify the license. Please retry.")
elif result.error_code == "Expired":
show_license_screen("License expired. Renew or enter a paid key.")
elif result.error_code == "VersionBlocked":
show_license_screen("Use your eligible version or renew updates.")
elif result.error_code:
show_license_screen("License not accepted. Check your account.")
else:
show_retry("Could not verify the license. Please retry.")The display callbacks belong to your app. Choose trial-specific wording from your trusted application context; an expired response need not include the trial flag. An unavailable server is not proof that a subscription ended. If you support offline use, apply only still-valid, verified signed proof with its configured device, product and expiry checks. A perpetual entitlement does not make a cached offline token last forever.
Test each cutoff with synthetic licenses: expired trial, subscription inside and outside grace, eligible and blocked versions, revoked key and an unavailable server. Keep user files separate from access decisions. The validation reference, license controls and startup check guide cover the underlying checks.