Claude’s Matomo connector can only apply a segment already saved in Matomo. To follow a series of pages, the fastest way is to create the segments through the API with a script, then test each definition against raw visits before relying on it.
The problem
I wanted to know how many visits entered on each of my 13 pillar pages, and which came from search engines. The MCP reads reports, but it refuses a segment passed on the fly: it has to exist in the interface. Creating them by hand, one by one, with their conditions, is slow and error-prone.
The hack: SegmentEditor.add
Matomo’s API lets you save a segment with a single POST call. The script below creates an “all pillar pages” segment, a “via search engines” segment and one segment per page:
import os, requests, urllib.parse as up
URL, TOKEN, SITE_ID = os.environ["MATOMO_URL"].rstrip("/"), os.environ["MATOMO_TOKEN_AUTH"], 1
def api(method, **params):
params.update(module="API", method=method, format="json", token_auth=TOKEN)
return requests.post(f"{URL}/index.php", data=params, timeout=60).json()
# Value encoded twice, like segments created from the interface
enc = lambda s: up.quote(up.quote(s, safe=""), safe="")
pages = ["email-marketing", "sms-marketing", "email-deliverability"]
cond = lambda p: f"entryPageUrl=^{enc('https://mysite.com/' + p + '/')}"
all_pages = ",".join(cond(p) for p in pages)
existing = {s["name"] for s in api("SegmentEditor.getAll", idSite=SITE_ID)}
segments = [("SEO | Entry pillar pages (all)", all_pages),
("SEO | Entry pillar pages via search", all_pages + ";referrerType==search")]
segments += [(f"SEO | Entry pillar {p}", cond(p)) for p in pages]
for name, definition in segments:
if name in existing:
continue
print(name, api("SegmentEditor.add", name=name, definition=definition,
idSite=SITE_ID, autoArchive=1, enabledAllUsers=1))
Three points matter:
- POST is mandatory. With a GET, the call is refused.
- The double encoding. URL values are encoded twice (
%253Afor a colon), as in segments from the interface. Without it, the segment matches nothing. autoArchive=1asks Matomo to pre-compute the segment’s reports, which is the condition for the MCP to be able to read them.
The API token comes from an environment variable, not from the script or from the conversation with Claude.
Test a definition before trusting it
A badly written segment doesn’t return an error, it returns zero. Before using it, I test it against raw visits, which accept any definition:
visits = api("Live.getLastVisitsDetails", idSite=SITE_ID, period="range",
date="2026-09-01,2026-10-07", segment=definition, filter_limit=500)
print(len(visits), "visits")
On my 15 segments, the test gave me entries per page since September 1 at a glance: 31 visits in total on the pillar pages, 12 of them from search engines, and zero for three of them. A segment that returns zero in this test isn’t necessarily wrong, but it deserves a second look.
The limits
- For a past period, the raw visits API is what gave me the numbers: a segment’s reports only cover what Matomo has archived.
- Visits that declined the cookie banner don’t exist for Matomo: the figures are a share of real traffic.
- Too many segments weigh down archiving. Keep the ones you use.
Key takeaway
When the reading tool demands saved segments, create them through the API, test them against raw visits, and file them under a name prefix (“SEO | “) to find them again. A fifteen-line script replaces an hour of clicking.