This guide covers migrating from v1’s key-based permission patterns to v2’s dedicated RBAC (Role-Based Access Control) system.

Overview

Both v1 and v2 have standalone permission and role management endpoints. The main differences are in request/response formats, HTTP methods, and enhanced functionality in v2.

Key Changes in v2:

  • Response format: Direct responses → {meta, data} envelope
  • HTTP methods: Some GET → POST changes for consistency
  • Enhanced pagination: Better pagination support in list endpoints
  • Domain change: api.unkey.devapi.unkey.com

Migration Impact:

  • Both versions: Have standalone permission and role endpoints
  • v2 Enhancement: Improved response format, consistent HTTP methods, simplified permission queries
  • Benefit: Better API consistency, enhanced debugging with request IDs, simplified permission syntax

POST /v1/permissions.createPermission → POST /v2/permissions.createPermission

Purpose: Create a standalone permission that can be reused across keys and roles. Key Changes:
  • Response format: Direct response → {meta, data} envelope
  • Domain change: api.unkey.devapi.unkey.com
v1 vs v2: Standalone Permission Creation
# v1: Create permission independently
curl -X POST https://api.unkey.dev/v1/permissions.createPermission
  -H "Authorization: Bearer <your-root-key>"
  -H "Content-Type: application/json"
  -d '{"name": "documents.read", "description": "Read access to documents"}'

# v2: Create permission once, reuse everywhere
curl -X POST https://api.unkey.com/v2/permissions.createPermission
  -H "Authorization: Bearer <your-root-key>"
  -H "Content-Type: application/json"
  -d '{"name": "documents.read", "description": "Read access to documents"}'

GET /v1/permissions.getPermission → POST /v2/permissions.getPermission

Purpose: Retrieve permission details by name. Key Changes:
  • HTTP method: GET → POST
  • Request format: Query parameters → Request body
  • Response format: Direct response → {meta, data} envelope
v1 vs v2: Direct Permission Retrieval
# v1: Get permission by ID
curl -X GET "https://api.unkey.dev/v1/permissions.getPermission?permissionId=perm_123"
  -H "Authorization: Bearer <your-root-key>"

# v2: Direct permission lookup
curl -X POST https://api.unkey.com/v2/permissions.getPermission
  -H "Authorization: Bearer <your-root-key>"
  -H "Content-Type: application/json"
  -d '{"name": "documents.read"}'

GET /v1/permissions.listPermissions → POST /v2/permissions.listPermissions

Purpose: Get paginated list of all permissions. Key Changes:
  • HTTP method: GET → POST
  • Request format: Query parameters → Request body
  • Response format: Direct array → {meta, data} envelope with pagination
v1 vs v2: Direct Permission Listing
# v1: List all permissions directly
curl -X GET "https://api.unkey.dev/v1/permissions.listPermissions"
  -H "Authorization: Bearer <your-root-key>"

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

POST /v1/permissions.deletePermission → POST /v2/permissions.deletePermission

Purpose: Permanently delete a permission globally. Key Changes:
  • Response format: Direct response → {meta, data} envelope
  • Domain change: api.unkey.devapi.unkey.com
v1 vs v2: Global Permission Deletion
# v1: Delete permission globally
curl -X POST https://api.unkey.dev/v1/permissions.deletePermission
  -H "Authorization: Bearer <your-root-key>"
  -H "Content-Type: application/json"
  -d '{"permissionId": "perm_123"}'

# v2: Delete permission globally (removes from all keys and roles)
curl -X POST https://api.unkey.com/v2/permissions.deletePermission
  -H "Authorization: Bearer <your-root-key>"
  -H "Content-Type: application/json"
  -d '{"name": "documents.read"}'

Role-Based Access Control (RBAC) Migration

Purpose: Group permissions into roles for easier management - available in both v1 and v2. Key Changes:
  • Response format: Direct responses → {meta, data} envelope
  • Enhanced role listing with better pagination in v2

POST /v1/permissions.createRole → POST /v2/permissions.createRole

v1 vs v2: Role Creation
# v1: Create role
curl -X POST https://api.unkey.dev/v1/permissions.createRole
  -H "Authorization: Bearer <your-root-key>"
  -H "Content-Type: application/json"
  -d '{"name": "editor", "description": "Content editor role"}'

# v2: Create role (envelope response)
curl -X POST https://api.unkey.com/v2/permissions.createRole
  -H "Authorization: Bearer <your-root-key>"
  -H "Content-Type: application/json"
  -d '{"name": "editor", "description": "Content editor role"}'

GET /v1/permissions.getRole → POST /v2/permissions.getRole

v1 vs v2: Role Retrieval
# v1: GET with query parameter
curl -X GET "https://api.unkey.dev/v1/permissions.getRole?roleId=role_123"
  -H "Authorization: Bearer <your-root-key>"

# v2: POST with request body
curl -X POST https://api.unkey.com/v2/permissions.getRole
  -H "Authorization: Bearer <your-root-key>"
  -H "Content-Type: application/json"
  -d '{"name": "editor"}'

GET /v1/permissions.listRoles → POST /v2/permissions.listRoles

v1 vs v2: Role Listing
# v1: GET request
curl -X GET "https://api.unkey.dev/v1/permissions.listRoles"
  -H "Authorization: Bearer <your-root-key>"

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

POST /v1/permissions.deleteRole → POST /v2/permissions.deleteRole

v1 vs v2: Role Deletion
# v1: Delete role globally
curl -X POST https://api.unkey.dev/v1/permissions.deleteRole
  -H "Authorization: Bearer <your-root-key>"
  -H "Content-Type: application/json"
  -d '{"roleId": "role_123"}'

