Skip to content

Error Handling

All SDK methods throw ConfigureError on failure. Each error carries structured properties — a machine-readable code for programmatic handling, metadata about retryability and suggested actions, and a human-readable message for logging.

ConfigureError

PropertyTypeDescription
codeErrorCodeMachine-readable error code for switch/case handling
statusCodenumber | undefinedHTTP status code from the API response
messagestringHuman-readable description of what went wrong
typestring | undefinedBackend error type (e.g., "authentication_error", "tool_error")
paramstring | nullThe field or parameter that caused the error
retryableboolean | undefinedWhether the same request might succeed if retried
suggestedActionstring | nullMachine-readable next action (e.g., "reauthenticate", "connect_tool")
docUrlstring | nullLink to error documentation
retryAfternumber | nullSeconds to wait before retrying (rate limits)
requestIdstring | undefinedBackend request ID for debugging and support

The SDK normalizes all exceptions — network failures, HTTP errors, timeouts — into ConfigureError instances before they reach your code. You never need to handle raw fetch errors or HTTP responses.

Error Codes

CodeHTTP StatusDescription
API_KEY_MISSING--No API key provided to ConfigureClient
AUTH_REQUIRED401, 403Token is missing, invalid, or expired
INVALID_INPUT400Input failed validation before reaching the API
TOOL_NOT_CONNECTED400The requested tool is not connected for this user
ACCESS_DENIED403Not authorized for this resource (distinct from AUTH_REQUIRED)
TOOL_ERRORvariesTool operation failed (provider error, misconfiguration)
PAYMENT_REQUIRED402Billing or quota limit reached
NETWORK_ERROR--Network request failed (DNS, connection refused, etc.)
RATE_LIMITED429Too many requests
NOT_FOUND404Requested resource does not exist
SERVER_ERROR5xxInternal server error
TIMEOUT--Request did not complete within the timeout window

Structured Error Details

The Configure backend returns structured error responses:

json
{
  "error": {
    "type": "tool_error",
    "code": "tool_not_connected",
    "message": "Gmail is not connected for this user.",
    "param": "tool",
    "retryable": false,
    "suggested_action": "connect_tool",
    "doc_url": "https://docs.configure.dev/errors/tool_not_connected"
  },
  "request_id": "req_7f2a9b4c"
}

The SDK parses these automatically via ConfigureError.fromResponse() — you just use the properties on the caught error. The backend uses 6 error types that map to SDK error codes:

Backend TypeSDK Code(s)
authentication_errorAUTH_REQUIRED
invalid_request_errorINVALID_INPUT
permission_errorACCESS_DENIED
tool_errorTOOL_NOT_CONNECTED or TOOL_ERROR
rate_limit_errorRATE_LIMITED or PAYMENT_REQUIRED
api_errorSERVER_ERROR

Use the structured properties for smarter error handling:

typescript
if (error instanceof ConfigureError) {
  if (error.retryable) {
    // Safe to retry — network issue, rate limit, or server error
  }
  if (error.suggestedAction === 'connect_tool') {
    // Show the <configure-connection-list> component
  }
  if (error.param) {
    // Highlight the specific field that caused the error
  }
  console.log(`Request ${error.requestId}: [${error.type}/${error.code}] ${error.message}`);
}

classifyError()

The classifyError() utility classifies any caught error — from the SDK, Anthropic, network failures, or plain strings — into a ConfigureError with a friendly user-facing message. Use it in agent catch blocks where errors can come from multiple sources.

typescript
import { classifyError } from 'configure';

try {
  // SDK calls, Anthropic calls, or any other operation
  const profile = await client.profile.get(token, userId);
  const response = await anthropic.messages.create({ ... });
} catch (error) {
  const classified = classifyError(error);
  // classified.code — e.g., ErrorCode.RATE_LIMITED
  // classified.message — e.g., "taking a breather — try again in a moment."
  sendToUser(classified.message);
}

The friendly messages are concise and safe to show to end users:

CodeFriendly Message
AUTH_REQUIRED"your session expired. please sign in again."
RATE_LIMITED"taking a breather — try again in a moment."
TIMEOUT"request timed out. try again."
NETWORK_ERROR"connection issue. check your network and try again."
SERVER_ERROR"something went wrong. try again."
ACCESS_DENIED"you don't have permission to do that."
TOOL_ERROR"something went wrong with the tool. try again."
PAYMENT_REQUIRED"usage quota exceeded. check your plan."

Catching Errors

typescript
import { ConfigureClient, ConfigureError, ErrorCode } from 'configure';

const client = new ConfigureClient({ apiKey: 'sk_your_api_key' });

try {
  const profile = await client.profile.get(token, userId);
} catch (error) {
  if (error instanceof ConfigureError) {
    switch (error.code) {
      case ErrorCode.AUTH_REQUIRED:
        // Token expired — re-authenticate the user
        // error.suggestedAction === 'reauthenticate'
        break;
      case ErrorCode.TOOL_NOT_CONNECTED:
        // Prompt user to connect the tool
        // error.suggestedAction === 'connect_tool'
        // error.param === 'tool'
        break;
      case ErrorCode.ACCESS_DENIED:
        // User doesn't have permission for this resource
        // error.suggestedAction === 'check_permissions'
        break;
      case ErrorCode.RATE_LIMITED:
        // Back off and retry
        // error.retryable === true
        // error.retryAfter — seconds to wait
        break;
      case ErrorCode.PAYMENT_REQUIRED:
        // Quota exceeded — upgrade plan
        // error.suggestedAction === 'upgrade_plan'
        break;
      default:
        console.error(`[${error.code}] ${error.message}`);
    }
  }
}

