query_metric_aggregates
Aggregate Klaviyo metrics by measurement, timeframe, and dimensions to analyze marketing performance data.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| metric_id | Yes | ID of the metric to aggregate | |
| measurement | Yes | Measurement to use (e.g., count, sum, unique) | |
| timeframe | Yes | Timeframe to use (e.g., last_30_days, this_month) | |
| group_by | No | Dimensions to group by | |
| start_date | No | Custom start date (ISO format, overrides timeframe) | |
| end_date | No | Custom end date (ISO format, overrides timeframe) |
Implementation Reference
- src/tools/reporting.js:127-284 (handler)The handler function for the 'query_metric_aggregates' tool. It validates inputs, builds the API payload for Klaviyo's metric-aggregate endpoint, handles timeframe and grouping parameters with custom logic and fallbacks, calls the API, and returns the results or error.async (params) => { try { logger.info(`Querying metric aggregates for metric ID: ${params.metric_id}`); // Validate measurement if (!VALID_MEASUREMENTS.includes(params.measurement)) { logger.warn(`Invalid measurement: ${params.measurement}. Using 'count' instead.`); params.measurement = 'count'; } // Create payload const payload = { data: { type: "metric-aggregate", attributes: { metric_id: params.metric_id, measurements: [params.measurement], interval: "day", filter: [], timezone: "UTC" } } }; // Use custom dates if provided, otherwise use timeframe parameter if (params.start_date && params.end_date) { // Use custom date range const startDateStr = params.start_date.split('T')[0]; const endDateStr = params.end_date.split('T')[0]; const dateFilters = FILTER_TEMPLATES.dateRange( `${startDateStr}T00:00:00`, `${endDateStr}T23:59:59` ); payload.data.attributes.filter.push(...dateFilters); logger.debug(`Using custom date range: ${startDateStr} to ${endDateStr}`); } else if (TIMEFRAME_OPTIONS[params.timeframe]) { // Use predefined timeframe if valid if (params.timeframe === "last_30_days") { const endDate = new Date(); const startDate = new Date(); startDate.setDate(startDate.getDate() - 30); const startDateStr = startDate.toISOString().split('T')[0]; const endDateStr = endDate.toISOString().split('T')[0]; const dateFilters = FILTER_TEMPLATES.dateRange( `${startDateStr}T00:00:00`, `${endDateStr}T23:59:59` ); payload.data.attributes.filter.push(...dateFilters); logger.debug(`Using timeframe: ${params.timeframe} (${startDateStr} to ${endDateStr})`); } else if (params.timeframe === "this_month") { const now = new Date(); const firstDay = new Date(now.getFullYear(), now.getMonth(), 1); const startDateStr = firstDay.toISOString().split('T')[0]; const endDateStr = now.toISOString().split('T')[0]; const dateFilters = FILTER_TEMPLATES.dateRange( `${startDateStr}T00:00:00`, `${endDateStr}T23:59:59` ); payload.data.attributes.filter.push(...dateFilters); logger.debug(`Using timeframe: ${params.timeframe} (${startDateStr} to ${endDateStr})`); } else { logger.debug(`Using predefined timeframe: ${params.timeframe}`); payload.data.attributes.timeframe = { key: params.timeframe }; } } else { // Default to last 7 days if timeframe is not recognized const endDate = new Date(); const startDate = new Date(); startDate.setDate(startDate.getDate() - 7); const startDateStr = startDate.toISOString().split('T')[0]; const endDateStr = endDate.toISOString().split('T')[0]; const dateFilters = FILTER_TEMPLATES.dateRange( `${startDateStr}T00:00:00`, `${endDateStr}T23:59:59` ); payload.data.attributes.filter.push(...dateFilters); logger.debug(`Using default 7-day range: ${startDateStr} to ${endDateStr}`); } if (params.group_by) { payload.data.attributes.by = params.group_by; logger.debug(`Grouping by: ${params.group_by.join(', ')}`); } logger.debug('Metric aggregates request payload', payload); // Define the fallback function const fallbackFn = async (error) => { logger.warn(`Error querying metric aggregates: ${error.message}. Attempting fallback.`); // Simplified fallback payload with minimal parameters const fallbackPayload = { data: { type: "metric-aggregate", attributes: { metric_id: params.metric_id, measurements: ["count"], // Default to count measurement interval: "day", filter: [], timezone: "UTC" } } }; // Use a default time range for the fallback (last 7 days) const endDate = new Date(); const startDate = new Date(); startDate.setDate(startDate.getDate() - 7); const startDateStr = startDate.toISOString().split('T')[0]; const endDateStr = endDate.toISOString().split('T')[0]; const dateFilters = FILTER_TEMPLATES.dateRange( `${startDateStr}T00:00:00`, `${endDateStr}T23:59:59` ); fallbackPayload.data.attributes.filter.push(...dateFilters); logger.debug('Metric aggregates fallback payload', fallbackPayload); const fallbackResults = await klaviyoClient.post('/metric-aggregates/', fallbackPayload); logger.info(`Successfully retrieved basic metric aggregates for metric ID: ${params.metric_id} using fallback`); return fallbackResults; }; // Ensure endpoint has trailing slash for consistency const results = await klaviyoClient.post('/metric-aggregates/', payload, fallbackFn); logger.info(`Successfully retrieved metric aggregates for metric ID: ${params.metric_id}`); return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] }; } catch (error) { logger.error(`Failed to query metric aggregates (including fallback attempt): ${error.message}`, { metricId: params.metric_id }); return { content: [{ type: "text", text: `Error querying metric aggregates (including fallback attempt): ${error.message}` }], isError: true }; }
- src/tools/reporting.js:119-126 (schema)Zod schema defining the input parameters for the query_metric_aggregates tool, including metric_id, measurement, timeframe, group_by, and optional date ranges.{ metric_id: z.string().describe("ID of the metric to aggregate"), measurement: z.string().describe("Measurement to use (e.g., count, sum, unique)"), timeframe: z.string().describe("Timeframe to use (e.g., last_30_days, this_month)"), group_by: z.array(z.string()).optional().describe("Dimensions to group by"), start_date: z.string().optional().describe("Custom start date (ISO format, overrides timeframe)"), end_date: z.string().optional().describe("Custom end date (ISO format, overrides timeframe)") },
- src/tools/reporting.js:117-287 (registration)The server.tool call that registers the query_metric_aggregates tool within the registerReportingTools function.server.tool( "query_metric_aggregates", { metric_id: z.string().describe("ID of the metric to aggregate"), measurement: z.string().describe("Measurement to use (e.g., count, sum, unique)"), timeframe: z.string().describe("Timeframe to use (e.g., last_30_days, this_month)"), group_by: z.array(z.string()).optional().describe("Dimensions to group by"), start_date: z.string().optional().describe("Custom start date (ISO format, overrides timeframe)"), end_date: z.string().optional().describe("Custom end date (ISO format, overrides timeframe)") }, async (params) => { try { logger.info(`Querying metric aggregates for metric ID: ${params.metric_id}`); // Validate measurement if (!VALID_MEASUREMENTS.includes(params.measurement)) { logger.warn(`Invalid measurement: ${params.measurement}. Using 'count' instead.`); params.measurement = 'count'; } // Create payload const payload = { data: { type: "metric-aggregate", attributes: { metric_id: params.metric_id, measurements: [params.measurement], interval: "day", filter: [], timezone: "UTC" } } }; // Use custom dates if provided, otherwise use timeframe parameter if (params.start_date && params.end_date) { // Use custom date range const startDateStr = params.start_date.split('T')[0]; const endDateStr = params.end_date.split('T')[0]; const dateFilters = FILTER_TEMPLATES.dateRange( `${startDateStr}T00:00:00`, `${endDateStr}T23:59:59` ); payload.data.attributes.filter.push(...dateFilters); logger.debug(`Using custom date range: ${startDateStr} to ${endDateStr}`); } else if (TIMEFRAME_OPTIONS[params.timeframe]) { // Use predefined timeframe if valid if (params.timeframe === "last_30_days") { const endDate = new Date(); const startDate = new Date(); startDate.setDate(startDate.getDate() - 30); const startDateStr = startDate.toISOString().split('T')[0]; const endDateStr = endDate.toISOString().split('T')[0]; const dateFilters = FILTER_TEMPLATES.dateRange( `${startDateStr}T00:00:00`, `${endDateStr}T23:59:59` ); payload.data.attributes.filter.push(...dateFilters); logger.debug(`Using timeframe: ${params.timeframe} (${startDateStr} to ${endDateStr})`); } else if (params.timeframe === "this_month") { const now = new Date(); const firstDay = new Date(now.getFullYear(), now.getMonth(), 1); const startDateStr = firstDay.toISOString().split('T')[0]; const endDateStr = now.toISOString().split('T')[0]; const dateFilters = FILTER_TEMPLATES.dateRange( `${startDateStr}T00:00:00`, `${endDateStr}T23:59:59` ); payload.data.attributes.filter.push(...dateFilters); logger.debug(`Using timeframe: ${params.timeframe} (${startDateStr} to ${endDateStr})`); } else { logger.debug(`Using predefined timeframe: ${params.timeframe}`); payload.data.attributes.timeframe = { key: params.timeframe }; } } else { // Default to last 7 days if timeframe is not recognized const endDate = new Date(); const startDate = new Date(); startDate.setDate(startDate.getDate() - 7); const startDateStr = startDate.toISOString().split('T')[0]; const endDateStr = endDate.toISOString().split('T')[0]; const dateFilters = FILTER_TEMPLATES.dateRange( `${startDateStr}T00:00:00`, `${endDateStr}T23:59:59` ); payload.data.attributes.filter.push(...dateFilters); logger.debug(`Using default 7-day range: ${startDateStr} to ${endDateStr}`); } if (params.group_by) { payload.data.attributes.by = params.group_by; logger.debug(`Grouping by: ${params.group_by.join(', ')}`); } logger.debug('Metric aggregates request payload', payload); // Define the fallback function const fallbackFn = async (error) => { logger.warn(`Error querying metric aggregates: ${error.message}. Attempting fallback.`); // Simplified fallback payload with minimal parameters const fallbackPayload = { data: { type: "metric-aggregate", attributes: { metric_id: params.metric_id, measurements: ["count"], // Default to count measurement interval: "day", filter: [], timezone: "UTC" } } }; // Use a default time range for the fallback (last 7 days) const endDate = new Date(); const startDate = new Date(); startDate.setDate(startDate.getDate() - 7); const startDateStr = startDate.toISOString().split('T')[0]; const endDateStr = endDate.toISOString().split('T')[0]; const dateFilters = FILTER_TEMPLATES.dateRange( `${startDateStr}T00:00:00`, `${endDateStr}T23:59:59` ); fallbackPayload.data.attributes.filter.push(...dateFilters); logger.debug('Metric aggregates fallback payload', fallbackPayload); const fallbackResults = await klaviyoClient.post('/metric-aggregates/', fallbackPayload); logger.info(`Successfully retrieved basic metric aggregates for metric ID: ${params.metric_id} using fallback`); return fallbackResults; }; // Ensure endpoint has trailing slash for consistency const results = await klaviyoClient.post('/metric-aggregates/', payload, fallbackFn); logger.info(`Successfully retrieved metric aggregates for metric ID: ${params.metric_id}`); return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] }; } catch (error) { logger.error(`Failed to query metric aggregates (including fallback attempt): ${error.message}`, { metricId: params.metric_id }); return { content: [{ type: "text", text: `Error querying metric aggregates (including fallback attempt): ${error.message}` }], isError: true }; } }, { description: "Query aggregated metric data for custom analytics reporting" } );
- src/server.js:38-38 (registration)Top-level call to registerReportingTools, which includes the registration of the query_metric_aggregates tool among other reporting tools.registerReportingTools(server);