# v2: Delete role globally (envelope response)
curl -X POST https://api.unkey.com/v2/permissions.deleteRole
  -H "Authorization: Bearer <your-root-key>"
  -H "Content-Type: application/json"
  -d '{"name": "editor"}'

Permission Query Migration: v1 Object → v2 String Syntax

Key Change: v2 simplifies permission queries from complex object syntax to intuitive string syntax.
AND Query Migration
// v1: Object syntax for key verification
{
  "authorization": { 
    "permissions": { 
      "and": ["documents.read", "documents.write"] 
    } 
  } 
}

// v2: String syntax for key verification
{
  "permissions": "documents.read AND documents.write"
}

Migration Patterns

Response Format Migration

v1 vs v2: Standalone Permission Management
// v1: Create permission independently
const response = await fetch('/v1/permissions.createPermission', { 
  method: 'POST',
  headers: {
    'Authorization': 'Bearer <root-key>',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    name: 'documents.read',
    description: 'Read access to documents'
  })
});

const permissionData = await response.json(); // Direct response
// Permission exists independently, can be reused

// v2: Create permission independently
const permissionResponse = await fetch('/v2/permissions.createPermission', { 
  method: 'POST',
  headers: {
    'Authorization': 'Bearer <root-key>',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    name: 'documents.read',
    description: 'Read access to documents'
  })
});

const result = await permissionResponse.json(); 
const permissionData = result.data; // v2 envelope format
const requestId = result.meta.requestId; // For debugging

// Permission now exists independently, can be reused

RBAC Implementation Patterns

v1 vs v2: RBAC Implementation Patterns
// v1: Had to add permissions to each key individually
const keys = ['key_123', 'key_456', 'key_789'];
const permissions = [ 
  { name: 'documents.read', description: 'Read access' }, 
  { name: 'documents.write', description: 'Write access' } 
]; 

// Add same permissions to multiple keys
for (const keyId of keys) { 
  await fetch('/v1/keys.addPermissions', { 
    method: 'POST', 
    headers: { 
      'Authorization': 'Bearer <root-key>', 
      'Content-Type': 'application/json'
    }, 
    body: JSON.stringify({ keyId, permissions }) 
  }); 
} 

// v2: Create role once, assign to multiple keys
const roleResponse = await fetch('/v2/permissions.createRole', { 
  method: 'POST', 
  headers: { 
    'Authorization': 'Bearer <root-key>', 
    'Content-Type': 'application/json'
  }, 
  body: JSON.stringify({ 
    name: 'editor', 
    description: 'Content editor role', 
    permissions: ['documents.read', 'documents.write', 'comments.moderate'] 
  }) 
}); 

// Assign role to multiple keys (much more efficient)
for (const keyId of keys) { 
  await fetch('/v2/keys.addRoles', { 
    method: 'POST', 
    headers: { 
      'Authorization': 'Bearer <root-key>', 
      'Content-Type': 'application/json'
    }, 
    body: JSON.stringify({ keyId, roles: ['editor'] }) 
  }); 
} 

Key Benefits of v2 Permission Management

Reusable Permission Definitions

Standalone permission creation
{
  "name": "api.execute",
  "description": "Execute API operations"
}
Create permissions once, use across multiple keys and roles.

Role-Based Access Control

Role with grouped permissions
{
  "name": "api_admin",
  "description": "Full API administrative access",
  "permissions": [
    "api.execute",
    "api.read",
    "api.write",
    "api.delete",
    "users.manage"
  ]
}
Group related permissions into roles for easier management.

Auto-Creation Support

Auto-create when referenced
{
  "keyId": "key_123",
  "permissions": [
    {
      "name": "new.permission",
      "description": "This will be auto-created if it doesn't exist"
    }
  ]
}
Permissions and roles are automatically created when referenced if they don’t exist.

Simplified Query Syntax

String-based permission queries
# Simple AND
"permissions": "read AND write"

# Simple OR
"permissions": "read OR write"

# Complex with parentheses
"permissions": "read AND (write OR delete)"

# Wildcard support
"permissions": "documents.*"

Enhanced Management (Both v1 and v2)

  • List all permissions and roles in workspace
  • Delete permissions/roles globally (affects all keys)
  • Audit permission usage across keys
  • Build management dashboards with dedicated endpoints
  • v2 adds request IDs for better debugging

Migration Checklist

Pattern Migration

  • Identify current v1 permission and role usage patterns
  • Update HTTP methods (GET → POST for some endpoints)
  • Update request formats (query parameters → request body)
  • Update response parsing (direct → envelope format)

Enhanced Functionality

  • Update to v2 envelope response format with meta.requestId
  • Use enhanced pagination in list endpoints
  • Update domain from api.unkey.dev to api.unkey.com
  • Leverage auto-creation for dynamic permission scenarios

Query Syntax Migration

  • Convert object-based permission queries to string syntax in key verification
  • Update AND operations: {"and": []}"perm1 AND perm2"
  • Update OR operations: {"or": []}"perm1 OR perm2"
  • Handle complex nested queries with parentheses: "perm1 AND (perm2 OR perm3)"

Response Format Updates

  • Update response parsing from direct format to response.data
  • Extract and log meta.requestId from responses for debugging
  • Handle new error structure with meta envelope

Enhanced Features

  • Build centralized permission and role management systems
  • Implement RBAC dashboards using list endpoints
  • Use global deletion for permission/role cleanup
  • Leverage wildcard permissions for hierarchical access control

Testing

  • Test HTTP method changes (GET → POST)
  • Verify request body format vs query parameters
  • Test permission queries with new string syntax
  • Confirm envelope response format parsing
  • Validate global deletion still works correctly