This guide covers migrating from v1’s key-based identity patterns to v2’s dedicated identity management system.

Overview

What’s New in v2:

  • Dedicated identity endpoints: Create, read, update, delete identities independently
  • Shared rate limits: Identity-level rate limiting across multiple keys
  • Centralized metadata: Single source of truth for user/entity data
  • Direct management: No longer need to infer identities from keys

Migration Impact:

  • v1 Pattern: Identity implied through key externalId field
  • v2 Enhancement: Explicit identity management with dedicated endpoints
  • Benefit: Better organization, shared resources, and centralized control

Creating Identities: v1 Key Pattern → v2 Dedicated Endpoint

v1 Approach: Identities were implicit - you created keys with externalId and metadata directly on each key. v2 Approach: Create explicit identities first, then reference them from keys for shared resources. Key Benefits:
  • Centralized identity metadata management
  • Shared rate limits across multiple keys
  • Direct identity operations without key dependency
v1 vs v2: Identity Creation Patterns
// v1: Create key with identity data embedded
{
  "apiId": "api_123", 
  "externalId": "user_123",
  "meta": {
    "email": "user@example.com",
    "plan": "pro"
  },
  "ratelimit": { 
    "type": "fast", 
    "limit": 1000, 
    "refillRate": 1000, 
    "refillInterval": 3600000
  } 
}

// v2: Create identity first with shared resources
{
  "externalId": "user_123",
  "meta": {
    "email": "user@example.com",
    "plan": "pro"
  },
  "ratelimits": [ 
    { 
      "name": "requests", 
      "limit": 1000, 
      "duration": 3600000
    } 
  ] 
}

Retrieving Identity Data: v1 Key Lookup → v2 Direct Access

v1 Approach: To get identity data, you had to list/get keys and extract identity information from individual keys. v2 Approach: Directly retrieve identity data using the dedicated getIdentity endpoint.
v1 vs v2: Identity Retrieval Patterns
# v1: Had to find keys by externalId to get identity data
curl -X GET "https://api.unkey.dev/v1/apis.listKeys?apiId=api_123"
  -H "Authorization: Bearer <your-root-key>"

# Then filter results to find keys with matching externalId #
# Identity data scattered across multiple key records #

# v2: Direct identity lookup
curl -X POST https://api.unkey.com/v2/identities.getIdentity
  -H "Authorization: Bearer <your-root-key>"
  -H "Content-Type: application/json"
  -d '{"externalId": "user_123"}'

NEW: POST /v2/identities.updateIdentity

Purpose: Update identity metadata and rate limits - not possible in v1. v1 Limitation: To update identity data, you had to update each key individually. v2 Enhancement: Update identity once, changes apply to all associated keys.
v1 vs v2: Identity Update Patterns
# v1: Had to update each key separately - very inefficient
curl -X POST https://api.unkey.dev/v1/keys.updateKey
  -H "Authorization: Bearer <your-root-key>"
  -H "Content-Type: application/json"
  -d '{"keyId": "key_123", "meta": {"plan": "enterprise"}}'

curl -X POST https://api.unkey.dev/v1/keys.updateKey
  -H "Authorization: Bearer <your-root-key>"
  -H "Content-Type: application/json"
  -d '{"keyId": "key_456", "meta": {"plan": "enterprise"}}'
# ... repeat for every key with this externalId #

# v2: Update identity once, affects all associated keys
curl -X POST https://api.unkey.com/v2/identities.updateIdentity
  -H "Authorization: Bearer <your-root-key>"
  -H "Content-Type: application/json"
  -d '{"externalId": "user_123", "meta": {"plan": "enterprise"}, "ratelimits": [{"name": "requests", "limit": 5000, "duration": 3600000}]}'

NEW: POST /v2/identities.listIdentities

