Skip to main content
v2.2.0
April 13, 2026

New Features

License Management Module

A new client.license module provides full lifecycle management for software licenses. All methods are available in both synchronous and asynchronous clients.license.activate(licenseKey, customerId?, instanceName?, metadata?)Activates a license key and creates a new instance. Returns a LicenseActivateResponse (Pydantic model) containing the instanceId, activatedAt, expiresOn, and the full LicenseDetails object.
response = client.license.activate(
    licenseKey="LIC-XXXX-YYYY-ZZZZ",
    customerId="cust_789",
    instanceName="My MacBook Pro",
    metadata={"os": "macOS", "arch": "arm64"},
)

print(response.instanceId)
print(response.license.activationUsage)       # e.g. 1
print(response.license.plan.product.name)     # e.g. "Kelviq Engine"
print(response.license.subscription.billingType)  # "SUBSCRIPTION"
license.deactivate(licenseKey, instanceId)Deactivates a specific license instance. Returns a LicenseDeactivateResponse with message and deactivatedAt.
response = client.license.deactivate(
    licenseKey="LIC-XXXX-YYYY-ZZZZ",
    instanceId="8f3e2b1a-5c6d-4e9f-8a0b-1c2d3e4f5g6h",
)
print(response.message)  # "License instance deactivated successfully."
license.validate(licenseKey, instanceId?)Validates a license key and optionally a specific instance. Returns a LicenseValidateResponse with valid, code, detail, metadata, and the full LicenseDetails.
response = client.license.validate(
    licenseKey="LIC-XXXX-YYYY-ZZZZ",
    instanceId="8f3e2b1a-5c6d-4e9f-8a0b-1c2d3e4f5g6h",
)

if response.valid:
    print(f"Valid — code: {response.code}")
else:
    print(f"Invalid — {response.detail}")

New Pydantic Models

  • LicenseDetails — Full license object with id, licenseKey, activatedOn, expiresOn, activationUsage, activationLimit, enabled, customer, plan, and subscription.
  • LicenseCustomercustomerId, name, email nested within LicenseDetails.
  • LicensePlan — Expanded with description, version, isLatest, and product (nested LicensePlanProduct model).
  • LicensePlanProductid, identifier, name, taxCode, createdOn, modifiedOn.
  • LicenseActivateResponse, LicenseDeactivateResponse, LicenseValidateResponse — Typed Pydantic response models for each operation.

SubscriptionData New Fields

Three new fields derived from the subscription’s recurrence string are now included in SubscriptionData (returned within LicenseDetails.subscription and customer subscription summaries):
  • billingType"SUBSCRIPTION" if the plan has a recurrence, "ONE_TIME" otherwise.
  • recurrenceUnit — Integer unit from the recurrence string (e.g. 1 from "1 month"), or None.
  • recurrenceType — Recurrence period in uppercase (e.g. "MONTH"), or None.
v2.1.0
April 2, 2026

New Features

Customer Portal Module

A new client.portal module lets you create pre-authenticated customer portal sessions server-side and redirect customers directly to their self-serve portal. Available on both synchronous and asynchronous clients.portal.create_session(customerId)
session = client.portal.create_session(customerId="cust_789")

# Redirect your customer — no login required
print(session.customerPortalUrl)
Async variant:
session = await async_client.portal.create_session(customerId="cust_789")
Returns CreatePortalSessionResponse (Pydantic model) with:
  • token (str) — Session token authenticating the portal session.
  • email (str) — The customer’s email address.
  • customerPortalUrl (str) — Pre-authenticated URL to redirect the customer to.
v2.0.0
February 27, 2026

New Features

Entitlements Aggregation Engine

When a customer has multiple active subscriptions, the API can return duplicate featureId entries. The SDK now automatically aggregates them into a single Entitlement model while preserving the raw data.
entitlement = client.entitlements.get_entitlement("cust_123", "email-sends")

# Aggregated values across all subscriptions
print(entitlement.hasAccess)     # True
print(entitlement.usageLimit)    # 1500 (e.g., 1000 from base + 500 from top-up)
print(entitlement.currentUsage)  # 200
print(entitlement.remaining)     # 1300

# Per-subscription details
for item in entitlement.items:
    print(item.usageLimit, item.resetAt)
Aggregation rules:
FieldAggregation Strategy
hasAccessTrue if any raw entry grants access
usageLimitSummed across all entries (for METER feature types)
currentUsageSummed across all entries (for METER feature types)
remainingComputed as usageLimit - currentUsage
hardLimitTrue if any entry has a hard limit
Note: resetAt is intentionally not aggregated at the top level because each subscription item can have a different reset date. Access individual reset dates via entitlement.items[i].resetAt.

New Entitlement Pydantic Model

A new model representing an aggregated entitlement:
  • featureId, featureType, hasAccess, hardLimit, usageLimit, currentUsage, remaining
  • items: List[EntitlementDetail] — the raw entries that were aggregated into this model

get_entitlements(customerId) — All Entitlements as a Map

Returns a Dict[str, Entitlement] keyed by featureId. Each value is an aggregated model with an .items list containing the original raw entries.
entitlements = client.entitlements.get_entitlements("cust_123")

for feature_id, entitlement in entitlements.items():
    print(f"{feature_id}: hasAccess={entitlement.hasAccess}, remaining={entitlement.remaining}")

get_raw_entitlement(customerId, featureId) — Raw API Response for a Feature

