MCP
Peeko as tools for your agent. One endpoint, one bearer key, and the same reads the dashboard makes.
Peeko hosts an MCP server, so the agent that built your site can also answer questions about the traffic it brought in. One endpoint, one key, and the same site scoped reads the dashboard makes.
https://usepeeko.com/api/mcpIt is read only except for one tool that creates a site, and it is scoped to your own data by construction: every call goes through the same site scoped queries the dashboard uses, so a key can only ever read its own team's sites, never raw SQL, and never a visitor's address.
No account yet? Sign up from your agent
You do not need an account, or a key, to connect. Point your agent at the same address with no
header and it gets exactly one tool, sign_up.
claude mcp add --transport http peeko https://usepeeko.com/api/mcp
codex mcp add peeko --url https://usepeeko.com/api/mcp
gemini mcp add --transport http peeko https://usepeeko.com/api/mcpThen ask it: "Use the peeko MCP server to send a Peeko sign in link to my email address."
You finish in a browser. Opening the emailed link is what creates the account, or signs you in, because one link does both. It expires in 15 minutes and works once. Choosing a size and paying happen on the web. An agent never touches checkout, and this tool never returns a session, a token or a key.
Its answer is the same sentence whether or not the address already has an account. That is deliberate, so nobody can use this endpoint to discover who has a Peeko account. It is rate limited per address and per network, so asking repeatedly will not produce more mail.
Once you are signed up and subscribed, come back for a key. The analytics tools stay invisible until you reconnect with one, and the sign up tool disappears from the list once you do.
Get a key first
The API keys card under Your account at usepeeko.com/settings mints one. Name the key, create it, and copy either the raw key or the ready made connect command from the panel that appears once. The same card lists your keys with their prefix, creation date and last use. Rotate replaces a key with a fresh one under the same name and shows the new secret once, and Revoke deletes it. Both take effect immediately, and nothing warns a client that was using the old key.
Send the key as a bearer token, never as a session cookie.
Authorization: Bearer mcpk_your_key_hereA key belongs to a person and sites belong to a team, so a key reaches every site of every team you are a member of while that team's plan is active, and no more. Both are re-checked on every call rather than baked into the key, so removing someone closes their access at the same moment it closes their dashboard, and a team whose subscription lapses disappears from the key until it is back.
Connect a client
Every client below reads the same endpoint. Two of them take a command, the rest take a config file.
Claude Code
claude mcp add --transport http peeko https://usepeeko.com/api/mcp --header "Authorization: Bearer mcpk_your_key_here"claude mcp list should then show peeko as connected. The tools load at the start of a new
session, or check /mcp in the one you are in.
Codex
codex mcp add peeko --url https://usepeeko.com/api/mcp --header "Authorization: Bearer mcpk_your_key_here"Claude Desktop, Cursor, or any client with a config file
An HTTP server with a header. Cursor reads ~/.cursor/mcp.json for every project, or
.cursor/mcp.json for one.
{
"mcpServers": {
"peeko": {
"type": "http",
"url": "https://usepeeko.com/api/mcp",
"headers": { "Authorization": "Bearer mcpk_your_key_here" }
}
}
}Then ask the assistant to list your sites and go from there. Call the explain_metrics prompt for
exact metric definitions, including the one that catches people out: visitors are distinct daily
hashes, so a multi day total sums daily uniques.
Reading the answers
A few payload conventions are worth knowing before you ask an agent to reason about the numbers.
- Money is in minor units.
499900with currencyUSDis $4,999.shownMinoris the one to quote, andkindsays whether it came from Stripe or from the site's own report. The two are never added together. - Search CTR is a fraction between 0 and 1, so
0.042is 4.2 percent, and position is an average rank where lower is better. - The two search tools read either engine.
providerpicksgoogleorbing, and the response says which one answered. Left out, it is Google when the site is linked to a Search Console property and Bing when it is linked only to a Bing site. - A search window never lines up with
get_summary's range. Google publishes about three days late, Bing publishes on no schedule at all, and both report in their own Pacific days. Every search response carries the exact dates it covers. Bing's query rows are weekly buckets dated to a Friday, so a window shorter than a week can hold none, and position then comes back null rather than zero. - Ranges follow the site's own time zone, reported as
timeZoneon every analytics range object. A search range carries notimeZoneat all, deliberately, because search days are the engine's own Pacific days. A custom window is writtenYYYY-MM-DD..YYYY-MM-DD, covers at most 731 days, cannot end in the future, and includes both days.
Tool reference
Every tool the server advertises to a key, with the input schema a client sees. Plus one prompt,
explain_metrics, defining every metric and the privacy model.
list_sites
List the sites you can read (id, name, domain, emoji). Call this first, because every other tool needs a site id.
Takes no input.
create_site
Create a new site in Peeko and get the tracking snippet for it. Returns the site id, its public site key, and a ready-to-use prompt describing exactly what to add to the site's HTML. If your key reaches more than one organization you must pass org (call list_sites to see the orgId of existing sites).
| Input | Type | Required | What it is |
|---|---|---|---|
name | string | yes | What to call the site in Peeko, e.g. 'Acme marketing'. |
domain | string | yes | The site's domain, without a scheme or path, e.g. 'acme.com'. |
org | string | no | Organization id to create the site in. Required only when this key reaches more than one. |
get_summary
Headline metrics for a site + range (+ optional filters): views, visitors, sessions, bounce rate, pages/session, avg duration, plus the previous window (for deltas) and returning-vs-new. previous is null for the all range and when the previous window is not a meaningful baseline: the site had no data before it began, or it holds fewer than 10 people.
| Input | Type | Required | What it is |
|---|---|---|---|
site | string | yes | Site id (from list_sites). |
range | one of today, 24h, 7d, 30d, 90d, all or string matching ^(\d{4})-(\d{2})-(\d{2})\.\.(\d{4})-(\d{2})-(\d{2})$ | no | Time window. One of: today, 24h, 7d, 30d, 90d, all, or a custom window as YYYY-MM-DD..YYYY-MM-DD (calendar days in the site's time zone, at most two years, not in the future). Default 7d. |
filters | object with page, source, country, device, browser, browser_version, os, os_version, screen, viewport, utm_source, utm_medium, utm_campaign | no | Optional dimension filters that narrow every metric (e.g. { country: 'US', device: 'mobile', page: '/pricing' }). |
get_top_pages
Most-viewed pages for a site + range (+ optional filters). Each row: { name: path, count: views, pct: share of window views }. Returns 10 rows unless you pass limit (1 to 100).
| Input | Type | Required | What it is |
|---|---|---|---|
site | string | yes | Site id (from list_sites). |
range | one of today, 24h, 7d, 30d, 90d, all or string matching ^(\d{4})-(\d{2})-(\d{2})\.\.(\d{4})-(\d{2})-(\d{2})$ | no | Time window. One of: today, 24h, 7d, 30d, 90d, all, or a custom window as YYYY-MM-DD..YYYY-MM-DD (calendar days in the site's time zone, at most two years, not in the future). Default 7d. |
filters | object with page, source, country, device, browser, browser_version, os, os_version, screen, viewport, utm_source, utm_medium, utm_campaign | no | Optional dimension filters that narrow every metric (e.g. { country: 'US', device: 'mobile', page: '/pricing' }). |
limit | integer | no | How many rows to return, 1 to 100. Defaults to 10. |
get_sources
Referring sources (traffic by host) for a site + range (+ optional filters). 'Direct' means no referrer. Each row: { name: source, count: views, pct }. Returns 10 rows unless you pass limit (1 to 100).
| Input | Type | Required | What it is |
|---|---|---|---|
site | string | yes | Site id (from list_sites). |
range | one of today, 24h, 7d, 30d, 90d, all or string matching ^(\d{4})-(\d{2})-(\d{2})\.\.(\d{4})-(\d{2})-(\d{2})$ | no | Time window. One of: today, 24h, 7d, 30d, 90d, all, or a custom window as YYYY-MM-DD..YYYY-MM-DD (calendar days in the site's time zone, at most two years, not in the future). Default 7d. |
filters | object with page, source, country, device, browser, browser_version, os, os_version, screen, viewport, utm_source, utm_medium, utm_campaign | no | Optional dimension filters that narrow every metric (e.g. { country: 'US', device: 'mobile', page: '/pricing' }). |
limit | integer | no | How many rows to return, 1 to 100. Defaults to 10. |
get_engagement
Pages ranked by how long visitors stay, which answers 'which pages hold attention longest'. Each row: { path, avgSeconds, transitions }.
| Input | Type | Required | What it is |
|---|---|---|---|
site | string | yes | Site id (from list_sites). |
range | one of today, 24h, 7d, 30d, 90d, all or string matching ^(\d{4})-(\d{2})-(\d{2})\.\.(\d{4})-(\d{2})-(\d{2})$ | no | Time window. One of: today, 24h, 7d, 30d, 90d, all, or a custom window as YYYY-MM-DD..YYYY-MM-DD (calendar days in the site's time zone, at most two years, not in the future). Default 7d. |
filters | object with page, source, country, device, browser, browser_version, os, os_version, screen, viewport, utm_source, utm_medium, utm_campaign | no | Optional dimension filters that narrow every metric (e.g. { country: 'US', device: 'mobile', page: '/pricing' }). |
get_live_now
Who is on the site right now: visitors active in the last 2 minutes: online count, live countries, and recent per-visitor detail (page, country, device, source). online always counts everyone, including the owner's own browsers (tagged with ?me=1, flagged self on a visitor row); others is the same cohort without them, and countSelf says which of the two this site's dashboard shows.
| Input | Type | Required | What it is |
|---|---|---|---|
site | string | yes | Site id (from list_sites). |
get_bot_summary
AI-crawler traffic for a site + range (server-detected, separate from human analytics): totals, 404 count, and breakdowns by category / provider / agent / most-crawled pages / most-requested-but-missing pages. Also returns answers, which are recent moments an assistant fetched a page WHILE ANSWERING SOMEONE (the question itself is never observable), and referrals, humans who clicked through from an AI product. Pass provider (e.g. "OpenAI", "Anthropic") to narrow every field to one company. ⚠ Check responseStatusAvailable before saying anything about 404s: it is false when the site's install cannot observe response codes (its middleware sees the request, not the response), and then missingHits is null and missing is empty because 404s are UNKNOWABLE for that site, not because there were none. Bot identity comes from the user-agent alone and is never verified, so a crawler's name is a claim, not a fact.
| Input | Type | Required | What it is |
|---|---|---|---|
site | string | yes | Site id (from list_sites). |
range | one of today, 24h, 7d, 30d, 90d, all or string matching ^(\d{4})-(\d{2})-(\d{2})\.\.(\d{4})-(\d{2})-(\d{2})$ | no | Time window. One of: today, 24h, 7d, 30d, 90d, all, or a custom window as YYYY-MM-DD..YYYY-MM-DD (calendar days in the site's time zone, at most two years, not in the future). Default 7d. |
provider | string | no | Narrow to one provider, e.g. OpenAI, Anthropic, Google, Perplexity. Case does not matter, and a name Peeko does not know is an error naming the ones it does. |
list_goals
The site's goals with their numbers for a range. A goal is something the owner decided counts as a win: a page reached, a click, or a form sent. Each returns its count (how many times it happened) and its conversion (the share of people in the range who did it at least once). previous is null for the all range and when the previous window is not a meaningful baseline: the site had no data before it began, or it holds fewer than 10 people. Archived goals are not included.
| Input | Type | Required | What it is |
|---|---|---|---|
site | string | yes | Site id (from list_sites). |
range | one of today, 24h, 7d, 30d, 90d, all or string matching ^(\d{4})-(\d{2})-(\d{2})\.\.(\d{4})-(\d{2})-(\d{2})$ | no | Time window. One of: today, 24h, 7d, 30d, 90d, all, or a custom window as YYYY-MM-DD..YYYY-MM-DD (calendar days in the site's time zone, at most two years, not in the future). Default 7d. |
get_goal
One goal by id, with its definition and its numbers for a range, including the same previous window on the same rule: null for the all range and when that window is not a meaningful baseline (the site had no data before it began, or it holds fewer than 10 people). Use list_goals first to get goal ids.
| Input | Type | Required | What it is |
|---|---|---|---|
site | string | yes | Site id (from list_sites). |
goal | string | yes | Goal id (from list_goals). |
range | one of today, 24h, 7d, 30d, 90d, all or string matching ^(\d{4})-(\d{2})-(\d{2})\.\.(\d{4})-(\d{2})-(\d{2})$ | no | Time window. One of: today, 24h, 7d, 30d, 90d, all, or a custom window as YYYY-MM-DD..YYYY-MM-DD (calendar days in the site's time zone, at most two years, not in the future). Default 7d. |
list_funnels
The site's funnels: an ordered set of pages or goals people are counted through, inside a completion window. Returns each funnel's steps, its step-1 people and its end-to-end conversion for the range.
| Input | Type | Required | What it is |
|---|---|---|---|
site | string | yes | Site id (from list_sites). |
range | one of today, 24h, 7d, 30d, 90d, all or string matching ^(\d{4})-(\d{2})-(\d{2})\.\.(\d{4})-(\d{2})-(\d{2})$ | no | Time window. One of: today, 24h, 7d, 30d, 90d, all, or a custom window as YYYY-MM-DD..YYYY-MM-DD (calendar days in the site's time zone, at most two years, not in the future). Default 7d. |
get_funnel
One funnel by id, step by step: people at each step, the share of step 1 that reached it, and how many days the computation actually covered (a funnel is capped at the smaller of 90 days and the site's retention window, and counts fewer people than really converted over a long one). Use list_funnels first to get funnel ids.
| Input | Type | Required | What it is |
|---|---|---|---|
site | string | yes | Site id (from list_sites). |
funnel | string | yes | Funnel id (from list_funnels). |
range | one of today, 24h, 7d, 30d, 90d, all or string matching ^(\d{4})-(\d{2})-(\d{2})\.\.(\d{4})-(\d{2})-(\d{2})$ | no | Time window. One of: today, 24h, 7d, 30d, 90d, all, or a custom window as YYYY-MM-DD..YYYY-MM-DD (calendar days in the site's time zone, at most two years, not in the future). Default 7d. |
get_revenue
Money for a range, always split by kind. All amounts are in the currency's MINOR unit (cents for USD, whole yen for JPY), so 499900 with currency USD is $4,999. verifiedMinor is what Stripe confirmed, reportedMinor is what the site's own page reported, and shownMinor is the one to quote: the verified sum when the range holds any verified sale, the reported sum otherwise, with kind saying which. Also returns revenue by source and by page, a series per bucket, and totals in any other currencies, which are never converted. A site with no sales returns zeros, empty arrays and a null currency rather than a different shape.
| Input | Type | Required | What it is |
|---|---|---|---|
site | string | yes | Site id (from list_sites). |
range | one of today, 24h, 7d, 30d, 90d, all or string matching ^(\d{4})-(\d{2})-(\d{2})\.\.(\d{4})-(\d{2})-(\d{2})$ | no | Time window. One of: today, 24h, 7d, 30d, 90d, all, or a custom window as YYYY-MM-DD..YYYY-MM-DD (calendar days in the site's time zone, at most two years, not in the future). Default 7d. |
get_search_summary
Search engine headline numbers for a window: clicks, impressions, CTR and average position, with the immediately preceding equal window to compare against. Google Search Console and Bing Webmaster Tools both answer here, chosen by the provider argument; omitted, it is Google when the site is linked to a Search Console property and Bing when it is linked only to a Bing site, and the response reports which one in provider. CTR is a FRACTION between 0 and 1, so 0.042 is 4.2 percent. Position is an average rank where LOWER IS BETTER, so a move from 12 to 7 is an improvement. Google publishes this data about three days late, so the window ends three days ago; Bing publishes on no schedule at all, so its window ends on the latest day Bing has actually reported, which may be several days back. Either way window gives the exact dates covered and they are Pacific days, not the site's own: do not assume the window lines up with get_summary's range. FOR BING, POSITION IS THE ONE NUMBER THAT IS NOT DAILY: Bing reports no rank with its totals, so clicks, impressions and CTR are daily figures while position is the impression-weighted mean of the WEEKLY query buckets the window covers, each dated to the Friday its week ended on. A window shorter than a week may hold no weekly bucket, in which case position is null and the query list is empty; that is an absence of rank data for those days, not a rank of zero and not a claim about the site. today and 24h are served as 7d because no data that recent exists, and range reports what was actually served. previous is null for the all range, which has nothing before it, and when the previous window is not a meaningful baseline: it holds no impressions, or Peeko's data for that provider does not reach back through the whole of it. Every number is for the one site named by the site argument. A site that is not linked to the provider asked for is an error, not zeros, and so is a site linked to neither. A site whose first sync has not completed is an error, not zeros. When the last refresh from the provider failed, the response carries stale: true and lastError, and the numbers are the last ones that landed.
| Input | Type | Required | What it is |
|---|---|---|---|
site | string | yes | Site id (from list_sites). |
range | one of today, 24h, 7d, 30d, 90d, all or string matching ^(\d{4})-(\d{2})-(\d{2})\.\.(\d{4})-(\d{2})-(\d{2})$ | no | Time window. One of: today, 24h, 7d, 30d, 90d, all, or a custom window as YYYY-MM-DD..YYYY-MM-DD (calendar days in the site's time zone, at most two years, not in the future). Default 7d. |
provider | one of google, bing | no | Which search engine: google or bing. Omitted, this is Google when the site is linked to a Search Console property and Bing when it is linked only to a Bing site. The response says which one answered. |
get_search_queries
The search queries that brought people to a site, in one of four views, from Google Search Console or Bing Webmaster Tools. top is the biggest by clicks. winners and losers are the queries that gained and lost the most clicks against the previous equal window. opportunities are queries ranking between 8 and 20 with at least 50 impressions: real demand just off the first page. Every row carries clicks, impressions, CTR as a FRACTION between 0 and 1 (0.042 is 4.2 percent), average position where LOWER IS BETTER, and deltaClicks, the clicks gained or lost against the previous window, which is null on EVERY row whenever that window is not a baseline: the all range has none, and otherwise it may hold no impressions or Peeko's data for that provider may not reach back through the whole of it. provider picks the engine and the response reports which one answered; omitted, it is Google when the site is linked to a Search Console property and Bing when it is linked only to a Bing site. Google drops rare queries it cannot anonymize, so these rows always sum to LESS than get_search_summary's totals for the same window, and the difference is not missing data. BING'S QUERY ROWS ARE WEEKLY BUCKETS, each dated to the Friday its week ended on, while its totals are daily: a 7d window therefore holds ONE bucket and compares it against ONE bucket, so a short range gives few rows and small movements by design. The Google window ends about three days ago and the Bing window ends on the latest day Bing has reported; window gives the dates either way. A window shorter than a week may hold no weekly bucket, in which case position is null and the query list is empty: no Bing rows exist for those days, which is not the same as the site having no queries. Every row is for the one site named by the site argument, and range reports the window actually served, which is 7d when you ask for today or 24h. winners and losers are an error whenever there is no window worth comparing against: the all range, which has nothing before it, and a previous window that is not a meaningful baseline, meaning it holds no impressions or Peeko's data does not reach back through the whole of it. The Search tab hides the same two lists in the same two cases. A site whose first sync has not completed is an error, not zeros. When the last refresh from the provider failed, the response carries stale: true and lastError, and the numbers are the last ones that landed.
| Input | Type | Required | What it is |
|---|---|---|---|
site | string | yes | Site id (from list_sites). |
range | one of today, 24h, 7d, 30d, 90d, all or string matching ^(\d{4})-(\d{2})-(\d{2})\.\.(\d{4})-(\d{2})-(\d{2})$ | no | Time window. One of: today, 24h, 7d, 30d, 90d, all, or a custom window as YYYY-MM-DD..YYYY-MM-DD (calendar days in the site's time zone, at most two years, not in the future). Default 7d. |
provider | one of google, bing | no | Which search engine: google or bing. Omitted, this is Google when the site is linked to a Search Console property and Bing when it is linked only to a Bing site. The response says which one answered. |
view | one of top, winners, losers, opportunities | no | Which view. One of: top, winners, losers, opportunities. Default top. |
limit | integer | no | How many rows to return, at most 100. Default 20. |
Next
- Install Peeko if the tracker is not on the site yet.
- The command line speaks this same endpoint from a terminal.