Integrate an analytics and reporting platform
Learn how to integrate an analytics, attribution, or BI platform with Klaviyo.
Overview
This resource will help you as you build an integration between an analytics, attribution, or business intelligence (BI) platform and Klaviyo. Unlike most integrations in this section, this one is primarily a read integration: you pull Klaviyo's performance, event, and profile data out to build dashboards, attribution models, and warehouses. This guide walks through what to pull and from where, the Reporting API, pull patterns, an optional write-back, and the authentication requirements, with an API quick reference at the end.
Why connect your analytics platform to Klaviyo
Email and SMS are among the highest-ROI channels a brand runs, and for most merchants Klaviyo is where that revenue is generated and measured. An analytics or attribution platform that can't see Klaviyo's numbers is blind to a huge share of owned-channel revenue. Pulling Klaviyo data in gives merchants the complete picture:
- Blended ROAS, not paid-only. Combining Klaviyo campaign and flow revenue with ad spend gives true return on marketing, not just paid-media return.
- LTV and CAC by acquisition source. Joining Klaviyo profile and order data against first-touch channel answers which sources produce the most valuable customers.
- Owned-channel performance in the warehouse. Landing campaign, flow, and form reporting in a data warehouse lets merchants build custom BI alongside every other source.
- Cross-channel attribution. Klaviyo engagement as an input to a multi-touch model closes the gap between "sent an email" and "drove a sale."
- Stickier merchants. When a merchant's core dashboards depend on Klaviyo data, the integration rarely gets turned off.
Short version: pulling Klaviyo's campaign, flow, and metric performance into your platform lets the merchant measure owned-channel revenue alongside everything else they track.
The flow
Primarily pull, your platform reading from Klaviyo. Klaviyo is the source of truth for engagement and profile data; your platform is the consumer. There are two things to get right:
- Reporting reads for aggregated performance. Campaign, flow, form, and segment reports, plus raw metric aggregation. This is the heart of the integration.
- Object reads for the underlying records. Events, profiles, lists, segments, and the metadata that ties reports back to named campaigns and flows.
Identity matching is your problem, not Klaviyo's. Klaviyo returns profiles keyed by email, phone, or external ID; you join that to your own customer and order graph, usually on email or a shared external ID. There's no PII-hashing handoff as there is for an ad platform; you just need a stable join key.
An optional write-back exists and is common: after your model runs, write computed attributes (LTV, predicted value, attribution source) back onto the Klaviyo profile so the merchant can segment on them. See Optional write-back below. "Mostly read" does not mean "read only."
What to pull
| Data you want | Endpoint | Scope |
|---|---|---|
| Campaign performance | POST /api/campaign-values-reports/ | campaigns:read |
| Flow performance (totals) | POST /api/flow-values-reports/ | flows:read |
| Flow performance (time series) | POST /api/flow-series-reports/ | flows:read |
| Form performance | POST /api/form-values-reports/, POST /api/form-series-reports/ | forms:read |
| Segment size and growth | POST /api/segment-values-reports/, POST /api/segment-series-reports/ | segments:read |
| Raw event aggregation | POST /api/metric-aggregates/ | metrics:read |
| Metric catalog (get metric IDs) | GET /api/metrics/ | metrics:read |
| Raw events (timeline) | GET /api/events/ | events:read |
| Profiles | GET /api/profiles/ | profiles:read |
| Lists | GET /api/lists/ | lists:read |
| Segment metadata | GET /api/segments/ | segments:read |
| Campaign / flow metadata | GET /api/campaigns/, GET /api/flows/ | campaigns:read / flows:read |
There is no separate reports:read scope. Each Reporting API endpoint reuses the scope of its underlying resource: a campaign report needs campaigns:read, a flow report needs flows:read, and so on.
The Reporting API
The Reporting API is the heart of this integration. It returns the same numbers the merchant sees in the Klaviyo UI, which is exactly what you want for anything they'll compare against Klaviyo directly.
Reports come in two shapes:
- Values reports return totals over a timeframe.
- Series reports return the same statistics broken into buckets by an
interval(daily,weekly,monthly, and so on), with adate_timesarray to align against.
Every reporting endpoint is a POST. The request body (under data.attributes) takes:
statistics(required): the metrics to return, for exampleopens_unique,click_rate,conversion_value,revenue_per_recipient.timeframe(required): either a key like{ "key": "last_30_days" }or a custom{ "start", "end" }range, up to a one-year span.conversion_metric_id(required): the metric ID that defines a conversion, typically Placed Order. Get it fromGET /api/metrics/.filter(optional): a JSON:API filter string, one condition per attribute, combined with AND only.group_by(optional): dimensions such ascampaign_idandsend_channel.interval(series reports only).
The response is a results array of { groupings, statistics }, with relationships back to the named campaigns, flows, and messages so you can resolve IDs to human-readable names.
Reporting API versus Query Metric Aggregates
There are two ways to get aggregated numbers, and they don't reconcile, which is the single most common source of confusion:
- The Reporting API groups by send date, matching the Klaviyo UI. Use it for anything a merchant will compare against Klaviyo's own dashboards.
- Query Metric Aggregates (
POST /api/metric-aggregates/) groups by event-occurrence time, and lets you roll up arbitrary event data by custom dimensions ($attributed_flow,$message, custom properties). Use it for flexible, SQL-style rollups that don't need to match the UI.
Pick deliberately and tell the merchant which one a given number came from. A "why don't these match" support ticket almost always traces back to mixing the two.
Pull patterns
- Cursor pagination. Object reads (
GET /api/events/,GET /api/profiles/) use JSON:API cursor pagination. Followlinks.nextuntil it's null. Don't compute offsets. - Incremental sync. Store a high-water mark and use the filter DSL to pull only what changed:
filter=greater-than(datetime,2026-06-01T00:00:00Z)on events,filter=greater-than(updated,2026-06-01T00:00:00Z)on profiles. - Large historical pulls. There is no bulk-export job endpoint (unlike bulk import). For large backfills, page through the object endpoints, or use Klaviyo's scheduled data export options and prebuilt ETL connectors (Fivetran, Stitch, Segment, and the open-source
tap-klaviyoSinger tap) rather than hammering the API. - Cadence. Reporting endpoints are tightly rate-limited (see below), so pull them a few times a day at most and cache aggressively. Incremental object reads can run hourly. Backfill once, then sync deltas.
Optional write-back
After your model runs, you can write results back onto the Klaviyo profile so the merchant can segment and automate on them:
- Computed properties. Write LTV, predicted value, or attribution source as a custom property via Update Profile (
PATCH /api/profiles/{id}/) for single writes, or Bulk Import Profiles (POST /api/profile-bulk-import-jobs/) for large batches. Requiresprofiles:write. - Enrichment events. Push events sourced from your own pixel or model back via
POST /api/events/to increase flow-trigger volume. Requiresevents:write.
Keep write-back explicitly secondary. The primary flow is read, and you should only request write scopes if you actually use them.
Auth and scopes
Build as a public OAuth app. Private API keys are a non-starter for the marketplace. Start with the Create a public OAuth app guide.
Key OAuth specifics for analytics integrations:
- PKCE is required on the authorization code exchange (
code_verifiermust be included). - Access tokens are short-lived (1 hour). Refresh tokens are long-lived but rotate on every use. Persist the latest refresh token after every refresh call, or you'll lose the integration on the next refresh.
- Tokens are scoped per Klaviyo account. Agencies and platforms serving many merchants connect each account separately.
Scopes to request (read):
campaigns:read,flows:read,forms:read,segments:readfor the reporting endpoints.metrics:readfor the metric catalog and metric aggregates.events:read,profiles:read,lists:readfor raw object reads.tags:readonly if you group or filter reports by tag.
Add write scopes only for the optional write-back:
profiles:writeto write computed properties back.events:writeto push enrichment events back.
Don't over-scope. Requesting write scopes on a read-only integration gets flagged in marketplace review. Once functional, submit for App Marketplace review under the analytics and reporting category. Plan for at least one round of feedback covering OAuth, scopes, install and uninstall validation, and that the integration delivers what your listing claims.
API quick reference
Concrete specs for AI coding assistants and engineers grounding on this doc. The narrative above is the why and the flow. This section is the what-to-actually-type.
Headers (required on every request)
Authorization: Bearer <oauth_access_token>
revision: 2026-04-15
Accept: application/json
Content-Type: application/json
Pin the revision header to a specific date when you ship. Check the API versioning guide for the current latest. Without it you'll get a 400.
Campaign values report
Pull campaign performance for a timeframe, grouped by campaign. Get conversion_metric_id from GET /api/metrics/ (the Placed Order metric).
POST /api/campaign-values-reports/
{
"data": {
"type": "campaign-values-report",
"attributes": {
"statistics": ["opens_unique", "click_rate", "conversion_value", "revenue_per_recipient"],
"timeframe": { "key": "last_30_days" },
"conversion_metric_id": "RESQ6t",
"group_by": ["campaign_id", "send_channel"]
}
}
}
Query metric aggregates
Roll up a metric by event-occurrence time and custom dimensions. The filter must bound the time range.
POST /api/metric-aggregates/
{
"data": {
"type": "metric-aggregate",
"attributes": {
"metric_id": "RESQ6t",
"measurements": ["sum_value", "count"],
"interval": "day",
"by": ["$attributed_flow"],
"filter": [
"greater-or-equal(datetime,2026-05-01T00:00:00Z)",
"less-than(datetime,2026-06-01T00:00:00Z)"
],
"timezone": "America/New_York"
}
}
}
Incremental object read
Page through events changed since your high-water mark; follow the cursor.
GET /api/events/?filter=greater-than(datetime,2026-06-01T00:00:00Z)&page[size]=200
→ response.links.next contains the full next-page URL
→ loop until links.next is null
Retries
- 429: respect the
Retry-Afterheader. Otherwise exponential backoff starting at 1s, cap at 60s, max 5 retries. - 503: respect
Retry-After. - 5xx: same backoff.
- 4xx (other than 429): don't retry. Log and surface to the merchant.
Rate limits
Per-account, not per-app. Reporting endpoints are tightly rate-limited relative to object reads, with low steady rates and a modest daily cap, so cache aggressively and pull them only a few times a day. Raw GET /api/events/ and GET /api/profiles/ have much higher limits but are still per-account, so a platform serving many merchants can be throttled account by account. Verify current limits in the developer docs at ship time. For merchants at unusual scale, request per-account increases through Klaviyo Support and CSM before launch.
Glossary
- Klaviyo profile: customer record. Keyed by email, phone, or external ID. Your join key back to your own customer graph.
- Klaviyo metric: event type. "Placed Order," "Opened Email," etc. Its ID is the
conversion_metric_idin reports. - Values report: a report returning totals over a timeframe.
- Series report: a report returning statistics bucketed by an interval, with a
date_timesarray. - Conversion metric: the metric that defines a conversion for a report, typically Placed Order.
- Query Metric Aggregates: the endpoint for flexible event rollups grouped by event-occurrence time, distinct from the Reporting API's send-date grouping.
- High-water mark: the last-synced timestamp you store to pull only what changed on the next run.
Resources
- Klaviyo Developer Portal. Start here for everything.
- Create a public OAuth app
- Reporting API overview
- Query Metric Aggregates / Get Metrics
- Get Events / Get Profiles
- Update Profile / Bulk Import Profiles (for optional write-back)
- Klaviyo App Marketplace. The existing set of analytics and reporting integrations.
- App Marketplace Review: [email protected]
Updated about 2 hours ago