Returns the raw API response (CheckEntitlementsResponse) for a specific feature without any aggregation.
raw = client.entitlements.get_raw_entitlement("cust_123", "email-sends")

get_raw_entitlements(customerId) — Full Raw API Response

Returns the raw API response for all entitlements without any aggregation.
raw = client.entitlements.get_raw_entitlements("cust_123")

Enum Helpers for IDE Autocompletion

New str-based Enum classes:
  • ChargePeriodONE_TIME, MONTHLY, YEARLY, WEEKLY, DAILY, THREE_MONTHS, SIX_MONTHS
  • CancellationTypeIMMEDIATE, CURRENT_PERIOD_ENDS, SPECIFIC_DATE
from kelviq_sdk import ChargePeriod, CancellationType

response = client.checkout.create_session(
    planIdentifier="pro-plan",
    chargePeriod=ChargePeriod.MONTHLY,
    successUrl="https://example.com/success"
)
Raw string values (e.g., "MONTHLY") continue to work everywhere — fully backward-compatible.

Changed

  • get_entitlement(customerId, featureId) now returns Optional[Entitlement] (aggregated) instead of raw CheckEntitlementsResponse. Use get_raw_entitlement() for the original response.
  • get_all_entitlements removed — Use get_entitlements() instead.
  • resetAt removed from top-level Entitlement — Access individual reset dates via the .items list.
v1.1.0
February 27, 2026

New Features

Entitlements Aggregation Engine

When a customer has multiple active subscriptions, the API can return duplicate featureId entries. The SDK now automatically aggregates them into a single Entitlement model while preserving the raw data.
entitlement = client.entitlements.get_entitlement("cust_123", "email-sends")

# Aggregated values across all subscriptions
print(entitlement.has_access)     # True
print(entitlement.usage_limit)    # 1500 (e.g., 1000 from base + 500 from top-up)
print(entitlement.current_usage)  # 200
print(entitlement.remaining)      # 1300

# Per-subscription details
for item in entitlement.items:
    print(item.usage_limit, item.reset_at)
Aggregation rules:
FieldAggregation Strategy
hasAccessTrue if any raw entry grants access
usageLimitSummed across all entries (for METER feature types)
currentUsageSummed across all entries (for METER feature types)
remainingComputed as usageLimit - currentUsage
hardLimitTrue if any entry has a hard limit
Note: resetAt is intentionally not aggregated at the top level because each subscription item can have a different reset date. Access individual reset dates via entitlement.items[i].resetAt.

New Entitlement Pydantic Model

A new model representing an aggregated entitlement:
  • featureId, featureType, hasAccess, hardLimit, usageLimit, currentUsage, remaining
  • items: List[EntitlementDetail] — the raw entries that were aggregated into this model

get_entitlements(customerId) — All Entitlements as a Map

Returns a Dict[str, Entitlement] keyed by featureId. Each value is an aggregated model with an .items list containing the original raw entries.
entitlements = client.entitlements.get_entitlements("cust_123")

for feature_id, entitlement in entitlements.items():
    print(f"{feature_id}: hasAccess={entitlement.has_access}, remaining={entitlement.remaining}")

get_raw_entitlement(customerId, featureId) — Raw API Response for a Feature

Returns the raw API response (CheckEntitlementsResponse) for a specific feature without any aggregation. Includes the customerId wrapper.
raw = client.entitlements.get_raw_entitlement("cust_123", "email-sends")
# CheckEntitlementsResponse with customerId wrapper

get_raw_entitlements(customerId) — Full Raw API Response

Returns the raw API response for all entitlements without any aggregation.
raw = client.entitlements.get_raw_entitlements("cust_123")
# CheckEntitlementsResponse with customerId wrapper and all raw entries

Enum Helpers for IDE Autocompletion

New str-based Enum classes that improve developer experience with IDE autocompletion and prevent typos:
  • ChargePeriodONE_TIME, MONTHLY, YEARLY, WEEKLY, DAILY, THREE_MONTHS, SIX_MONTHS
  • CancellationTypeIMMEDIATE, CURRENT_PERIOD_ENDS, SPECIFIC_DATE
from kelviq_sdk import ChargePeriod, CancellationType

# With enums — IDE autocompletion and typo prevention
response = client.checkout.create_session(
    planIdentifier="pro-plan",
    chargePeriod=ChargePeriod.MONTHLY,
    successUrl="https://example.com/success"
)

# Cancel with enum
client.subscriptions.cancel(
    subscriptionId="sub_123",
    cancellationType=CancellationType.CURRENT_PERIOD_ENDS,
)
Raw string values (e.g., "MONTHLY") continue to work everywhere — the enums are fully backward-compatible.

Changed

  • get_entitlement(customerId, featureId) now returns Optional[Entitlement] (aggregated) instead of raw CheckEntitlementsResponse. Use get_raw_entitlement() if you need the original response.
  • get_all_entitlements removed — Use get_entitlements() instead, which returns a Dict[str, Entitlement] keyed by featureId.
  • resetAt removed from top-level Entitlement — Access individual reset dates via the .items list.

Fixed

  • Removed invalid JavaScript-style comments (// Server-generated UUID) from JSON response blocks in documentation.
  • Fixed // comments to # comments in Python code snippets.
  • Fixed trailing comma in Checkout response JSON block.
  • Fixed typo: “newly created customer” → “updated customer” in customers.update return description.
  • Removed placeholder text from “Supported Functionalities” section.
  • Updated entitlements description to explain multi-subscription aggregation and the items attribute.