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
└── DecryptionErrorBase 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
| Code | HTTP | Description |
|---|---|---|
| AUTHENTICATION_ERROR | 401 | Invalid or missing API key |
| VALIDATION_ERROR | 422 | Request validation failed |
| RATE_LIMIT_ERROR | 429 | Rate limit exceeded |
| TIMEOUT_ERROR | - | Request timed out |
| NETWORK_ERROR | - | Network connectivity failed |
| SERVER_ERROR | 5xx | Server error |
| OTP_NOT_FOUND | 404 | OTP request not found |
| OTP_EXPIRED | 410 | OTP request has expired |
| OTP_ALREADY_CONSUMED | 410 | OTP already consumed |
| OTP_APPROVAL_DENIED | 403 | User denied OTP access |
| OTP_CANCELLED | 410 | OTP 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;
}