MartechARTICLE

CrUX History API: how to measure a campaign's before and after without waiting 28 days

Chrome's field data history gives access to up to 10 months of Core Web Vitals per page, without paid RUM. See how growth teams can prove the impact of a landing page.

CrUX History API: how to measure a campaign's before and after without waiting 28 days
Image: Sabrina Santos

Every growth team has lived this scene: a new landing page goes up for the campaign, images get optimized, a third-party script gets cut, and the technical SEO folks ask "did Core Web Vitals improve?". The standard answer is frustrating: "wait 28 days". This happens because Chrome's field data (CrUX) is a 28-day moving average. But there's a way to shorten the wait and, more importantly, to prove the trend with a number: the CrUX History API, documented on Chrome for Developers.

What the History API delivers that the daily API doesn't

The daily CrUX API returns a single snapshot: the current p75, the current histogram. The History API returns the time series. According to the documentation, it provides low-latency access to historical real-user experience data at page and origin granularity, both at the origin level (the entire site) and at the URL level (a specific page). In practice, the ceiling is even higher: the API stores the previous 40 weeks (about 10 months), one collection per week, and by default returns 25 periods. You can request from 1 to 40 using the collectionPeriodCount field.

The structural difference from the daily API is straightforward: values become arrays and keys get pluralized names. Where the daily API has histogram, the historical one has histogramTimeseries; where it has p75, it has p75s. Each array position corresponds to a collection period, from oldest to most recent.

Why there's still a delay (and how to live with it)

Here's the part that separates those who use the tool honestly from those who promise a miracle. The History API doesn't eliminate the 28-day window, it makes it visible over time. Each point in the series is still a 28-day moving average, and the periods are weekly, so they overlap: between one point and the next, three weeks of data are the same and only one week is new.

What does this mean for a campaign? If the landing page went live on a Monday, the LCP improvement doesn't show up in full at the next point. It enters diluted, gaining weight each week as the 28 days get taken over by the post-optimization period. That's why the right reading isn't "the number changed from X to Y", but rather "the curve started dropping this week and stabilized at a better level four weeks later". The trend is the argument, not the isolated point.

Two operational details reinforce this: the database is updated every Monday around 04:00 UTC, with data through the previous Saturday (the standard 2-day lag), and there's no time-of-day SLA. And repeating the call within the same week returns exactly the same result, so there's no point polling daily hoping the number will move.

The query in practice

Editor's note: the code in this section was not run in a real environment. Validate before using in production.

The call is a POST to the queryHistoryRecord endpoint (note the History in the name, which replaces the queryRecord from the daily API). It requires a Google Cloud key provisioned for the Chrome UX Report, the same one used for the daily API. To measure a specific campaign page, the trick is to pass url instead of origin, which restricts the data to that exact URL:

bash
curl -s --request POST \
  'https://chromeuxreport.googleapis.com/v1/records:queryHistoryRecord?key=API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
    "url": "https://loja.exemplo.com.br/promo-black-friday",
    "formFactor": "PHONE",
    "metrics": ["largest_contentful_paint", "cumulative_layout_shift", "interaction_to_next_paint"],
    "collectionPeriodCount": 40
  }'

formFactor accepts PHONE, TABLET, or DESKTOP, and the History API only aggregates along this dimension. For a mobile-first campaign, PHONE is the slice that matters. If you omit the metrics list, the API returns everything available, including metrics useful for LCP diagnosis such as largest_contentful_paint_image_time_to_first_byte and largest_contentful_paint_image_element_render_delay, plus round_trip_time and navigation_types.

The response includes, for each metric, the histogramTimeseries (percentage of page loads in each bucket: good, needs improvement, poor) and the percentilesTimeseries with the p75s. A snippet of LCP p75 looks like this, from the oldest to the most recent period:

json
"percentilesTimeseries": {
  "p75s": [1362, 1352, 1344, 1356, 1366, 1377]
}

To align each number to a date, use the collectionPeriods array, which comes in ascending order with the firstDate and lastDate of each 28-day window. It's this pairing between p75s[i] and collectionPeriods[i] that lets you mark on the chart the week the landing page went live.

The gap in the series: missing data

One thing that often trips up whoever builds the dashboard: not every period has eligible data. Campaign URLs, by nature, have traffic that rises and falls. When a window doesn't meet the minimum sample size, the API returns "NaN" in the histogram densities and null in the percentiles for that period. The documentation explains the difference: density is always a number, but percentile can be a number or a string (CLS comes as a string, even though it looks like a number).

json
"p75s": [1362, null, 1344, 1356, 1366, 1377]

For URLs or origins that become qualified and then lose qualification over time, as the source states, you can see plenty of gaps. This has a tactical consequence: a low-traffic campaign page may simply not appear at the URL level. In that case, the way out is to measure at the origin level (the whole site) or to accept that field-based proof won't come from this source.

Where this shines and where it's not worth it

The strongest use case is exactly the one behind this piece: a growth team in Brazil that launched a campaign landing page, optimized LCP and INP, and wants to prove the impact without hiring a paid RUM tool. The History API covers this for free, with real Chrome user data, and the 40-week series gives you the before and after in a single request.

Here's how I'd set it up: pull the series at the URL level for PHONE, cross-reference p75s with collectionPeriods, mark the deployment week, and watch four to six points afterward. Reading the gradual rise of the "good" bucket in histogramTimeseries tends to be more convincing to stakeholders than the p75 alone, because it shows the proportion of users benefited.

Where it's not worth it: no flash-campaign landing pages that stay live for ten days. With the 28-day moving average and weekly granularity, that timeframe doesn't build a readable series, and you'll just get NaN. Also, don't use the History API as real-time monitoring, for that the path is your own RUM (for example, collecting Web Vitals client-side and sending them to your backend), which sees the effect on the same day. CrUX is for audit and trend proof, not incident alerting. The two complement each other: RUM to react fast, History API to prove the result with the same data Google uses for ranking.

Translated from the Brazilian Portuguese original · Read the original