Skip to main content
Glama
client.ts4.6 kB
import axios from 'axios'; import type { AxiosInstance, AxiosError, AxiosResponse, InternalAxiosRequestConfig, AxiosRequestConfig, } from 'axios'; import { enhanceApiError } from '../utils/error-enhancer.js'; import { createScopedLogger, OperationType } from '../utils/logger.js'; const MAX_RETRIES = 3; const RETRY_DELAY_MS = 1000; interface RetryableAxiosRequestConfig extends InternalAxiosRequestConfig { retryCount?: number; } export function createAttioApiClient( baseURL: string, apiKey: string ): AxiosInstance { const instance = axios.create({ baseURL, headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json', Accept: 'application/json', }, }); instance.interceptors.response.use( (response: AxiosResponse) => response, async (error: unknown) => { const log = createScopedLogger( 'api.client', 'responseInterceptor', OperationType.API_CALL ); // Enhanced logging to inspect the error object thoroughly log.error('Raw error received by interceptor', error, { isAxiosError: axios.isAxiosError(error), }); if (axios.isAxiosError(error)) { const axiosError = error as AxiosError; log.debug('Axios error config', { config: axiosError.config as unknown, }); // console.error('[Interceptor] Axios error request:', axiosError.request); // Often large, skip for brevity unless needed log.error('Axios error response details', undefined, { status: axiosError.response?.status, headers: axiosError.response?.headers as unknown, data: axiosError.response?.data as unknown, }); if (axiosError.response) { log.error('API Error', axiosError, { status: axiosError.response.status, method: axiosError.config?.method?.toUpperCase(), url: axiosError.config?.url, }); // console.error('[Interceptor] Full error.response object:', JSON.stringify(axiosError.response)); // Can be very verbose const config = (axiosError.config ?? {}) as RetryableAxiosRequestConfig; const { status } = axiosError.response; // Attempt to enhance the error first const enhancedError = enhanceApiError( axiosError as unknown as Record<string, unknown> ); // Pass the original AxiosError config.retryCount = config.retryCount || 0; // Only retry network errors, 429 (throttling), and 5xx server errors // Never retry 4xx client errors (400, 401, 403, 404, etc.) except 429 const shouldRetry = !status || status === 429 || status >= 500; if (config.retryCount < MAX_RETRIES && shouldRetry) { config.retryCount++; const target = config.url ?? '(unknown url)'; log.warn('Retrying request due to error status', { retryCount: config.retryCount, maxRetries: MAX_RETRIES, target, status, }); // Exponential backoff with jitter to prevent thundering herd const baseDelay = RETRY_DELAY_MS * Math.pow(2, config.retryCount - 1); const jitter = Math.floor(Math.random() * 300); // 0-300ms jitter const totalDelay = Math.min(baseDelay + jitter, 5000); // Cap at 5s await new Promise((resolve) => setTimeout(resolve, totalDelay)); // Convert InternalAxiosRequestConfig to AxiosRequestConfig before retrying const retryConfig: AxiosRequestConfig = { ...config }; return instance.request(retryConfig); } return Promise.reject(enhancedError); // Reject with the potentially enhanced error } } else { log.error('Non-Axios error structure', error); } // Fallback for non-Axios errors or Axios errors without a response return Promise.reject(error); } ); return instance; } // Example of creating a default client (ensure env variables are set up) // const defaultBaseURL = process.env.ATTIO_API_BASE_URL || 'https://api.attio.com/v2'; // const defaultApiKey = process.env.ATTIO_API_KEY; // if (!defaultApiKey) { // console.warn('ATTIO_API_KEY is not set. Default Attio API client will not be functional.'); // } // export const attio = defaultApiKey ? createAttioApiClient(defaultBaseURL, defaultApiKey) : null; // If you had a global Attio instance like this, re-add it. For now, just exporting the factory.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/kesslerio/attio-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server