Migrating from Other Systems
Migrating from Other Systems
Already running licensing on Keygen, Cryptolens, LicenseSpring, or something you built yourself? You can bring your existing licenses into PermitCore without reissuing new keys to a single customer. Licenses → Import reads a CSV export from your old system and creates matching licenses here — using the exact same key strings your customers already have.
Why the key string doesn't need to change
PermitCore never validates a license by comparing its format — it validates by hash.
When a customer's software calls POST /api/v1/validate or
/activate, the server computes a SHA-256 hash of whatever string was sent and
looks it up. That means any string works as a license key, not just PermitCore's own
PERMIT-XXXX-XXXX-XXXX-XXXX format — importing simply registers your existing
key strings under that same lookup, unchanged.
How it works
- Export your licenses from your current system as a CSV (most vendors offer this from their dashboard or API).
- Go to Licenses → Import, pick your source system from the dropdown (or "Generic CSV" for anything else), and upload the file or paste its contents.
- Map columns — PermitCore guesses the mapping based on the source system you picked; review and correct anything before continuing.
- Preview every row before anything is sent — rows with a missing key, an unrecognized product name, or an invalid value are flagged and skipped, without blocking the rest of the file.
- Optionally set a passphrase so you can decrypt/reveal these keys again later from the admin panel.
- Review the results — each row shows whether it was created, already existed (skipped, not an error), or failed, with a downloadable CSV of anything that didn't import cleanly.
The passphrase is optional for imports
Manually-created licenses always need a passphrase because PermitCore generates the key itself and needs a way to show it to you once. Imported licenses are different — you already have the plaintext key from your old system's export, so encryption here is purely for being able to view the key again later from the admin panel.
If you skip the passphrase, the license still works completely normally for validation and activation (only the key's hash matters for that) — you just won't be able to reveal the plaintext again afterward. This is the same trade-off PermitCore already applies to Store-purchased licenses claimed through a customer's own account.
Carrying over activations already in use
A license that's already been activated on, say, 3 of its 5 seats on your old system needs that carried over — otherwise it would import at 0 activations and let 2 extra devices activate for free before you're even aware. Map a column with the number of seats already used to "Activations already used" in the wizard, and it seeds the license's activation count directly instead of starting from zero.
Field mapping by source system
Field names below are the ones each vendor's own API/export documentation uses at the time of writing — a vendor changing their export format won't automatically update the wizard's auto-guess, but you can always remap columns manually.
Keygen.sh
| Keygen field | PermitCore field | Notes |
|---|---|---|
key | License Key | Used as-is, no reformatting. |
policy / product | Product | Matched against your PermitCore product names — a Keygen "policy" is roughly a product's license rules. |
maxMachines | Max Activations | Keygen's per-license machine limit. |
uses | Activations already used | Keygen's own usage counter. |
expiry | Expires At | ISO 8601 timestamp. |
| (owner relationship — no direct field on the license) | Customer Email | Keygen licenses reference a separate user resource; most CSV exports add their own flattened email column. |
Cryptolens
| Cryptolens field | PermitCore field | Notes |
|---|---|---|
key | License Key | Used as-is. |
productId / product name | Product | Matched against your PermitCore product names. |
maxNoOfMachines | Max Activations | Cryptolens's activation limit. |
activatedMachines | Activations already used | A list in Cryptolens's API — use its count/length as a plain number in your CSV. |
expires | Expires At | |
customer | Customer Email |
LicenseSpring
| LicenseSpring field | PermitCore field | Notes |
|---|---|---|
license_key | License Key | Used as-is. |
product | Product | Matched against your PermitCore product names. |
max_activations (or max_consumptions for metered licenses) | Max Activations | LicenseSpring splits activation-based and consumption-based licenses — map whichever your license type uses. |
times_activated | Activations already used | |
validity_period | Expires At | |
customer.email | Customer Email | Nested under a customer object in LicenseSpring's API — most CSV exports flatten it to its own column. |
Generic CSV / home-grown systems
Any CSV with a column of license key strings works — the wizard doesn't care about your column names, only how you map them. At minimum you need a key column; a product, max activations, activations-used, expiry, and customer email column are all optional and can be left unmapped (a default product and max-activations value apply instead).
What's not carried over yet
Tags, IP/country allowlists, and custom fields aren't part of the import wizard today —
they're genuinely not sent, not silently dropped. If your old system's licenses used any
of these, add them afterward from each license's Edit screen (or via
PUT /api/licenses/{"{id}"} if you're scripting the import).
Permissions
Importing requires the same Create / Edit / Revoke license permission as manually creating a license — if you use custom roles, grant that permission to any role that should be able to import.
API
The wizard is a thin client over POST /api/licenses/import — if you'd rather
script a migration directly, hash each existing key with SHA-256 client-side (never send a
plaintext key to the server) and POST an array of rows, up to 500 per request:
POST /api/licenses/import
Authorization: Bearer <token>
Content-Type: application/json
[
{
"productId": "...",
"encryptedKey": "",
"iv": "",
"plaintextKeyHash": "<sha256 hex of the existing key>",
"maxActivations": 5,
"initialActivations": 3,
"expiresAt": "2027-01-01T00:00:00Z",
"customerEmail": "[email protected]"
}
]
Leaving encryptedKey/iv empty (as above) skips the reveal-later
capability, matching the wizard's "Skip passphrase" option. Each row reports back one of
Created, Duplicate (already imported — not an error), ValidationFailed,
or Failed — a bad or duplicate row never blocks the rest of the batch.