Skip to main content
If your app already has API keys in production, you can move them to Unkey without asking users to replace them. You send us the hash of each key, never the key itself, and your users keep using the keys they have. We agree on the hash format with you first.

Get a migration ID

Email support@unkey.com with your workspace ID, the system the keys come from, and how they’re hashed, and we’ll send you a migrationId. We support two hash formats today. Keys hashed any other way, such as bcrypt or a salted hash, can’t be imported from the hash alone. Tell support, and we’ll plan the migration another way.

Hash the keys yourself

For sha256, this is the same function Unkey uses, so a key hashed this way works exactly like one Unkey created.
hash.ts
Never include plaintext keys in the export or the migration request.
You need a root key with the permissions listed on this page. Create one in the dashboard under Settings > Root Keys, and pass it as Authorization: Bearer <root key>. See Permission reference for every permission.
keys.migrateKeys needs api.*.create_key or api.<api_id>.create_key. Verifying afterwards needs the usual api.*.verify_key. See Root key permissions.

Import the hashes

string
required
The identifier from support, 3 to 255 characters. An unknown ID returns HTTP 404 err:unkey:data:migration_not_found.
string
required
The keyspace to import into.
object[]
required
At least one entry. Each has a required hash (3 or more characters, in your migration’s format) and the same optional fields as keys.createKey: name, externalId, meta, roles (up to 100), permissions (up to 1000), expires, enabled (default true), credits, and ratelimits (up to 50). The rules match Creating keys.

Response

The response is HTTP 200 even when some hashes fail. A hash that already exists anywhere in Unkey, not just in your workspace, is skipped and listed under failed. Every other key is created and returned with its new keyId. Store it next to your own record of the key.
Send large migrations in batches and reconcile migrated against your export after each one.

Switch verification over

Point your backend at keys.verifyKey. For the sha256 algorithm nothing else changes. For the prefixed-api-key algorithm, include the migrationId in every verification during the roll-out:
After a key’s first verification with migrationId, it works with or without the field. A key that hasn’t been verified yet still needs it and returns NOT_FOUND without it, so keep sending migrationId until the fallback below goes quiet.

Run both systems during the switch

Verify against Unkey first. If a key comes back NOT_FOUND, check your old system and log it. Remove the fallback when the log goes quiet. Create new keys with keys.createKey so new users are never in the old system.
Last modified on September 29, 2026