Migrate from Keygen
A comprehensive guide to migrating your licensing system from Keygen to Licenz, including data export, API mapping, and client SDK updates.
Migration Overview
The migration process involves four main steps:
- Export data from Keygen - Products, policies, licenses, and users
- Transform and import to Licenz - Map Keygen concepts to Licenz
- Update client integrations - Switch SDK and API calls
- Validate and cutover - Test thoroughly before going live
Plan Your Migration
We recommend running Keygen and Licenz in parallel during migration. This allows you to validate the migration without service interruption.
Concept Mapping
Understanding how Keygen concepts map to Licenz:
| Keygen | Licenz | Notes |
|---|---|---|
| Account | Organization | Top-level container |
| Product | Product | Direct mapping |
| Policy | Product Settings | Policies are merged into product configuration |
| License | License | Direct mapping with enhanced features |
| Machine | Activation | Renamed for clarity |
| User | Customer | Optional customer association |
| Entitlement | Feature Flag | Enhanced with rules engine |
Step 1: Export Data from Keygen
Use the Keygen API to export all your data:
#!/bin/bash
# Keygen Data Export Script
# Exports licenses, products, and policies from Keygen
KEYGEN_ACCOUNT_ID="your-account-id"
KEYGEN_TOKEN="your-admin-token"
OUTPUT_DIR="./keygen-export"
mkdir -p "$OUTPUT_DIR"
# Export products
curl -s "https://api.keygen.sh/v1/accounts/$KEYGEN_ACCOUNT_ID/products" -H "Authorization: Bearer $KEYGEN_TOKEN" -H "Accept: application/vnd.api+json" > "$OUTPUT_DIR/products.json"
# Export policies
curl -s "https://api.keygen.sh/v1/accounts/$KEYGEN_ACCOUNT_ID/policies" -H "Authorization: Bearer $KEYGEN_TOKEN" -H "Accept: application/vnd.api+json" > "$OUTPUT_DIR/policies.json"
# Export licenses (paginated)
page=1
while true; do
response=$(curl -s "https://api.keygen.sh/v1/accounts/$KEYGEN_ACCOUNT_ID/licenses?page[number]=$page&page[size]=100" -H "Authorization: Bearer $KEYGEN_TOKEN" -H "Accept: application/vnd.api+json")
echo "$response" > "$OUTPUT_DIR/licenses_page_$page.json"
# Check if more pages
has_next=$(echo "$response" | jq -r '.links.next // empty')
[ -z "$has_next" ] && break
((page++))
done
# Export users
curl -s "https://api.keygen.sh/v1/accounts/$KEYGEN_ACCOUNT_ID/users" -H "Authorization: Bearer $KEYGEN_TOKEN" -H "Accept: application/vnd.api+json" > "$OUTPUT_DIR/users.json"
echo "Export complete: $OUTPUT_DIR" Verify Export
# Check exported files
ls -la keygen-export/
# Count licenses
cat keygen-export/licenses_page_*.json | jq '.data | length' | paste -sd+ | bc
# Preview products
cat keygen-export/products.json | jq '.data[].attributes.name' Step 2: Transform and Import
Use the migration script to convert and import your data:
#!/usr/bin/env python3
"""
Keygen to Licenz Migration Script
Converts Keygen export data to Licenz format and imports via API
"""
import json
import requests
import os
from datetime import datetime
LICENZ_API_URL = "https://api.licenz.io/v1" # or your self-hosted URL
LICENZ_API_KEY = os.environ.get("LICENZ_API_KEY")
EXPORT_DIR = "./keygen-export"
def load_keygen_data(filename):
with open(f"{EXPORT_DIR}/{filename}") as f:
data = json.load(f)
return data.get("data", [])
def map_policy_to_product(policy):
"""Map Keygen policy to Licenz product"""
return {
"name": policy["attributes"]["name"],
"description": policy["attributes"].get("metadata", {}).get("description", ""),
"metadata": {
"keygen_policy_id": policy["id"],
"max_machines": policy["attributes"].get("maxMachines"),
"max_uses": policy["attributes"].get("maxUses"),
"duration": policy["attributes"].get("duration"),
}
}
def map_license(license_data, policy_map):
"""Map Keygen license to Licenz license"""
attrs = license_data["attributes"]
policy_id = license_data["relationships"]["policy"]["data"]["id"]
# Map license type
keygen_scheme = attrs.get("scheme", "ED25519_SIGN")
licenz_type = "perpetual" # default
if attrs.get("expiry"):
licenz_type = "subscription"
return {
"key": attrs.get("key"),
"product_id": policy_map.get(policy_id),
"type": licenz_type,
"status": "active" if attrs.get("status") == "ACTIVE" else "suspended",
"email": attrs.get("metadata", {}).get("email", ""),
"expires_at": attrs.get("expiry"),
"max_activations": attrs.get("maxMachines", 1),
"metadata": {
"keygen_license_id": license_data["id"],
"keygen_policy_id": policy_id,
"original_created_at": attrs.get("created"),
}
}
def create_product(product_data):
"""Create product in Licenz"""
response = requests.post(
f"{LICENZ_API_URL}/products",
headers={
"Authorization": f"Bearer {LICENZ_API_KEY}",
"Content-Type": "application/json"
},
json=product_data
)
response.raise_for_status()
return response.json()["data"]["id"]
def create_license(license_data):
"""Create license in Licenz"""
response = requests.post(
f"{LICENZ_API_URL}/licenses",
headers={
"Authorization": f"Bearer {LICENZ_API_KEY}",
"Content-Type": "application/json"
},
json=license_data
)
response.raise_for_status()
return response.json()["data"]["id"]
def main():
print("Starting Keygen to Licenz migration...")
# Load exported data
policies = load_keygen_data("policies.json")
# Create products from policies
policy_map = {} # keygen_policy_id -> licenz_product_id
print(f"Migrating {len(policies)} policies as products...")
for policy in policies:
product_data = map_policy_to_product(policy)
licenz_product_id = create_product(product_data)
policy_map[policy["id"]] = licenz_product_id
print(f" Created product: {product_data['name']} -> {licenz_product_id}")
# Migrate licenses
page = 1
total_licenses = 0
while os.path.exists(f"{EXPORT_DIR}/licenses_page_{page}.json"):
licenses = load_keygen_data(f"licenses_page_{page}.json")
for license_data in licenses:
try:
license_mapped = map_license(license_data, policy_map)
licenz_license_id = create_license(license_mapped)
total_licenses += 1
print(f" Migrated license: {license_mapped['key'][:20]}... -> {licenz_license_id}")
except Exception as e:
print(f" Error migrating license {license_data['id']}: {e}")
page += 1
print(f"\nMigration complete!")
print(f" Products created: {len(policy_map)}")
print(f" Licenses migrated: {total_licenses}")
if __name__ == "__main__":
main() Run the Migration
# Set your Licenz API key
export LICENZ_API_KEY="your-api-key"
# Run migration (dry run first)
python3 migrate.py --dry-run
# Run actual migration
python3 migrate.py
# Verify migration
curl -H "Authorization: Bearer $LICENZ_API_KEY" \
https://api.licenz.io/v1/licenses | jq '.meta.total' API Endpoint Mapping
Reference for updating API calls:
| Keygen Endpoint | Licenz Endpoint | Notes |
|---|---|---|
POST /licenses/actions/validate-key | POST /v1/licenses/validate | Direct mapping |
GET /licenses/:id | GET /v1/licenses/:id | Direct mapping |
POST /licenses | POST /v1/licenses | Direct mapping |
PATCH /licenses/:id | PATCH /v1/licenses/:id | Direct mapping |
DELETE /licenses/:id | DELETE /v1/licenses/:id | Direct mapping |
POST /machines | POST /v1/activations | Renamed to activations |
DELETE /machines/:id | DELETE /v1/activations/:id | Renamed |
GET /policies | N/A | Policies merged into products |
Step 3: Update Client Integrations
JavaScript/TypeScript
// Before: Keygen SDK
import Keygen from 'keygen-sdk';
const keygen = new Keygen({
account: 'your-account-id',
});
const validation = await keygen.licenses.validate({
key: licenseKey,
fingerprint: machineFingerprint,
});
// After: Licenz SDK
import { Licenz } from 'licenz';
const licenz = new Licenz({
apiKey: 'your-api-key',
// For self-hosted:
// baseUrl: 'https://licenz.yourdomain.com/api/v1'
});
const validation = await licenz.licenses.validate({
key: licenseKey,
fingerprint: machineFingerprint,
}); Rust
// Before: Keygen validation (HTTP request)
let response = client
.post("https://api.keygen.sh/v1/accounts/ACCOUNT/licenses/actions/validate-key")
.header("Authorization", format!("Bearer {}", api_key))
.json(&json!({
"meta": {
"key": license_key,
"scope": { "fingerprint": fingerprint }
}
}))
.send()
.await?;
// After: Licenz (using licenz crate)
use licenz::{Client, LicenseValidation};
let client = Client::new("your-api-key");
let validation = client
.licenses()
.validate(license_key)
.fingerprint(fingerprint)
.send()
.await?;
match validation {
LicenseValidation::Valid(license) => {
println!("License valid until: {:?}", license.expires_at);
}
LicenseValidation::Invalid(reason) => {
println!("License invalid: {}", reason);
}
} Request/Response Mapping
The validation request format is similar but simplified:
// Keygen validation request
{
"meta": {
"key": "ABC-123-DEF",
"scope": {
"fingerprint": "device-fingerprint"
}
}
}
// Licenz validation request
{
"key": "ABC-123-DEF",
"fingerprint": "device-fingerprint"
} Step 4: Validation and Cutover
Pre-Cutover Checklist
- All products migrated and verified
- All licenses migrated with correct metadata
- Test validation with sample licenses
- Client SDK updated and tested in staging
- Webhooks reconfigured (if used)
- Rollback plan documented
- Support team notified
Validation Script
#!/bin/bash
# Validate migration by testing sample licenses
# Test license validation
curl -X POST https://api.licenz.io/v1/licenses/validate \
-H "Authorization: Bearer $LICENZ_API_KEY" \
-H "Content-Type: application/json" \
-d '{"key": "MIGRATED-LICENSE-KEY"}'
# Compare license counts
KEYGEN_COUNT=$(cat keygen-export/licenses_page_*.json | jq '[.data | length] | add')
LICENZ_COUNT=$(curl -s -H "Authorization: Bearer $LICENZ_API_KEY" \
https://api.licenz.io/v1/licenses | jq '.meta.total')
echo "Keygen licenses: $KEYGEN_COUNT"
echo "Licenz licenses: $LICENZ_COUNT"
if [ "$KEYGEN_COUNT" -eq "$LICENZ_COUNT" ]; then
echo "License counts match!"
else
echo "WARNING: License count mismatch"
fi Parallel Operation Period
During migration, you can run both systems in parallel:
// Dual-validation during migration period
async function validateLicense(key: string, fingerprint: string) {
// Try Licenz first
try {
const result = await licenz.licenses.validate({ key, fingerprint });
return result;
} catch (licenzError) {
// Fallback to Keygen during migration
console.warn('Licenz validation failed, falling back to Keygen');
return await keygen.licenses.validate({ key, fingerprint });
}
} Handling Special Cases
Floating Licenses
Keygen floating licenses map to Licenz concurrent activations:
// Keygen floating license policy
{
"maxMachines": 5,
"floating": true,
"floatingInterval": 3600
}
// Licenz equivalent (in product settings)
{
"max_concurrent_activations": 5,
"activation_heartbeat_interval": 3600
} Component Licenses
// Keygen component validation
{
"meta": {
"key": "LICENSE-KEY",
"scope": {
"fingerprint": "device-fingerprint",
"components": [
{"name": "cpu", "fingerprint": "cpu-id"},
{"name": "motherboard", "fingerprint": "mb-id"}
]
}
}
}
// Licenz component validation
{
"key": "LICENSE-KEY",
"fingerprint": "device-fingerprint",
"components": {
"cpu": "cpu-id",
"motherboard": "mb-id"
}
} Post-Migration
Decommission Keygen
After successful migration and validation period:
- Remove Keygen SDK from your application
- Update documentation to reference Licenz
- Cancel Keygen subscription
- Archive Keygen export data for records
Optimize for Licenz Features
Take advantage of Licenz-specific features:
- Offline Activation - Air-gapped validation support
- Hardware Binding - Enhanced fingerprinting options
- Rules Engine - Complex license policies with Graxon
- Self-Hosted - Run on your own infrastructure
Getting Help
If you encounter issues during migration:
- Check the API documentation for endpoint details
- Review error messages in the migration script output
- Contact support@licenz.io for assistance
- Join our Discord community for help