Error Handling

Learn how to handle errors gracefully when using the Agent OTP SDK. All errors are typed and provide detailed information for debugging.

Error Hierarchy

All SDK errors inherit from AgentOTPError:

AgentOTPError
├── AuthenticationError
├── ValidationError
├── RateLimitError
├── TimeoutError
├── NetworkError
├── ServerError
└── OTP Errors
    ├── OTPNotFoundError
    ├── OTPExpiredError
    ├── OTPAlreadyConsumedError
    ├── OTPApprovalDeniedError
    ├── OTPCancelledError
    └── DecryptionError

Base Error

AgentOTPError

Base class for all SDK errors.

interface AgentOTPError extends Error {
  code: string;                    // Error code (e.g., 'OTP_EXPIRED')
  message: string;                 // Human-readable message
  details?: Record<string, unknown>; // Additional error details
}

Common Errors

AuthenticationError

Thrown when authentication fails (invalid or missing API key).

import { AuthenticationError } from '@orrisai/agent-otp-sdk';

try {
  await client.requestOTP({...});
} catch (error) {
  if (error instanceof AuthenticationError) {
    console.log('Auth failed:', error.message);
    // Check your API key configuration
  }
}

ValidationError

Thrown when request validation fails.

import { ValidationError } from '@orrisai/agent-otp-sdk';

try {
  await client.requestOTP({
    reason: '', // Invalid: empty string
    publicKey: 'invalid', // Invalid: malformed key
  });
} catch (error) {
  if (error instanceof ValidationError) {
    console.log('Validation failed:', error.message);
    console.log('Details:', error.details);
  }
}

RateLimitError

Thrown when rate limits are exceeded.

import { RateLimitError } from '@orrisai/agent-otp-sdk';

try {
  await client.requestOTP({...});
} catch (error) {
  if (error instanceof RateLimitError) {
    console.log('Rate limited');
    console.log('Retry after:', error.retryAfter, 'seconds');

    // Wait and retry
    await sleep(error.retryAfter! * 1000);
    await client.requestOTP({...});
  }
}

TimeoutError

Thrown when a request times out.

import { TimeoutError } from '@orrisai/agent-otp-sdk';

try {
  await client.requestOTP({
    waitForOTP: true,
    timeout: 60000, // 60 seconds
  });
} catch (error) {
  if (error instanceof TimeoutError) {
    console.log('Request timed out');
    // The OTP may still arrive - check status later
  }
}

NetworkError

Thrown when network connectivity fails.

import { NetworkError } from '@orrisai/agent-otp-sdk';

try {
  await client.requestOTP({...});
} catch (error) {
  if (error instanceof NetworkError) {
    console.log('Network error:', error.message);
    // Retry with exponential backoff
  }
}

ServerError

Thrown when the server returns a 5xx error.

import { ServerError } from '@orrisai/agent-otp-sdk';

try {
  await client.requestOTP({...});
} catch (error) {
  if (error instanceof ServerError) {
    console.log('Server error:', error.status);
    console.log('Request ID:', error.requestId);
    // Report to support with requestId
  }
}

OTP-Specific Errors

OTPNotFoundError

Thrown when no matching OTP request is found.

import { OTPNotFoundError } from '@orrisai/agent-otp-sdk';

try {
  await client.getOTPStatus('otp_invalid_id');
} catch (error) {
  if (error instanceof OTPNotFoundError) {
    console.log('OTP request not found');
  }
}

OTPExpiredError

Thrown when an OTP request has expired.

import { OTPExpiredError } from '@orrisai/agent-otp-sdk';

try {
  await client.consumeOTP(requestId, privateKey);
} catch (error) {
  if (error instanceof OTPExpiredError) {
    console.log('Request expired at:', error.expiredAt);
    // Create a new OTP request
  }
}

OTPAlreadyConsumedError

Thrown when attempting to consume an OTP that has already been read.

import { OTPAlreadyConsumedError } from '@orrisai/agent-otp-sdk';

