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
| Property | Type | Description |
|---|---|---|
code | ErrorCode | Machine-readable error code for switch/case handling |
statusCode | number | undefined | HTTP status code from the API response |
message | string | Human-readable description of what went wrong |
type | string | undefined | Backend error type (e.g., "authentication_error", "tool_error") |
param | string | null | The field or parameter that caused the error |
retryable | boolean | undefined | Whether the same request might succeed if retried |
suggestedAction | string | null | Machine-readable next action (e.g., "reauthenticate", "connect_tool") |
docUrl | string | null | Link to error documentation |
retryAfter | number | null | Seconds to wait before retrying (rate limits) |
requestId | string | undefined | Backend 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
| Code | HTTP Status | Description |
|---|---|---|
API_KEY_MISSING | -- | No API key provided to ConfigureClient |
AUTH_REQUIRED | 401, 403 | Token is missing, invalid, or expired |
INVALID_INPUT | 400 | Input failed validation before reaching the API |
TOOL_NOT_CONNECTED | 400 | The requested tool is not connected for this user |
ACCESS_DENIED | 403 | Not authorized for this resource (distinct from AUTH_REQUIRED) |
TOOL_ERROR | varies | Tool operation failed (provider error, misconfiguration) |
PAYMENT_REQUIRED | 402 | Billing or quota limit reached |
NETWORK_ERROR | -- | Network request failed (DNS, connection refused, etc.) |
RATE_LIMITED | 429 | Too many requests |
NOT_FOUND | 404 | Requested resource does not exist |
SERVER_ERROR | 5xx | Internal 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 Type | SDK Code(s) |
|---|---|
authentication_error | AUTH_REQUIRED |
invalid_request_error | INVALID_INPUT |
permission_error | ACCESS_DENIED |
tool_error | TOOL_NOT_CONNECTED or TOOL_ERROR |
rate_limit_error | RATE_LIMITED or PAYMENT_REQUIRED |
api_error | SERVER_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:
| Code | Friendly 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:
| Code | Retryable | Strategy |
|---|---|---|
API_KEY_MISSING | No | Fix client configuration |
AUTH_REQUIRED | No | Re-authenticate the user |
INVALID_INPUT | No | Fix the input |
TOOL_NOT_CONNECTED | No | Prompt user to connect the tool |
ACCESS_DENIED | No | Check permissions |
TOOL_ERROR | Yes | Retry — provider issue |
PAYMENT_REQUIRED | No | Upgrade plan |
NETWORK_ERROR | Yes | Exponential backoff |
RATE_LIMITED | Yes | Wait retryAfter seconds, then retry |
NOT_FOUND | No | Verify the resource exists |
SERVER_ERROR | Yes | Exponential backoff |
TIMEOUT | Yes | Exponential 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_REQUIREDorINVALID_INPUT— the same request will always fail. - Use
error.retryableinstead of maintaining your own set of retryable codes.