Skip to main content
Question

Getting Message Metric via API

  • January 22, 2026
  • 0 replies
  • 5 views

ptrau
Contributor I
Forum|alt.badge.img+3

We wind up with too many messages that get Skipped for one reason or another. And we don’t have time to research each one at the Profile level. To improve our messaging and re-messaging we need to know why a Profile got skipped, punted, dropped, lost, whatever.

We built a Power Query in Excel using the proper metric_IDs (i.e. filter = "equals(metric_id,'XXXXXX')"  // Dropped Email), the columns are created but the values are null. A quick trip to ChatGPT and we were told that Klaviyo has three different metric levels and the ones we need aren’t available. Which raised another question, why publish a metric list if they are available for use?

I think it’s because we aren’t handling the calls right. The M Code we are using for this particular metric is below. Anyone that can point us in the right direction would highly esteemed and revered eternally. OK, maybe just a day or two, but still…..

************************************************************************

let
    // =========================
    // PARAMETERS (best practice)
    // =========================
    apiKey = pKlaviyoPrivateKey,

    // =========================
    // CONSTANTS
    // =========================
    BaseUrl  = "https://a.klaviyo.com",
    PageSize = 100,

    Headers = [
        Authorization = "Klaviyo-API-Key " & apiKey,
        accept        = "application/vnd.api+json",
        revision      = "2025-01-15"
    ],

    // =========================
    // FIRST REQUEST (EVENTS)
    // =========================
    FirstRequest = [
        RelativePath = "/api/events/",
        Query = [
            #"page[size]" = Text.From(PageSize),
            // placeholder — replace METRIC_ID later
            filter = "equals(metric_id,'R25rRH')"  // Dropped Email

        ]
    ],

    // =========================
    // FETCH ONE PAGE
    // =========================
    GetPageByRequest = (req as record) as record =>
        let
            Raw =
                Web.Contents(
                    BaseUrl,
                    [
                        RelativePath = req[RelativePath],
                        Query        = req[Query],
                        Headers      = Headers
                    ]
                ),
            Json = Json.Document(Raw)
        in
            Json,

    // =========================
    // NEXT PAGE HANDLER
    // =========================
    NextUrlToRequest = (nextUrl as nullable text) as nullable record =>
        let
            Out =
                if nextUrl = null or nextUrl = "" then
                    null
                else
                    let
                        Parts = Uri.Parts(nextUrl),
                        P     = try Parts[Path]  otherwise null,
                        Q     = try Parts[Query] otherwise null
                    in
                        if P = null then null else [RelativePath = P, Query = if Q = null then [] else Q]
        in
            Out,

    // =========================
    // PAGINATION
    // =========================
    GetAllPages = (req as record, optional acc as list) as list =>
        let
            Page    = GetPageByRequest(req),
            Data    = try Page[data]  otherwise {},
            Links   = try Page[links] otherwise [],
            NextUrl = try Links[next] otherwise null,
            NextReq = NextUrlToRequest(NextUrl),
            NewAcc  = if acc = null then Data else List.Combine({acc, Data}),
            Result  = if NextReq <> null then @GetAllPages(NextReq, NewAcc) else NewAcc
        in
            Result,

    RawList = GetAllPages(FirstRequest),

    // =========================
    // LIST → TABLE
    // =========================
    EventsTable =
        Table.FromList(
            RawList,
            Splitter.SplitByNothing(),
            {"event_record"},
            null,
            ExtraValues.Error
        ),

    // =========================
    // EXPAND CORE EVENT FIELDS
    // =========================
    Expanded =
        Table.ExpandRecordColumn(
            EventsTable,
            "event_record",
            {"id", "type", "attributes"},
            {"event_id", "event_type", "attributes"}
        ),

    ExpandedAttr =
        Table.ExpandRecordColumn(
            Expanded,
            "attributes",
            {"datetime", "properties", "profile_id", "metric_id"},
            {"event_datetime", "properties", "profile_id", "metric_id"}
        ),

    // =========================
    // SCHEMA-RESILIENT PROPERTIES
    // =========================
    DesiredProps = {
        "flow_id",
        "flow_name",
        "message_name",
        "action_id",
        "action_type",
        "reason",
        "skip_reason",
        "email",
        "phone_number",
        "campaign_name"
    },

    ExistingPropNames =
        List.Intersect({
            DesiredProps,
            List.Union(
                List.Transform(
                    Table.Column(ExpandedAttr, "properties"),
                    each if _ is record then Record.FieldNames(_) else {}
                )
            )
        }),

    ExpandedProps =
        if List.Count(ExistingPropNames) > 0 then
            Table.ExpandRecordColumn(
                ExpandedAttr,
                "properties",
                ExistingPropNames,
                ExistingPropNames
            )
        else
            ExpandedAttr,

    AddMissingProps =
        List.Accumulate(
            List.Difference(DesiredProps, ExistingPropNames),
            ExpandedProps,
            (state as table, col as text) => Table.AddColumn(state, col, each null)
        ),

    Clean =
        if List.Contains(Table.ColumnNames(AddMissingProps), "properties")
        then Table.RemoveColumns(AddMissingProps, {"properties"})
        else AddMissingProps,

    // =========================
    // REFRESH TIMESTAMP (UTC)
    // =========================
    AddRefreshTimestamp =
        Table.AddColumn(
            Clean,
            "Refresh_Timestamp",
            each DateTimeZone.UtcNow(),
            type datetimezone
        ),

    // =========================
    // SANITY CHECK
    // =========================
    RowCount = Table.RowCount(AddRefreshTimestamp),

    CheckedOutput =
        if RowCount = 0 then
            error "Klaviyo returned ZERO rows. Stopping refresh to protect dashboards."
        else
            AddRefreshTimestamp
in
    CheckedOutput

**********************************************************************************

And the obligatory screenshot:

Any help would be greatly appreciated.