Input Validation

The SDK validates inputs locally before making API calls. Invalid inputs throw INVALID_INPUT immediately, without a network round-trip.

Validated inputs:

  • Phone numbers — must be E.164 format (e.g., +14155551234)
  • OTP codes — must be exactly 6 digits
  • Storage paths — must not contain ../ (path traversal prevention)
  • Tool types — must be one of: gmail, calendar, drive, notion
  • Required fields — must be non-empty strings
typescript
try {
  await client.auth.sendOtp('bad-number');
} catch (error) {
  if (error instanceof ConfigureError && error.code === ErrorCode.INVALID_INPUT) {
    // 'Invalid phone number "bad-number". Must be in E.164 format (e.g., "+14155551234").'
  }
}

Common Errors and Solutions

AUTH_REQUIRED

The user's token is missing, expired, or invalid (HTTP 401/403).

Solution: Re-run the OTP authentication flow to obtain a fresh token. If you cache tokens, check expiration before use.

typescript
async function withAuth<T>(
  fn: (token: string) => Promise<T>,
  token: string,
  refreshToken: () => Promise<string>,
): Promise<T> {
  try {
    return await fn(token);
  } catch (error) {
    if (error instanceof ConfigureError && error.code === ErrorCode.AUTH_REQUIRED) {
      const newToken = await refreshToken();
      return fn(newToken);
    }
    throw error;
  }
}

TOOL_NOT_CONNECTED

A tool method was called (e.g., tools.searchEmails()) but the user has not connected that service.

Solution: Check the user's profile for connected tools before calling tool methods. Prompt the user to connect via OAuth if the tool is not linked.

ACCESS_DENIED

The authenticated user or agent does not have permission to access the requested resource (HTTP 403). This is distinct from AUTH_REQUIRED — the credentials are valid but insufficient.

Solution: Check the user's permission settings. For cross-agent reads, verify the user has granted access to your agent.

TOOL_ERROR

A connected tool (Gmail, Calendar, Drive, Notion) returned an error during an operation. This is a provider-side issue, not a Configure issue.

Solution: This error is retryable (retryable: true). Retry after a short delay. If persistent, the user may need to reconnect the tool.

PAYMENT_REQUIRED

The developer account has exceeded its billing quota (HTTP 402).

Solution: Check usage in the developer dashboard. Upgrade your plan to increase limits.

API_KEY_MISSING

The client was constructed without an API key.

Solution: Pass your API key when creating the client. Obtain keys from the developer dashboard.

RATE_LIMITED

Too many requests in a short period (HTTP 429).

Solution: Use error.retryAfter for the recommended wait time. See Retry Patterns below.

NOT_FOUND

The requested resource does not exist (HTTP 404).

Solution: Verify the user ID, storage path, or resource identifier is correct.

SERVER_ERROR

The Configure API returned a 5xx status code.

Solution: Retry with exponential backoff. If the error persists, check the status page.

Retry Patterns

Use the retryable property to determine if a retry is safe:

CodeRetryableStrategy
API_KEY_MISSINGNoFix client configuration
AUTH_REQUIREDNoRe-authenticate the user
INVALID_INPUTNoFix the input
TOOL_NOT_CONNECTEDNoPrompt user to connect the tool
ACCESS_DENIEDNoCheck permissions
TOOL_ERRORYesRetry — provider issue
PAYMENT_REQUIREDNoUpgrade plan
NETWORK_ERRORYesExponential backoff
RATE_LIMITEDYesWait retryAfter seconds, then retry
NOT_FOUNDNoVerify the resource exists
SERVER_ERRORYesExponential backoff
TIMEOUTYesExponential backoff

Exponential Backoff

typescript
import { ConfigureError, ErrorCode } from 'configure';

async function withRetry<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (
        error instanceof ConfigureError &&
        error.retryable &&
        attempt < maxRetries
      ) {
        // Use retryAfter if available (rate limits), otherwise exponential backoff
        const delay = error.retryAfter
          ? error.retryAfter * 1000
          : Math.min(1000 * 2 ** attempt, 30_000);
        await new Promise((r) => setTimeout(r, delay));
        continue;
      }
      throw error;
    }
  }
  throw new Error('Unreachable');
}

// Usage
const profile = await withRetry(() => client.profile.get(token, userId));

Tips:

  • Cap backoff at 30 seconds to avoid stalling your application.
  • Add jitter in high-concurrency scenarios: delay * (0.5 + Math.random()).
  • Three retries is a reasonable default. More than five usually indicates a persistent issue.
  • Never retry AUTH_REQUIRED or INVALID_INPUT — the same request will always fail.
  • Use error.retryable instead of maintaining your own set of retryable codes.

Identity and memory infrastructure for AI agents