NEWAI content checker: see which pages read like AI text

APICore Web Vitals

Core Web Vitals API. Real Chrome speed for your site and its competitors, straight into your own tools.

Read whole-site LCP, INP and CLS from Google's Chrome UX Report for every site in your account and up to six competitors. We keep the data ready over REST and MCP for your dashboard or client report. Every plan includes both at 60 calls per minute.

Competitor scoreboard

Rank your site against up to six competitors on phones, desktops or all devices.

104-week history

Up to two years of 28-day windows, with every domain on the same dates.

Field diagnostics

Check server TTFB, the four image LCP steps, back/forward cache use and ad load.

Paired with GA4

Your 28-day speed lined up with GA4 conversions and revenue per session.

Core Web Vitals REST endpoints and MCP tools

Call /rest/v1/<endpoint> with POST and a JSON body, or with GET and query parameters. Your personal API key is on the MCP page in the app and works for both REST and MCP. Every call is read-only and takes site_id. Pass device as desktop or all to switch from the default phone view. Timings use the 75th percentile (p75): three quarters of real Chrome visits were at least that fast.

REST endpointMCP toolWhat it returns
crux.summaryget_crux_summaryLatest 28-day p75 Core Web Vitals for the site and up to six competitors, with pass status, metric ratings, 28-day change and rank.
crux.historyget_crux_historyUp to 104 rolling 28-day windows of LCP, INP and CLS per domain on shared dates, oldest week first.
crux.metricget_crux_metricOne metric in detail per week: p75 by device, visit shares by rating, image LCP steps and each competitor's newest week.
crux.diagnoseget_crux_diagnosePer-domain breakdown of the four image LCP steps, navigation types, largest element type, server TTFB, FCP, network RTT and ad load.
crux.visitorsget_crux_visitorsThe site's 28-day speed paired with GA4 revenue, key event rate, engaged time, bounce rate and pages per session, plus 28-day block correlations.

What crux.summary returns for a site and its competitors

Send site_id and device to get the current scoreboard for a site and the competitors tracked with it:

curl -X POST https://app.marketingtracer.com/rest/v1/crux.summary \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"site_id": "YOUR_SITE_ID", "device": "phone"}'
{
  "data": {
    "enabled": true,
    "enabled_at": "2026-10-28T15:36:30Z",
    "device": "phone",
    "own_domain": "example-site.nl",
    "competitors": [
      "competitor-one.nl"
    ],
    "latest_date": "2026-10-24",
    "domains": [
      {
        "domain": "example-site.nl",
        "own": true,
        "origin": "https://www.example-site.nl",
        "state": "ok",
        "status": "pass",
        "partial": false,
        "rank": 1,
        "lcp_ms": {
          "value": 587,
          "date": "2026-10-24",
          "stale": false,
          "previous": 838,
          "change_pct": -30,
          "rating": "good"
        },
        "inp_ms": {
          "value": 78,
          "date": "2026-10-10",
          "stale": true,
          "previous": null,
          "change_pct": null,
          "rating": "good"
        },
        "cls": {
          "value": 0,
          "date": "2026-10-24",
          "stale": false,
          "previous": 0.01,
          "change_pct": 0,
          "rating": "good"
        }
      },
      {
        "domain": "competitor-one.nl",
        "own": false,
        "origin": "https://www.competitor-one.nl",
        "state": "ok",
        "status": "pass",
        "partial": false,
        "rank": 3,
        "lcp_ms": {
          "value": 1526,
          "date": "2026-10-24",
          "stale": false,
          "previous": 1512,
          "change_pct": 0.9,
          "rating": "good"
        },
        …
      }
    ]
  },
  "meta": {
    "site_id": "YOUR_SITE_ID"
  }
}

Your own site always comes first in domains (own: true), and each tracked competitor follows with its rank in the group. Negative numbers in change_pct mean the site got faster than 28 days earlier: -30 on lcp_ms is the drop from 838 ms to 587 ms. Look at inp_ms on example-site.nl too. Google had no phone INP value in the 2026-10-24 window, so the API keeps the last measured one (2026-10-10, 78 ms) and marks it stale: true.

Show clients where they stand against the competitors they watch

An LCP of 1,500 ms means little to a client on Monday morning. They want to know if they are faster than the shops they lose orders to, and whether last month's template change paid off.

One crux.summary call puts the client's site first and up to six competitors behind it, with pass or fail, each metric's rating, the 28-day change and the rank. Add crux.history when you want up to 104 weeks of 28-day windows on shared dates. When a smaller competitor lacks phone INP or has no Chrome data at all, partial: true and state: "no_data" keep missing metrics from plotting as 0 ms.

Put field speed and GA4 conversions on the same 28-day clock

Getting developer time for speed work is easier when your report shows conversions and revenue for the same dates and device. When a site gets faster after a release, clients ask straight away whether visitors stayed longer or bought more.