try {
  await client.consumeOTP(requestId, privateKey);
} catch (error) {
  if (error instanceof OTPAlreadyConsumedError) {
    console.log('OTP already consumed at:', error.consumedAt);
    // OTPs are one-time use - request a new one if needed
  }
}

OTPApprovalDeniedError

Thrown when a user denies an OTP request.

import { OTPApprovalDeniedError } from '@orrisai/agent-otp-sdk';

try {
  await client.requestOTP({
    reason: 'Sign up verification',
    waitForOTP: true,
  });
} catch (error) {
  if (error instanceof OTPApprovalDeniedError) {
    console.log('User denied the request');
    console.log('Reason:', error.reason);
  }
}

OTPCancelledError

Thrown when an OTP request was cancelled.

import { OTPCancelledError } from '@orrisai/agent-otp-sdk';

try {
  await client.getOTPStatus(requestId);
} catch (error) {
  if (error instanceof OTPCancelledError) {
    console.log('Request was cancelled');
  }
}

DecryptionError

Thrown when OTP decryption fails (usually wrong private key).

import { DecryptionError } from '@orrisai/agent-otp-sdk';

try {
  await client.consumeOTP(requestId, privateKey);
} catch (error) {
  if (error instanceof DecryptionError) {
    console.log('Failed to decrypt OTP');
    // Verify you are using the correct private key
    // for the public key used in the request
  }
}

Error Codes Reference

CodeHTTPDescription
AUTHENTICATION_ERROR401Invalid or missing API key
VALIDATION_ERROR422Request validation failed
RATE_LIMIT_ERROR429Rate limit exceeded
TIMEOUT_ERROR-Request timed out
NETWORK_ERROR-Network connectivity failed
SERVER_ERROR5xxServer error
OTP_NOT_FOUND404OTP request not found
OTP_EXPIRED410OTP request has expired
OTP_ALREADY_CONSUMED410OTP already consumed
OTP_APPROVAL_DENIED403User denied OTP access
OTP_CANCELLED410OTP request cancelled
DECRYPTION_ERROR-Failed to decrypt OTP payload

Best Practices

1. Handle Specific Errors

import {
  AgentOTPError,
  AuthenticationError,
  OTPApprovalDeniedError,
  OTPExpiredError,
  DecryptionError,
  RateLimitError,
} from '@orrisai/agent-otp-sdk';

async function getOTPCode(requestId: string, privateKey: CryptoKey) {
  try {
    return await client.consumeOTP(requestId, privateKey);
  } catch (error) {
    if (error instanceof OTPApprovalDeniedError) {
      return { error: 'User denied access', reason: error.reason };
    }
    if (error instanceof OTPExpiredError) {
      return { error: 'Request expired', expiredAt: error.expiredAt };
    }
    if (error instanceof DecryptionError) {
      throw new Error('Wrong private key - check your key configuration');
    }
    if (error instanceof AuthenticationError) {
      throw new Error('Invalid API key - check your configuration');
    }
    if (error instanceof RateLimitError) {
      // Wait and retry
      await sleep(error.retryAfter! * 1000);
      return getOTPCode(requestId, privateKey);
    }
    throw error;
  }
}

2. Implement Retry Logic

async function requestOTPWithRetry(
  options: RequestOTPOptions,
  maxRetries = 3,
): Promise<OTPRequestResult> {
  let lastError: Error | undefined;

  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await client.requestOTP(options);
    } catch (error) {
      lastError = error as Error;

      if (error instanceof RateLimitError) {
        await sleep(error.retryAfter! * 1000);
        continue;
      }

      if (error instanceof NetworkError || error instanceof TimeoutError) {
        // Exponential backoff
        await sleep(Math.pow(2, attempt) * 1000);
        continue;
      }

      // Don't retry other errors
      throw error;
    }
  }

  throw lastError;
}

3. Log Errors with Context

try {
  await client.consumeOTP(requestId, privateKey);
} catch (error) {
  if (error instanceof AgentOTPError) {
    console.error('Agent OTP Error', {
      code: error.code,
      message: error.message,
      requestId, // Include context
      // Don't log sensitive data like private keys
    });
  }
  throw error;
}

See Also