Skip to main content
Glama

query_metric_aggregates

Aggregate Klaviyo metrics by measurement, timeframe, and dimensions to analyze marketing performance data.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
metric_idYesID of the metric to aggregate
measurementYesMeasurement to use (e.g., count, sum, unique)
timeframeYesTimeframe to use (e.g., last_30_days, this_month)
group_byNoDimensions to group by
start_dateNoCustom start date (ISO format, overrides timeframe)
end_dateNoCustom end date (ISO format, overrides timeframe)

Implementation Reference

  • 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
        };
      }
  • 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)")
    },
  • 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);

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/ivan-rivera-projects/Klaviyo-MCP-Server-Enhanced'

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