Purpose: Get paginated list of all identities - not possible in v1. v1 Limitation: No direct way to list identities; had to infer from key listings. v2 Enhancement: Direct identity listing with metadata and pagination.
v1 vs v2: Identity Listing Patterns
# v1: List all keys and manually group by externalId
curl -X GET "https://api.unkey.dev/v1/apis.listKeys?apiId=api_123"
  -H "Authorization: Bearer <your-root-key>"

# Then manually process results to extract unique externalIds #
# No direct identity metadata or pagination #

# v2: Direct identity listing with pagination
curl -X POST https://api.unkey.com/v2/identities.listIdentities
  -H "Authorization: Bearer <your-root-key>"
  -H "Content-Type: application/json"
  -d '{"limit": 100, "cursor": "optional_cursor"}'

NEW: POST /v2/identities.deleteIdentity

Purpose: Permanently delete an identity - not directly possible in v1. v1 Limitation: To remove identity, had to delete all associated keys individually. v2 Enhancement: Delete identity while preserving or handling associated keys appropriately.
v1 vs v2: Identity Deletion Patterns
# v1: Had to delete each key individually to remove identity
curl -X POST https://api.unkey.dev/v1/keys.deleteKey
  -H "Authorization: Bearer <your-root-key>"
  -H "Content-Type: application/json"
  -d '{"keyId": "key_123"}'

curl -X POST https://api.unkey.dev/v1/keys.deleteKey
  -H "Authorization: Bearer <your-root-key>"
  -H "Content-Type: application/json"
  -d '{"keyId": "key_456"}'
# ... repeat for every key with this externalId #

# v2: Delete identity (associated keys remain but lose shared resources)
curl -X POST https://api.unkey.com/v2/identities.deleteIdentity
  -H "Authorization: Bearer <your-root-key>"
  -H "Content-Type: application/json"
  -d '{"externalId": "user_123"}'

Migration Patterns

Response Format Migration

v1 vs v2: Identity Data Access Patterns
// v1: Get identity data by listing keys
const response = await fetch('/v1/apis.listKeys?apiId=api_123', { 
  method: 'GET', 
  headers: { 
    'Authorization': 'Bearer <root-key>'
  } 
}); 

const data = await response.json(); 
// Filter keys by externalId and aggregate identity data
const userKeys = data.keys.filter(key => key.externalId === 'user_123'); 
const identityMeta = {}; // Manually merge metadata from all keys

// v2: Direct identity retrieval
const response = await fetch('/v2/identities.getIdentity', { 
  method: 'POST', 
  headers: { 
    'Authorization': 'Bearer <root-key>', 
    'Content-Type': 'application/json'
  }, 
  body: JSON.stringify({ externalId: 'user_123' }) 
}); 

const result = await response.json(); 
const identityData = result.data; // Complete identity data
const requestId = result.meta.requestId; // For debugging

Identity-Key Relationship Patterns

v1 vs v2: Identity-Key Relationship Patterns
// v1: Create keys with identity data embedded
const keyResponse = await fetch('/v1/keys.createKey', { 
  method: 'POST',
  headers: {
    'Authorization': 'Bearer <root-key>',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    apiId: 'api_123',
    externalId: 'user_123', 
    meta: { email: 'user@example.com', plan: 'pro' } 
  })
});

// v2: Create identity first
const identityResponse = await fetch('/v2/identities.createIdentity', { 
  method: 'POST', 
  headers: { 
    'Authorization': 'Bearer <root-key>', 
    'Content-Type': 'application/json'
  }, 
  body: JSON.stringify({ 
    externalId: 'user_123', 
    meta: { email: 'user@example.com', plan: 'pro' }, 
    ratelimits: [{ name: 'requests', limit: 1000, duration: 3600000 }] 
  }) 
}); 

// Then create keys that reference the identity
const keyResponse = await fetch('/v2/keys.createKey', { 
  method: 'POST', 
  headers: { 
    'Authorization': 'Bearer <root-key>', 
    'Content-Type': 'application/json'
  }, 
  body: JSON.stringify({ 
    apiId: 'api_123', 
    externalId: 'user_123' // References the identity
  }) 
});