Skip to main content
Glama
client.ts15.5 kB
const API_BASE = 'https://api-metrika.yandex.net'; const API_MANAGMENT = 'management/v1'; const API_REPORTS = 'stat/v1/data'; interface ClientConfig { timeout?: number; retries?: number; retryDelay?: number; } export class YandexMetrikaClient { private token: string; private config: Required<ClientConfig>; constructor(token: string, config: ClientConfig = {}) { this.token = token; this.config = { timeout: config.timeout || 30000, retries: config.retries || 3, retryDelay: config.retryDelay || 1000, }; } private async makeRequest<T>(url: string, attempt: number = 1): Promise<T> { try { const controller = new AbortController(); const timeoutId = setTimeout( () => controller.abort(), this.config.timeout ); const response = await fetch(url, { headers: { Authorization: `OAuth ${this.token}`, 'Content-Type': 'application/json', }, signal: controller.signal, }); clearTimeout(timeoutId); if (!response.ok) { const errorText = await response.text(); throw new Error( `Yandex Metrika error ${response.status}: ${errorText}` ); } return response.json(); } catch (error) { if (attempt < this.config.retries && this.isRetryableError(error)) { await this.delay(this.config.retryDelay * attempt); return this.makeRequest<T>(url, attempt + 1); } throw error; } } private isRetryableError(error: any): boolean { return ( error.name === 'AbortError' || (error.message && error.message.includes('500')) || (error.message && error.message.includes('502')) || (error.message && error.message.includes('503')) ); } private delay(ms: number): Promise<void> { return new Promise((resolve) => setTimeout(resolve, ms)); } async getAccountInfo(counterId: string): Promise<any> { if (!counterId || typeof counterId !== 'string') { throw new Error('Counter ID must be a non-empty string'); } const url = `${API_BASE}/${API_MANAGMENT}/counter/${counterId}`; return this.makeRequest(url); } async getVisits( counterId: string, dateFrom?: string, dateTo?: string ): Promise<any> { if (!counterId || typeof counterId !== 'string') { throw new Error('Counter ID must be a non-empty string'); } // Валидация дат if (dateFrom && !this.isValidDate(dateFrom)) { throw new Error('dateFrom must be in YYYY-MM-DD format'); } if (dateTo && !this.isValidDate(dateTo)) { throw new Error('dateTo must be in YYYY-MM-DD format'); } const url = `${API_BASE}/${API_REPORTS}?ids=${counterId}&metrics=ym:s:visits`; // const url = `${API_BASE}/${API_REPORTS}?ids=${counterId}&metrics=ym:s:visits&date1=${dateFrom}&date2=${dateTo}`; return this.makeRequest(url); } private isValidDate(dateString: string): boolean { const dateRegex = /^\d{4}-\d{2}-\d{2}$/; if (!dateRegex.test(dateString)) { return false; } const date = new Date(dateString); return date instanceof Date && !isNaN(date.getTime()); } async getSourcesSummary(counterId: string): Promise<any> { if (!counterId || typeof counterId !== 'string') { throw new Error('Counter ID must be a non-empty string'); } let url = `${API_BASE}/${API_REPORTS}?preset=sources_summary&id=${counterId}`; return this.makeRequest(url); } async getSourcesSearchPhrases(counterId: string): Promise<any> { if (!counterId || typeof counterId !== 'string') { throw new Error('Counter ID must be a non-empty string'); } const url = `${API_BASE}/${API_REPORTS}?preset=sources_search_phrases&id=${counterId}`; return this.makeRequest(url); } async getBrowsersReport(counterId: string): Promise<any> { if (!counterId || typeof counterId !== 'string') { throw new Error('Counter ID must be a non-empty string'); } const url = `${API_BASE}/${API_REPORTS}?preset=tech_platforms&dimensions=ym:s:browser&id=${counterId}`; return this.makeRequest(url); } async getContentAnalyticsSources(counterId: string, dateFrom?: string, dateTo?: string): Promise<any> { if (!counterId || typeof counterId !== 'string') { throw new Error('Counter ID must be a non-empty string'); } let url = `${API_BASE}/${API_REPORTS}?preset=publishers_sources&id=${counterId}`; if (dateFrom) url += `&date1=${dateFrom}`; if (dateTo) url += `&date2=${dateTo}`; return this.makeRequest(url); } async getContentAnalyticsCategories(counterId: string, dateFrom?: string, dateTo?: string): Promise<any> { if (!counterId || typeof counterId !== 'string') { throw new Error('Counter ID must be a non-empty string'); } let url = `${API_BASE}/${API_REPORTS}?preset=publishers_rubrics&id=${counterId}`; if (dateFrom) url += `&date1=${dateFrom}`; if (dateTo) url += `&date2=${dateTo}`; return this.makeRequest(url); } async getContentAnalyticsAuthors(counterId: string, dateFrom?: string, dateTo?: string): Promise<any> { if (!counterId || typeof counterId !== 'string') { throw new Error('Counter ID must be a non-empty string'); } let url = `${API_BASE}/${API_REPORTS}?preset=publishers_authors&id=${counterId}`; if (dateFrom) url += `&date1=${dateFrom}`; if (dateTo) url += `&date2=${dateTo}`; return this.makeRequest(url); } async getContentAnalyticsTopics(counterId: string, dateFrom?: string, dateTo?: string): Promise<any> { if (!counterId || typeof counterId !== 'string') { throw new Error('Counter ID must be a non-empty string'); } let url = `${API_BASE}/${API_REPORTS}?preset=publishers_thematics&id=${counterId}`; if (dateFrom) url += `&date1=${dateFrom}`; if (dateTo) url += `&date2=${dateTo}`; return this.makeRequest(url); } async getTrafficSourcesTypes(counterId: string): Promise<any> { if (!counterId || typeof counterId !== 'string') { throw new Error('Counter ID must be a non-empty string'); } const url = `${API_BASE}/${API_REPORTS}?dimensions=ym:s:lastTrafficSource&metrics=ym:s:visits,ym:s:users&filters=ym:s:lastTrafficSource=.('organic','direct','referral')&id=${counterId}&lang=ru`; return this.makeRequest(url); } async getSearchEnginesData(counterId: string, excludeRobots: boolean = false, newUsersOnly: boolean = false): Promise<any> { if (!counterId || typeof counterId !== 'string') { throw new Error('Counter ID must be a non-empty string'); } let filters = 'ym:s:trafficSource==\'organic\''; if (excludeRobots) { filters += ' AND ym:s:isRobot==\'No\''; } if (newUsersOnly) { filters += ' AND ym:s:isNewUser==\'Yes\''; } const url = `${API_BASE}/${API_REPORTS}?dimensions=ym:s:searchEngine&metrics=ym:s:visits,ym:s:users&filters=${encodeURIComponent(filters)}&id=${counterId}`; return this.makeRequest(url); } async getRegionalData(counterId: string, cities: string[] = ['Москва', 'Санкт-Петербург']): Promise<any> { if (!counterId || typeof counterId !== 'string') { throw new Error('Counter ID must be a non-empty string'); } const citiesFilter = cities.map(city => `'${city}'`).join(','); const filters = `ym:s:regionCityName=.(${citiesFilter})`; const url = `${API_BASE}/${API_REPORTS}?metrics=ym:s:visits,ym:s:users&filters=${encodeURIComponent(filters)}&id=${counterId}&lang=ru`; return this.makeRequest(url); } async getPageDepthAnalysis(counterId: string, minPages: number = 5): Promise<any> { if (!counterId || typeof counterId !== 'string') { throw new Error('Counter ID must be a non-empty string'); } const filters = `ym:s:pageViews>${minPages}`; const url = `${API_BASE}/${API_REPORTS}?metrics=ym:s:visits&filters=${encodeURIComponent(filters)}&id=${counterId}`; return this.makeRequest(url); } async getGoalsConversion(counterId: string, goalIds: number[]): Promise<any> { if (!counterId || typeof counterId !== 'string') { throw new Error('Counter ID must be a non-empty string'); } const metrics = goalIds.map(id => `ym:s:goal${id}conversionRate`).join(','); const url = `${API_BASE}/${API_REPORTS}?metrics=ym:s:users,${metrics}&id=${counterId}`; return this.makeRequest(url); } async getUserDemographics(counterId: string, dateFrom?: string, dateTo?: string): Promise<any> { if (!counterId || typeof counterId !== 'string') { throw new Error('Counter ID must be a non-empty string'); } let url = `${API_BASE}/${API_REPORTS}?dimensions=ym:s:ageInterval,ym:s:gender,ym:s:deviceCategory&metrics=ym:s:visits,ym:s:users,ym:s:pageviews,ym:s:bounceRate,ym:s:avgVisitDurationSeconds&id=${counterId}`; if (dateFrom) url += `&date1=${dateFrom}`; if (dateTo) url += `&date2=${dateTo}`; return this.makeRequest(url); } async getDeviceAnalysis(counterId: string, dateFrom?: string, dateTo?: string): Promise<any> { if (!counterId || typeof counterId !== 'string') { throw new Error('Counter ID must be a non-empty string'); } let url = `${API_BASE}/${API_REPORTS}?dimensions=ym:s:browser,ym:s:operatingSystem&metrics=ym:s:visits,ym:s:pageviews,ym:s:bounceRate,ym:s:avgVisitDurationSeconds&id=${counterId}`; if (dateFrom) url += `&date1=${dateFrom}`; if (dateTo) url += `&date2=${dateTo}`; return this.makeRequest(url); } async getPagePerformance(counterId: string, dateFrom?: string, dateTo?: string): Promise<any> { if (!counterId || typeof counterId !== 'string') { throw new Error('Counter ID must be a non-empty string'); } let url = `${API_BASE}/${API_REPORTS}?dimensions=ym:s:URLPath&metrics=ym:s:pageviews,ym:s:bounceRate,ym:s:avgVisitDurationSeconds&id=${counterId}`; if (dateFrom) url += `&date1=${dateFrom}`; if (dateTo) url += `&date2=${dateTo}`; return this.makeRequest(url); } async getOrganicSearchPerformance(counterId: string, dateFrom?: string, dateTo?: string): Promise<any> { if (!counterId || typeof counterId !== 'string') { throw new Error('Counter ID must be a non-empty string'); } let url = `${API_BASE}/${API_REPORTS}?dimensions=ym:s:searchEngine,ym:s:searchPhrase&metrics=ym:s:visits,ym:s:users,ym:s:pageviews&filters=ym:s:trafficSource==\'organic\'&id=${counterId}`; if (dateFrom) url += `&date1=${dateFrom}`; if (dateTo) url += `&date2=${dateTo}`; return this.makeRequest(url); } async getNewUsersBySource(counterId: string, dateFrom?: string, dateTo?: string): Promise<any> { if (!counterId || typeof counterId !== 'string') { throw new Error('Counter ID must be a non-empty string'); } let url = `${API_BASE}/${API_REPORTS}?dimensions=ym:s:trafficSource&metrics=ym:s:newUsers&id=${counterId}`; if (dateFrom) url += `&date1=${dateFrom}`; if (dateTo) url += `&date2=${dateTo}`; return this.makeRequest(url); } async getMobileVsDesktop(counterId: string, dateFrom?: string, dateTo?: string): Promise<any> { if (!counterId || typeof counterId !== 'string') { throw new Error('Counter ID must be a non-empty string'); } let url = `${API_BASE}/${API_REPORTS}?dimensions=ym:s:deviceCategory&metrics=ym:s:visits,ym:s:users,ym:s:pageviews,ym:s:bounceRate,ym:s:avgVisitDurationSeconds&id=${counterId}`; if (dateFrom) url += `&date1=${dateFrom}`; if (dateTo) url += `&date2=${dateTo}`; return this.makeRequest(url); } async getGeographicalOrganicTraffic(counterId: string, dateFrom?: string, dateTo?: string): Promise<any> { if (!counterId || typeof counterId !== 'string') { throw new Error('Counter ID must be a non-empty string'); } let url = `${API_BASE}/${API_REPORTS}?dimensions=ym:s:regionCountry,ym:s:regionCity&metrics=ym:s:visits,ym:s:users&filters=ym:s:trafficSource==\'organic\'&id=${counterId}`; if (dateFrom) url += `&date1=${dateFrom}`; if (dateTo) url += `&date2=${dateTo}`; return this.makeRequest(url); } async getYandexDirectExperiment(counterId: string, experimentId: number): Promise<any> { if (!counterId || typeof counterId !== 'string') { throw new Error('Counter ID must be a non-empty string'); } const url = `${API_BASE}/${API_REPORTS}?metrics=ym:s:bounceRate&dimensions=ym:s:experimentAB${experimentId}&id=${counterId}`; return this.makeRequest(url); } async getContentAnalyticsArticles(counterId: string, dateFrom?: string, dateTo?: string): Promise<any> { if (!counterId || typeof counterId !== 'string') { throw new Error('Counter ID must be a non-empty string'); } let url = `${API_BASE}/${API_REPORTS}?ids=${counterId}&dimensions=ym:s:publisherArticle&metrics=ym:s:publisherviews&filters=(ym:s:publisherArticle!n)&sort=-ym:s:publisherviews`; if (dateFrom) url += `&date1=${dateFrom}`; if (dateTo) url += `&date2=${dateTo}`; return this.makeRequest(url); } async getDataByTime( counterId: string, metrics: string[], dateFrom?: string, dateTo?: string, dimensions?: string[], group: 'day' | 'week' | 'month' | 'quarter' | 'year' = 'day', topKeys: number = 7, timezone?: string ): Promise<any> { if (!counterId || typeof counterId !== 'string') { throw new Error('Counter ID must be a non-empty string'); } if (!metrics || metrics.length === 0) { throw new Error('At least one metric is required'); } if (metrics.length > 20) { throw new Error('Maximum 20 metrics allowed per request'); } if (dimensions && dimensions.length > 10) { throw new Error('Maximum 10 dimensions allowed per request'); } let url = `${API_BASE}/${API_REPORTS}/bytime?ids=${counterId}&metrics=${metrics.join(',')}&group=${group}&top_keys=${topKeys}`; if (dateFrom) url += `&date1=${dateFrom}`; if (dateTo) url += `&date2=${dateTo}`; if (dimensions && dimensions.length > 0) url += `&dimensions=${dimensions.join(',')}`; if (timezone) url += `&timezone=${encodeURIComponent(timezone)}`; return this.makeRequest(url); } async getEcommercePerformance( counterId: string, currency: string = 'RUB', dateFrom?: string, dateTo?: string ): Promise<any> { if (!counterId || typeof counterId !== 'string') { throw new Error('Counter ID must be a non-empty string'); } let url = `${API_BASE}/${API_REPORTS}?dimensions=ym:s:productCategory,ym:s:regionCountry,ym:s:regionCity&metrics=ym:s:ecommercePurchases,ym:s:ecommerce${currency}ConvertedRevenue&id=${counterId}`; if (dateFrom) url += `&date1=${dateFrom}`; if (dateTo) url += `&date2=${dateTo}`; return this.makeRequest(url); } async getConversionRateBySourceAndLanding( counterId: string, goalId: number, dateFrom?: string, dateTo?: string ): Promise<any> { if (!counterId || typeof counterId !== 'string') { throw new Error('Counter ID must be a non-empty string'); } let url = `${API_BASE}/${API_REPORTS}?dimensions=ym:s:trafficSource,ym:s:landingPage&metrics=ym:s:visits,ym:s:goal${goalId}conversionRate&id=${counterId}`; if (dateFrom) url += `&date1=${dateFrom}`; if (dateTo) url += `&date2=${dateTo}`; return this.makeRequest(url); } }

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/Vadosdavos/yandex-metrika-mcp'

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