In crux.visitors, each week's 28-day LCP, INP and CLS sits next to GA4 revenue per session, key event rate, engaged time, bounce rate and pages per session. Under relations you see how strongly each speed metric and visit metric moved together across 28-day blocks, plus biggest_change with the speed and GA4 numbers before and after the sharpest shift.

Settle whether a slow LCP starts at the server, the hero image or ad scripts

When mobile LCP slips on a shop or publisher site, hosting blames the frontend and developers point at third-party tags. Wire crux.diagnose into your release check, or ask Claude Code with get_crux_diagnose while you investigate.

You get the numbers behind the score for your site and every competitor: overall TTFB, FCP, network RTT and the four image LCP steps in lcp_parts (first byte, load delay, load duration and render delay). It also returns how visits arrive, including back/forward cache (instant back-button loads) and prerender, plus ad kilobytes and main-thread ad CPU time. Seeing 478 ms to first byte next to a 940 ms load delay in lcp_parts tells your developer in ten seconds to start the hero image earlier.

Compare mobile LCP diagnostics and ad load on example-site.nl with competitor-one.nl.

get_crux_diagnose
site_id: YOUR_SITE_IDdevice: phone
example-site.nl LCP first byte478 ms
example-site.nl LCP load delay940 ms
example-site.nl ad CPU time602 ms
competitor-one.nl LCP first byte1,103 ms
competitor-one.nl LCP load delay545 ms
competitor-one.nl ad CPU time442 ms

Endpoints, arguments and field descriptions

Every endpoint is a POST with a JSON body; reading endpoints also take a GET with the same arguments in the query string. Send your API key as Authorization: Bearer YOUR_API_KEY. Every answer is {"data": …, "meta": {…}}: meta.site_id always, meta.next_cursor and meta.total for a list, meta.period where the endpoint covers a period. An error is {"error": {"code", "message"}} with the HTTP status: 400 invalid_argument, unknown_argument or missing_argument; 401 not_authenticated; 403 no_access or tool_off; 404 not_found; 429 rate_limited. The machine-readable version is openapi.json.

crux.summary

POST /rest/v1/crux.summary · MCP tool get_crux_summary

