This guide covers changes to error response formats, error codes, and debugging capabilities when migrating from v1 to v2.

Overview

Error handling changes affect all API endpoints, providing improved debugging capabilities and standardized error responses.

Key Changes in v2:

  • Standardized format: All errors use {meta, error} envelope with consistent structure
  • Request IDs: Every response includes meta.requestId for debugging
  • Enhanced error structure: Errors follow RFC 7807 Problem Details format
  • Better debugging: Improved error context and troubleshooting information
For detailed information about specific error codes and troubleshooting, see the Error Documentation.

Error Response Format Changes

v1 Error Format → v2 Error Format

Error Response Migration
// v1 Error Response
{
  "error": { 
    "code": "NOT_FOUND", 
    "message": "Key not found"
  } 
}

// v2 Error Response
{
  "meta": { 
    "requestId": "req_error123abc"
  }, 
  "error": { 
    "title": "Not Found", 
    "detail": "The requested key was not found", 
    "status": 404, 
    "type": "https://unkey.com/docs/errors/unkey/data/key_not_found"
  } 
}

Error Code Mapping Table

The following table provides a comprehensive mapping of v1 error codes to their v2 equivalents:

HTTP Status Errors

v1 Error CodeHTTP Statusv2 Error Typev2 CategoryDescription
BAD_REQUEST400https://unkey.com/docs/errors/unkey/application/invalid_inputApplicationInvalid request parameters or malformed input
UNAUTHORIZED401https://unkey.com/docs/errors/unkey/authentication/key_not_foundAuthenticationMissing or invalid authentication
FORBIDDEN403https://unkey.com/docs/errors/unkey/authorization/forbiddenAuthorizationInsufficient permissions for the requested action
NOT_FOUND404https://unkey.com/docs/errors/unkey/data/key_not_foundDataRequested resource does not exist
CONFLICT409https://unkey.com/docs/errors/unkey/data/conflictDataResource conflict (e.g., duplicate creation)
PRECONDITION_FAILED412https://unkey.com/docs/errors/unkey/application/precondition_failedApplicationRequired preconditions not met
TOO_MANY_REQUESTS429https://unkey.com/docs/errors/unkey/application/rate_limitedApplicationRate limit exceeded
INTERNAL_SERVER_ERROR500https://unkey.com/docs/errors/unkey/application/internal_errorApplicationUnexpected server error
DELETE_PROTECTED403https://unkey.com/docs/errors/unkey/authorization/delete_protectedAuthorizationResource cannot be deleted due to protection rules

Key Verification Specific Codes

v1 Verification Codev2 Error TypeDescriptionMigration Notes
VALIDN/AKey is valid and verification successfulNo error - successful response
NOT_FOUNDhttps://unkey.com/docs/errors/unkey/data/key_not_foundKey does not exist or has been deletedSame as HTTP 404 NOT_FOUND
FORBIDDENhttps://unkey.com/docs/errors/unkey/authorization/forbiddenKey is not allowed to access this APISame as HTTP 403 FORBIDDEN
USAGE_EXCEEDEDhttps://unkey.com/docs/errors/unkey/data/usage_exceededKey has exceeded its usage limitNew specific error type in v2
RATE_LIMITEDhttps://unkey.com/docs/errors/unkey/application/rate_limitedKey has been rate limitedSame as HTTP 429 TOO_MANY_REQUESTS
UNAUTHORIZEDhttps://unkey.com/docs/errors/unkey/authentication/unauthorizedKey authentication failedSame as HTTP 401 UNAUTHORIZED
DISABLEDhttps://unkey.com/docs/errors/unkey/authorization/key_disabledKey has been disabledNew specific error type in v2
INSUFFICIENT_PERMISSIONShttps://unkey.com/docs/errors/unkey/authorization/insufficient_permissionsKey lacks required permissionsEnhanced RBAC error in v2
EXPIREDhttps://unkey.com/docs/errors/unkey/data/key_expiredKey has expiredNew specific error type in v2

Migration Code Examples

v1 vs v2 Error Handling
// v1: Simple error code checking
const response = await fetch('/v1/keys.verifyKey', { /* ... */ });
const data = await response.json();

if (data.error) {
  switch (data.error.code) {
    case 'NOT_FOUND':
      console.log('Key not found');
      break;
    case 'RATE_LIMITED':
      console.log('Rate limited');
      break;
    default:
      console.log('Unknown error:', data.error.message);
  }
}

// v2: RFC 7807 error handling
const response = await fetch('/v2/keys.verifyKey', { /* ... */ });
const result = await response.json();

if (result.error) {
  const { title, detail, status, type } = result.error;
  const requestId = result.meta.requestId;
  
  // Log for debugging
  console.log(`Error ${status}: ${title} - ${detail} (Request: ${requestId})`);
  
  // Handle by category
  if (type.includes('/authentication/')) {
    console.log('Authentication error');
  } else if (type.includes('/authorization/')) {
    console.log('Authorization error');
  } else if (type.includes('/data/')) {
    console.log('Data error');
  }
}

Error Documentation

For comprehensive information about specific error codes, causes, and resolution steps, refer to the error documentation:

Common Error Categories

Error Troubleshooting

  • Request IDs: Always include the meta.requestId when contacting support
  • Error Types: Use the type URL for detailed documentation about specific errors
  • Validation Errors: Check the errors array for field-specific validation failures
  • Status Codes: HTTP status codes indicate the general category of the error

Common Error Migration Issues

Problem: Error handling code not working after migration Symptoms:
  • Errors not being caught properly
  • Missing error details that were available in v1
  • Unable to determine error type or category
Solutions:
  1. Update Error Access Pattern
    // ❌ v1 pattern
    if (response.error) {
      console.log('Error:', response.error.code);
    }
    
    // ✅ v2 pattern
    if (response.error) {
      console.log('Error:', response.error.type);
      console.log('Request ID:', response.meta.requestId);
    }
    
  2. Handle New Error Structure
    // v2 error handling with all fields
    if (response.error) {
      const { title, detail, status, type } = response.error;
      
      // Log complete error information
      console.error(`${title} (${status}): ${detail}`);
      console.error(`Error Type: ${type}`);
      console.error(`Request ID: ${response.meta.requestId}`);
      
      // Handle validation errors
      if (response.error.errors) {
        response.error.errors.forEach(err => {
          console.error(`Field ${err.location}: ${err.message}`);
        });
      }
    }
    
  3. Error Categorization
    function categorizeError(errorType: string): string {
      if (errorType.includes('/authentication/')) return 'auth';
      if (errorType.includes('/authorization/')) return 'permission';
      if (errorType.includes('/application/')) return 'client';
      if (errorType.includes('/data/')) return 'resource';
      return 'unknown';
    }
    
  4. Retry Logic for Retryable Errors
    function isRetryable(errorType: string): boolean {
      return errorType.includes('rate_limited') || 
             errorType.includes('internal_error');
    }
    
    if (response.error && isRetryable(response.error.type)) {
      // Implement retry logic
      setTimeout(() => retryRequest(), 1000);
    }
    

Migration Considerations

When migrating error handling code:
  • Update error parsing to access response.error instead of direct error access
  • Extract meta.requestId for logging and support requests
  • Handle the new RFC 7807 format with title, detail, status, and type fields
  • Process validation errors from the errors array for detailed field-level feedback