Returns the Core Web Vitals of one site next to its competitors, from real Chrome visits (Google's Chrome UX Report, whole site): per domain the latest LCP, INP and CLS (75th percentile over 28 days), the value 28 days earlier, the change, the rating, whether the domain passes, and its rank among the domains. The site comes first, then its competitors.

Arguments

ArgumentTypeWhat it does
site_id (required)stringThe site: its id from list_sites, or the id in the app’s address.
deviceone of phone, desktop, allphone, desktop or all (all devices together). Defaults to phone.

Example request

curl -X POST https://app.marketingtracer.com/rest/v1/crux.summary \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"site_id":"YOUR_SITE_ID","device":"phone"}'

Example response · A real answer, anonymised and shortened to two domains and three weeks.

{
  "data": {
    "enabled": true,
    "enabled_at": "2026-10-28T15:36:30Z",
    "device": "phone",
    "own_domain": "example-site.nl",
    "competitors": ["competitor-one.nl"],
    "latest_date": "2026-10-24",
    "domains": [
      {
        "domain": "example-site.nl",
        "own": true,
        "origin": "https://www.example-site.nl",
        "state": "ok",
        "status": "pass",
        "partial": false,
        "rank": 1,
        "lcp_ms": {
          "value": 587,
          "date": "2026-10-24",
          "stale": false,
          "previous": 838,
          "change_pct": -30,
          "rating": "good"
        },
        "inp_ms": {
          "value": 78,
          "date": "2026-10-10",
          "stale": true,
          "previous": null,
          "change_pct": null,
          "rating": "good"
        },
        "cls": {
          "value": 0,
          "date": "2026-10-24",
          "stale": false,
          "previous": 0.01,
          "change_pct": 0,
          "rating": "good"
        }
      },
      {
        "domain": "competitor-one.nl",
        "own": false,
        "origin": "https://www.competitor-one.nl",
        "state": "ok",
        "status": "pass",
        "partial": false,
        "rank": 3,
        "lcp_ms": {
          "value": 1526,
          "date": "2026-10-24",
          "stale": false,
          "previous": 1512,
          "change_pct": 0.9,
          "rating": "good"
        },
        "inp_ms": {
          "value": 95,
          "date": "2026-10-24",
          "stale": false,
          "previous": 95,
          "change_pct": 0,
          "rating": "good"
        },
        "cls": {
          "value": 0,
          "date": "2026-10-24",
          "stale": false,
          "previous": 0,
          "change_pct": 0,
          "rating": "good"
        }
      }
    ]
  },
  "meta": {"site_id": "YOUR_SITE_ID"}
}

Response fields · in data:

FieldTypeWhat it is
enabledbooleanWhether Core Web Vitals is switched on for the site. When false, the other fields are empty.
enabled_atstring or nullWhen it was switched on, ISO 8601 UTC; null when never.
devicestringThe device the numbers are for: phone, desktop or all.
own_domainstringThe domain of the site, without www.
competitorsarray<string>The competitor domains of the site (the shared list of the Rank tracker, Core Web Vitals and AI Insights), at most 6.
latest_datestring or nullLast day of the newest 28-day window of any domain, YYYY-MM-DD; null without data.
domainsarrayThe site first, then each competitor.
domains[].domainstringThe domain, without www.
domains[].ownbooleanWhether this is the site itself.
domains[].originstring or nullThe origin Google has data for (with or without www); null when it has none.
domains[].statestringok, pending (not fetched yet) or no_data (Google has no data for this origin and device).
domains[].statusstring or nullpass when LCP, INP and CLS are all good, fail when one is not; null without LCP or CLS.
domains[].partialbooleanWhether status leaves out INP because Google has no INP value.
domains[].rankinteger or nullPlace among the domains (1 is best): passing first, then by how many metrics are good and how close the rest is; null without data.
domains[].lcp_msobject or nullLargest Contentful Paint in milliseconds; null when Google has no value for it.
domains[].lcp_ms.valuenumberThe value (75th percentile of real Chrome visits over the 28 days up to date).
domains[].lcp_ms.datestringLast day of the 28-day window the value is from, YYYY-MM-DD.
domains[].lcp_ms.stalebooleanWhether the value is from an older window than latest_date (Google had no newer value).
domains[].lcp_ms.previousnumber or nullThe value 28 days before; null when there is none.
domains[].lcp_ms.change_pctnumber or nullChange against previous in percent (negative is faster); null without previous.
domains[].lcp_ms.ratingstringgood (up to 2500), needs_improvement (up to 4000) or poor.
domains[].inp_msobject or nullInteraction to Next Paint in milliseconds; null when Google has no value for it.
domains[].inp_ms.valuenumberThe value (75th percentile of real Chrome visits over the 28 days up to date).
domains[].inp_ms.datestringLast day of the 28-day window the value is from, YYYY-MM-DD.
domains[].inp_ms.stalebooleanWhether the value is from an older window than latest_date (Google had no newer value).
domains[].inp_ms.previousnumber or nullThe value 28 days before; null when there is none.
domains[].inp_ms.change_pctnumber or nullChange against previous in percent (negative is faster); null without previous.
domains[].inp_ms.ratingstringgood (up to 200), needs_improvement (up to 500) or poor.
domains[].clsobject or nullCumulative Layout Shift (no unit); null when Google has no value for it.
domains[].cls.valuenumberThe value (75th percentile of real Chrome visits over the 28 days up to date).
domains[].cls.datestringLast day of the 28-day window the value is from, YYYY-MM-DD.
domains[].cls.stalebooleanWhether the value is from an older window than latest_date (Google had no newer value).
domains[].cls.previousnumber or nullThe value 28 days before; null when there is none.
domains[].cls.change_pctnumber or nullChange against previous in percent; 0 for a change under 0.015, null when previous is under 0.05 or missing.
domains[].cls.ratingstringgood (up to 0.1), needs_improvement (up to 0.25) or poor.

crux.history

POST /rest/v1/crux.history · MCP tool get_crux_history

Returns LCP, INP and CLS per week for the site and each competitor, oldest week first: one item per domain, the site first. Each week is the 75th percentile of real Chrome visits over the 28 days up to its date; a value is null when Google had none that week. All domains share the same weeks.

Arguments

ArgumentTypeWhat it does
site_id (required)stringThe site: its id from list_sites, or the id in the app’s address.
deviceone of phone, desktop, allphone, desktop or all (all devices together). Defaults to phone.
weeksinteger, 1 to 104, default 104The newest this many weeks.

Example request

curl -X POST https://app.marketingtracer.com/rest/v1/crux.history \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"site_id":"YOUR_SITE_ID","device":"phone","weeks":3}'

Example response · A real answer, anonymised and shortened to two domains and three weeks.

{
  "data": [
    {
      "domain": "example-site.nl",
      "own": true,
      "state": "ok",
      "weeks": [
        {"date": "2026-10-10", "lcp_ms": 807, "inp_ms": 78, "cls": 0},
        {"date": "2026-10-17", "lcp_ms": 665, "inp_ms": null, "cls": 0},
        {"date": "2026-10-24", "lcp_ms": 587, "inp_ms": null, "cls": 0}
      ]
    },
    {
      "domain": "competitor-one.nl",
      "own": false,
      "state": "ok",
      "weeks": [
        {"date": "2026-10-10", "lcp_ms": 1533, "inp_ms": 95, "cls": 0},
        {"date": "2026-10-17", "lcp_ms": 1532, "inp_ms": 95, "cls": 0},
        {"date": "2026-10-24", "lcp_ms": 1526, "inp_ms": 95, "cls": 0}
      ]
    }
  ],
  "meta": {"site_id": "YOUR_SITE_ID", "next_cursor": null, "total": 2}
}

Response fields · data is a list; each item has:

FieldTypeWhat it is
domainstringThe domain, without www.
ownbooleanWhether this is the site itself.
statestringok, pending (not fetched yet) or no_data (Google has no data for this origin and device).
weeksarrayPer week, oldest first; empty without data.
weeks[].datestringLast day of the 28-day window, YYYY-MM-DD.
weeks[].lcp_msnumber or nullLargest Contentful Paint in milliseconds.
weeks[].inp_msnumber or nullInteraction to Next Paint in milliseconds.
weeks[].clsnumber or nullCumulative Layout Shift.

crux.metric

POST /rest/v1/crux.metric · MCP tool get_crux_metric

Returns one Core Web Vital of the site in detail: per week the 75th percentile on the chosen device and on each device, the share of good, needs-improvement and poor visits, and for LCP the four LCP parts; plus each competitor's newest week. Values null where Google had none.

Arguments

ArgumentTypeWhat it does
site_id (required)stringThe site: its id from list_sites, or the id in the app’s address.
metricone of lcp, inp, clslcp, inp or cls. Defaults to lcp.
deviceone of phone, desktop, allphone, desktop or all (all devices together). Defaults to phone.

Example request

curl -X POST https://app.marketingtracer.com/rest/v1/crux.metric \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"site_id":"YOUR_SITE_ID","metric":"lcp","device":"phone"}'

Example response · A real answer, anonymised and shortened to two domains and three weeks.

{
  "data": {
    "enabled": true,
    "enabled_at": "2026-10-28T15:36:30Z",
    "device": "phone",
    "own_domain": "example-site.nl",
    "competitors": ["competitor-one.nl"],
    "metric": "lcp",
    "unit": "ms",
    "latest_date": "2026-10-24",
    "weeks": [
      {
        "date": "2026-10-10",
        "p75": 807,
        "p75_phone": 807,
        "p75_desktop": null,
        "p75_all": 838,
        "good_pct": 97.4,
        "needs_improvement_pct": 1.2,
        "poor_pct": 1.4,
        "lcp_parts": {
          "ttfb_ms": null,
          "load_delay_ms": null,
          "load_duration_ms": null,
          "render_delay_ms": null
        }
      },
      {
        "date": "2026-10-17",
        "p75": 665,
        "p75_phone": 665,
        "p75_desktop": null,
        "p75_all": 653,
        "good_pct": 98.3,
        "needs_improvement_pct": 0.9,
        "poor_pct": 0.8,
        "lcp_parts": {
          "ttfb_ms": null,
          "load_delay_ms": null,
          "load_duration_ms": null,
          "render_delay_ms": null
        }
      },
      {
        "date": "2026-10-24",
        "p75": 587,
        "p75_phone": 587,
        "p75_desktop": 596,
        "p75_all": 587,
        "good_pct": 98.6,
        "needs_improvement_pct": 0.8,
        "poor_pct": 0.6,
        "lcp_parts": {
          "ttfb_ms": null,
          "load_delay_ms": null,
          "load_duration_ms": null,
          "render_delay_ms": null
        }
      }
    ],
    "competitor_weeks": [
      {
        "domain": "competitor-one.nl",
        "date": "2026-10-24",
        "p75": 1526,
        "good_pct": 95.5,
        "needs_improvement_pct": 3.6,
        "poor_pct": 0.9
      }
    ]
  },
  "meta": {"site_id": "YOUR_SITE_ID"}
}

Response fields · in data:

FieldTypeWhat it is
enabledbooleanWhether Core Web Vitals is switched on for the site. When false, the other fields are empty.
enabled_atstring or nullWhen it was switched on, ISO 8601 UTC; null when never.
devicestringThe device the numbers are for: phone, desktop or all.
own_domainstringThe domain of the site, without www.
competitorsarray<string>The competitor domains of the site (the shared list of the Rank tracker, Core Web Vitals and AI Insights), at most 6.
metricstringlcp, inp or cls.
unitstring or nullms for lcp and inp; null for cls (no unit).
latest_datestring or nullThe newest week, YYYY-MM-DD; null without data.
weeksarrayPer week of the site, oldest first.
weeks[].datestringLast day of the 28-day window, YYYY-MM-DD.
weeks[].p75number or nullThe 75th percentile on device, in unit.
weeks[].p75_phonenumber or nullThe 75th percentile on phones.
weeks[].p75_desktopnumber or nullThe 75th percentile on desktops.
weeks[].p75_allnumber or nullThe 75th percentile on all devices together.
weeks[].good_pctnumber or nullShare of visits with a good value, 0-100, on device.
weeks[].needs_improvement_pctnumber or nullShare of visits that need improvement, 0-100.
weeks[].poor_pctnumber or nullShare of visits with a poor value, 0-100.
weeks[].lcp_partsobject or nullFor lcp only, when the largest element is an image: the 75th percentile of each part in milliseconds (each part is its own percentile, so they do not add up to LCP); null otherwise.
weeks[].lcp_parts.ttfb_msnumber or nullTime to first byte.
weeks[].lcp_parts.load_delay_msnumber or nullFrom first byte until the image starts loading.
weeks[].lcp_parts.load_duration_msnumber or nullLoading the image.
weeks[].lcp_parts.render_delay_msnumber or nullFrom loaded until shown.
competitor_weeksarrayEach competitor's newest week with a value.
competitor_weeks[].domainstringThe domain, without www.
competitor_weeks[].datestringLast day of that 28-day window, YYYY-MM-DD.
competitor_weeks[].p75number or nullThe 75th percentile on device, in unit.
competitor_weeks[].good_pctnumber or nullShare of good visits, 0-100.
competitor_weeks[].needs_improvement_pctnumber or nullShare of visits that need improvement, 0-100.
competitor_weeks[].poor_pctnumber or nullShare of poor visits, 0-100.

crux.diagnose

POST /rest/v1/crux.diagnose · MCP tool get_crux_diagnose

Returns what is behind the numbers, per domain (the site first, then its competitors), each from its newest week with a value: the four LCP parts, how visits arrive (navigation types such as back/forward cache and prerender), the type of the largest element, server and network timing (TTFB, FCP, round trip time) and ads. A field is null when Google has no value.

Arguments

ArgumentTypeWhat it does
site_id (required)stringThe site: its id from list_sites, or the id in the app’s address.
deviceone of phone, desktop, allphone, desktop or all (all devices together). Defaults to phone.

Example request

curl -X POST https://app.marketingtracer.com/rest/v1/crux.diagnose \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"site_id":"YOUR_SITE_ID","device":"phone"}'

Example response · A real answer, anonymised and shortened to two domains and three weeks.

{
  "data": [
    {
      "domain": "example-site.nl",
      "own": true,
      "state": "ok",
      "lcp_parts": {
        "date": "2026-08-08",
        "stale": true,
        "ttfb_ms": 478,
        "load_delay_ms": 940,
        "load_duration_ms": 104,
        "render_delay_ms": 130
      },
      "navigation_types": null,
      "lcp_resource_types": {
        "date": "2026-08-08",
        "stale": true,
        "items": [{"key": "text", "share_pct": 87.6}, {"key": "image", "share_pct": 12.4}]
      },
      "ttfb_ms": {"value": 358, "date": "2026-10-24", "stale": false},
      "fcp_ms": {"value": 577, "date": "2026-10-24", "stale": false},
      "rtt_ms": {"value": 66, "date": "2026-10-24", "stale": false},
      "ad_density_pct": {"value": 21, "date": "2026-10-24", "stale": false},
      "ad_kilobytes": {"value": 1179, "date": "2026-10-24", "stale": false},
      "ad_cpu_ms": {"value": 602, "date": "2026-10-03", "stale": true},
      "ad_count": {"value": 0.51, "date": "2026-10-24", "stale": false}
    },
    {
      "domain": "competitor-one.nl",
      "own": false,
      "state": "ok",
      "lcp_parts": {
        "date": "2026-10-24",
        "stale": false,
        "ttfb_ms": 1103,
        "load_delay_ms": 545,
        "load_duration_ms": 61,
        "render_delay_ms": 97
      },
      "navigation_types": {
        "date": "2026-10-24",
        "stale": false,
        "items": [
          {"key": "navigate", "share_pct": 76.5},
          {"key": "back_forward_cache", "share_pct": 11.7},
          {"key": "navigate_cache", "share_pct": 5.5},
          {"key": "reload", "share_pct": 3.9},
          {"key": "back_forward", "share_pct": 2.1},
          {"key": "prerender", "share_pct": 0.4},
          {"key": "restore", "share_pct": 0}
        ]
      },
      "lcp_resource_types": {
        "date": "2026-10-24",
        "stale": false,
        "items": [{"key": "image", "share_pct": 50.6}, {"key": "text", "share_pct": 49.4}]
      },
      "ttfb_ms": {"value": 1146, "date": "2026-10-24", "stale": false},
      "fcp_ms": {"value": 1377, "date": "2026-10-24", "stale": false},
      "rtt_ms": {"value": 59, "date": "2026-10-24", "stale": false},
      "ad_density_pct": {"value": 3, "date": "2026-10-24", "stale": false},
      "ad_kilobytes": {"value": 1189, "date": "2026-10-24", "stale": false},
      "ad_cpu_ms": {"value": 442, "date": "2026-10-24", "stale": false},
      "ad_count": {"value": 0.15, "date": "2026-10-24", "stale": false}
    }
  ],
  "meta": {"site_id": "YOUR_SITE_ID", "next_cursor": null, "total": 2}
}

Response fields · data is a list; each item has:

FieldTypeWhat it is
domainstringThe domain, without www.
ownbooleanWhether this is the site itself.
statestringok, pending (not fetched yet) or no_data (Google has no data for this origin and device).
lcp_partsobject or nullThe four LCP parts when the largest element is an image, in milliseconds (each its own 75th percentile).
lcp_parts.datestringLast day of the 28-day window, YYYY-MM-DD.
lcp_parts.stalebooleanWhether this is an older window than the newest one.
lcp_parts.ttfb_msnumber or nullTime to first byte.
lcp_parts.load_delay_msnumber or nullFrom first byte until the image starts loading.
lcp_parts.load_duration_msnumber or nullLoading the image.
lcp_parts.render_delay_msnumber or nullFrom loaded until shown.
navigation_typesobject or nullHow visits arrive.
navigation_types.datestringLast day of the 28-day window, YYYY-MM-DD.
navigation_types.stalebooleanWhether this is an older window than the newest one.
navigation_types.itemsarrayLargest share first.
navigation_types.items[].keystringnavigate, navigate_cache, reload, restore, back_forward, back_forward_cache or prerender.
navigation_types.items[].share_pctnumberShare of visits, 0-100.
lcp_resource_typesobject or nullWhat the largest element is.
lcp_resource_types.datestringLast day of the 28-day window, YYYY-MM-DD.
lcp_resource_types.stalebooleanWhether this is an older window than the newest one.
lcp_resource_types.itemsarrayLargest share first.
lcp_resource_types.items[].keystringimage or text.
lcp_resource_types.items[].share_pctnumberShare of visits, 0-100.
ttfb_msobject or nullTime to first byte in milliseconds; null when Google has no value for it.
ttfb_ms.valuenumberThe value (75th percentile of real Chrome visits over the 28 days up to date).
ttfb_ms.datestringLast day of the 28-day window the value is from, YYYY-MM-DD.
ttfb_ms.stalebooleanWhether the value is from an older window than latest_date (Google had no newer value).
fcp_msobject or nullFirst Contentful Paint in milliseconds; null when Google has no value for it.
fcp_ms.valuenumberThe value (75th percentile of real Chrome visits over the 28 days up to date).
fcp_ms.datestringLast day of the 28-day window the value is from, YYYY-MM-DD.
fcp_ms.stalebooleanWhether the value is from an older window than latest_date (Google had no newer value).
rtt_msobject or nullRound trip time of the network in milliseconds; null when Google has no value for it.
rtt_ms.valuenumberThe value (75th percentile of real Chrome visits over the 28 days up to date).
rtt_ms.datestringLast day of the 28-day window the value is from, YYYY-MM-DD.
rtt_ms.stalebooleanWhether the value is from an older window than latest_date (Google had no newer value).
ad_density_pctobject or nullShare of the screen taken by ads, 0-100; null when Google has no value for it.
ad_density_pct.valuenumberThe value (75th percentile of real Chrome visits over the 28 days up to date).
ad_density_pct.datestringLast day of the 28-day window the value is from, YYYY-MM-DD.
ad_density_pct.stalebooleanWhether the value is from an older window than latest_date (Google had no newer value).
ad_kilobytesobject or nullKilobytes of ads per page; null when Google has no value for it.
ad_kilobytes.valuenumberThe value (75th percentile of real Chrome visits over the 28 days up to date).
ad_kilobytes.datestringLast day of the 28-day window the value is from, YYYY-MM-DD.
ad_kilobytes.stalebooleanWhether the value is from an older window than latest_date (Google had no newer value).
ad_cpu_msobject or nullMain-thread time spent on ads in milliseconds; null when Google has no value for it.
ad_cpu_ms.valuenumberThe value (75th percentile of real Chrome visits over the 28 days up to date).
ad_cpu_ms.datestringLast day of the 28-day window the value is from, YYYY-MM-DD.
ad_cpu_ms.stalebooleanWhether the value is from an older window than latest_date (Google had no newer value).
ad_countobject or nullAds per page; null when Google has no value for it.
ad_count.valuenumberThe value (75th percentile of real Chrome visits over the 28 days up to date).
ad_count.datestringLast day of the 28-day window the value is from, YYYY-MM-DD.
ad_count.stalebooleanWhether the value is from an older window than latest_date (Google had no newer value).

crux.visitors

POST /rest/v1/crux.visitors · MCP tool get_crux_visitors

Returns the site's own LCP, INP and CLS per week next to its GA4 visits per session over the same 28 days and device (revenue, key event rate, engagement, bounce rate, pages), and per pair of a speed metric and a visit metric how they moved together: the rank correlation of their changes over 28-day blocks and the biggest change in speed. Visit values are null without GA4.

Arguments

ArgumentTypeWhat it does
site_id (required)stringThe site: its id from list_sites, or the id in the app’s address.
deviceone of phone, desktop, allphone, desktop or all (all devices together). Defaults to phone.

Example request

curl -X POST https://app.marketingtracer.com/rest/v1/crux.visitors \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"site_id":"YOUR_SITE_ID","device":"phone"}'

Example response · A real answer, anonymised and shortened to two domains and three weeks (two of the fifteen relations).

{
  "data": {
    "enabled": true,
    "enabled_at": "2026-10-28T15:36:30Z",
    "device": "phone",
    "own_domain": "example-site.nl",
    "competitors": ["competitor-one.nl"],
    "latest_date": "2026-10-24",
    "ga4": "ok",
    "has_revenue": true,
    "weeks": [
      {
        "date": "2026-10-10",
        "lcp_ms": 807,
        "inp_ms": 78,
        "cls": 0,
        "revenue_per_session": 0,
        "key_event_rate_pct": 0,
        "engagement_seconds": 13,
        "bounce_rate_pct": 79.8,
        "pages_per_session": 1.09
      },
      {
        "date": "2026-10-17",
        "lcp_ms": 665,
        "inp_ms": null,
        "cls": 0,
        "revenue_per_session": 0,
        "key_event_rate_pct": 0.01,
        "engagement_seconds": 21,
        "bounce_rate_pct": 74.2,
        "pages_per_session": 1.21
      },
      {
        "date": "2026-10-24",
        "lcp_ms": 587,
        "inp_ms": null,
        "cls": 0,
        "revenue_per_session": 0,
        "key_event_rate_pct": 0.03,
        "engagement_seconds": 31,
        "bounce_rate_pct": 66.2,
        "pages_per_session": 1.5
      }
    ],
    "relations": [
      {
        "speed_metric": "lcp_ms",
        "visit_metric": "key_event_rate_pct",
        "better": "higher",
        "blocks": 9,
        "blocks_moved": 3,
        "blocks_with": 1,
        "rho": -0.411,
        "strength": "some",
        "direction": "expected",
        "biggest_change": {
          "direction": "faster",
          "change_pct": -30.8,
          "before": {"date": "2026-08-08", "speed": 1082, "visit": 0},
          "after": {"date": "2026-09-05", "speed": 749, "visit": 0}
        }
      },
      {
        "speed_metric": "lcp_ms",
        "visit_metric": "pages_per_session",
        "better": "higher",
        "blocks": 9,
        "blocks_moved": 3,
        "blocks_with": 3,
        "rho": -0.667,
        "strength": "clear",
        "direction": "expected",
        "biggest_change": {
          "direction": "faster",
          "change_pct": -30.8,
          "before": {"date": "2026-08-08", "speed": 1082, "visit": 1.3},
          "after": {"date": "2026-09-05", "speed": 749, "visit": 1.53}
        }
      }
    ]
  },
  "meta": {"site_id": "YOUR_SITE_ID"}
}

Response fields · in data:

FieldTypeWhat it is
enabledbooleanWhether Core Web Vitals is switched on for the site. When false, the other fields are empty.
enabled_atstring or nullWhen it was switched on, ISO 8601 UTC; null when never.
devicestringThe device the numbers are for: phone, desktop or all.
own_domainstringThe domain of the site, without www.
competitorsarray<string>The competitor domains of the site (the shared list of the Rank tracker, Core Web Vitals and AI Insights), at most 6.
latest_datestring or nullThe newest week, YYYY-MM-DD; null without data.
ga4string or nullok, connect (GA4 not connected) or error (Google returned an error); null when Core Web Vitals is off.
has_revenuebooleanWhether the site measures revenue in GA4.
weeksarrayPer week, oldest first.
weeks[].datestringLast day of the 28-day window, YYYY-MM-DD.
weeks[].lcp_msnumber or nullLargest Contentful Paint in milliseconds.
weeks[].inp_msnumber or nullInteraction to Next Paint in milliseconds.
weeks[].clsnumber or nullCumulative Layout Shift.
weeks[].revenue_per_sessionnumber or nullGA4 revenue per session, in the currency of the GA4 property.
weeks[].key_event_rate_pctnumber or nullShare of sessions with a key event, 0-100.
weeks[].engagement_secondsinteger or nullEngaged time per session in seconds.
weeks[].bounce_rate_pctnumber or nullShare of sessions that were not engaged, 0-100.
weeks[].pages_per_sessionnumber or nullPage views per session.
relationsarrayOne per speed metric and visit metric; empty without GA4.
relations[].speed_metricstringlcp_ms, inp_ms or cls.
relations[].visit_metricstringrevenue_per_session, key_event_rate_pct, engagement_seconds, bounce_rate_pct or pages_per_session.
relations[].betterstringhigher or lower: which way the visit metric is better.
relations[].blocksintegerPairs of 28-day blocks, 28 days apart, with both values.
relations[].blocks_movedintegerOf those, the blocks where speed changed noticeably.
relations[].blocks_withintegerOf those, the blocks where the visit metric moved the better way when speed got faster (or the worse way when slower).
relations[].rhonumber or nullSpearman rank correlation of the changes, -1 to 1; null with too few blocks or no movement.
relations[].strengthstringtoo_few (under 6 blocks), none, some or clear.
relations[].directionstring or nullexpected (faster goes with better visits) or opposite; null without strength.
relations[].biggest_changeobject or nullThe biggest change in speed between two weeks 28 days apart, with the visit metric before and after; null when there is none.
relations[].biggest_change.directionstringfaster or slower.
relations[].biggest_change.change_pctnumberChange in speed in percent.
relations[].biggest_change.beforeobjectThe week before.
relations[].biggest_change.before.datestringYYYY-MM-DD.
relations[].biggest_change.before.speednumberThe speed metric.
relations[].biggest_change.before.visitnumberThe visit metric.
relations[].biggest_change.afterobjectThe week after.
relations[].biggest_change.after.datestringYYYY-MM-DD.
relations[].biggest_change.after.speednumberThe speed metric.
relations[].biggest_change.after.visitnumberThe visit metric.
White-label / agency

White-label Core Web Vitals API. Your hostname returns client LCP, INP and CLS.

When your agency runs MarketingTracer on a custom domain, the REST API and the MCP server answer on that domain under your own name. Your personal key reads every client site you can see, so a single integration feeds all your client reports. You decide whether your clients also get the API and the MCP. The whole dashboard is white label in every plan.

All endpointsSEO API hub REST & MCPRank tracker API AI assistantsSEO MCP server

Free 14-day trial

MarketingTracer smart A.I. tool shows you what you need to rank. Then we'll help you get it done.

  • No credit card required
  • No installation needed
  • No strings attached
Start your 14-day trial

Core Web Vitals API questions

Something else? Ask us.

What data does the Core Web Vitals API return?

Real-user field data from Google's Chrome UX Report (CrUX) for the whole site (origin) and up to six tracked competitors. Each value is the 75th percentile of real Chrome visits over a rolling 28-day window on phones, desktops or all devices together. Across the five endpoints you get LCP, INP, CLS, server TTFB, FCP, network RTT, navigation types and ad load.

Does the API return Core Web Vitals per page or for the whole site?

Whole-site (origin) data only. For individual URLs, call get_scan on the MarketingTracer MCP server to read the per-page lab timings from our SEO audit. If you need field data from the site's own visitors, use CoreDash, real user monitoring from the same team. You get Core Web Vitals per page, per element and per day.

How does the API handle domains with missing Chrome UX Report data?

Missing values return null, and explicit status fields tell you why. When Google has too little Chrome traffic for a domain and device, state is no_data (or pending right after a domain is added). Domains with LCP and CLS but no INP value return partial: true in crux.summary. If a metric has no value in the newest 28-day window, crux.summary and crux.diagnose return the last measured window with its date and stale: true.

Why does enabled return false on a newly added site?

Core Web Vitals starts switched off on each site in MarketingTracer. Anyone with edit rights on the site can open Speed in the app and click Switch on to pull 40 weeks of history for the site and its competitors right away. Over time, crux.history returns up to 104 stored weeks. Every public API and MCP call is read-only, so you switch it on once in the dashboard.

How do I use the Core Web Vitals tools in Claude Code or Cursor?

Connect your MCP client over HTTP to /rest/mcp/server on app.marketingtracer.com and send your personal key in the Authorization bearer header. Your key is on the MCP page in the app. Claude Code and Cursor work right away, and the Claude app works where custom connectors support request headers (beta). ChatGPT needs a sign-in flow and does not connect. Copy-paste setup snippets are on the SEO MCP page.

Does crux.visitors require Google Analytics 4?

Yes, for the session numbers and correlations. Once GA4 is connected to the site in MarketingTracer, crux.visitors returns ga4: "ok" and pairs your speed history with GA4 session metrics (including revenue per session when has_revenue is true). When GA4 is not connected, the endpoint still returns the 28-day speed series with ga4: "connect" and an empty relations array.

What is the rate limit, and what does API access cost?

60 calls per minute per personal API key, shared across the REST API and the MCP server. Both are included in every MarketingTracer plan at no extra cost. See the pricing page for plan details.