matchstick

Matchstick for agents

Version 1.5.0 · All sections

What it is

Connect Matchstick and your agent runs your SEO.

Matchstick watches a site's Search Console, crawls its pages, finds what is costing clicks and drafts the fix as a pull request a person merges. The agent API opens that loop to the agent you already use: over MCP it reads the data, the findings and the changes, starts research, checks and drafts, reviews and, where the site's owner allows it, approves. Everything an agent does is a row on the site's activity page, and nothing changes on a site until a pull request is merged.

The server is at https://matchstick.io/mcp. It speaks the MCP protocol revision 2026-07-28 and the 2025 revisions, with 35 tools, a playbooks resource and a weekly-review prompt. The same calls are available as JSON at https://matchstick.io/api/v1 (OpenAPI).

How to connect

By URL, with OAuth

Add https://matchstick.io/mcp as a custom connector in claude.ai (Settings → Connectors), in ChatGPT (Settings → Connectors, developer mode), in Cursor, or in any client that follows the MCP authorization spec. The client finds the authorization server through https://matchstick.io/.well-known/oauth-protected-resource/mcp, sends you to sign in with Google or GitHub, and shows the consent page: which sites, which permissions, a weekly credit budget. The client identifies itself with a Client ID Metadata Document or registers dynamically; PKCE S256 and the resource parameter are required. Access tokens last an hour and refresh for thirty days; revoke a connection at any time from Settings → Connected agents.

Claude Code and Cursor

claude mcp add --transport http matchstick https://matchstick.io/mcp

Cursor takes the same URL in mcpServers ({ "url": "https://matchstick.io/mcp" }) and completes OAuth on the first call.

With a personal API key

Settings → API keys mints a key (msk_live_…) with the scopes and sites you choose. Send it as a bearer token to /mcp or /api/v1:

curl -H "Authorization: Bearer msk_live_…" https://matchstick.io/api/v1/sites

Scopes

GroupScopesAllows
See my sites and reportssites:read data:read findings:read changes:read reports:read activity:read runs:readEvery read tool. The bundle matchstick:read stands for all of them.
Run research and draft fixes (uses credits)findings:write runs:writeResearch passes, checks, drafts, reverts and suggested findings; uses credits.
Review and approve fixes, within each site's autonomy levelchanges:review changes:approveComment, request changes, reject, approve (within the site's level).
Tell Matchstick when a change is live on the sitechanges:deployNot available yet.

The full list: sites:read, data:read, findings:read, changes:read, reports:read, activity:read, runs:read, findings:write, runs:write, changes:review, changes:approve, changes:deploy, cloudflare:apply.

Tool reference

Read tools are free. Propose tools cost credits (research pass 2, check 4, draft or revert 10, revision 5) from the connection's weekly budget. Every write takes an idempotency_key; the propose tools also take dry_run. Results carry structuredContent (the same object the REST API returns) and a short text summary; a failure is an isError result whose text is the error body below.

list_sites List sites

read-onlyidempotent scope sites:read

Every site this credential may see, with connection health (Search Console, GitHub, Cloudflare), the autonomy level and how many changes and findings are open, plus the site ids and domains the other tools take. Free.

ParameterTypeDescription
include_pausedbooleanInclude paused sites.

get_connection_info This credential

read-onlyidempotent scope sites:read

Who is calling: the account, the API key or OAuth grant this call runs under, its scopes and site restriction, and every site it can see with what is connected (the Search Console property and whether it is a domain or URL-prefix property, the repository, the Cloudflare zone) and the matchstick.io pages where a person connects what is missing. Call it when a site seems absent or a tool answers not_found. Free. Sites and connections are only ever added by a person in the web app.

No parameters.

get_updates_since What changed since

read-onlyidempotent scope sites:read

Anything new since a moment, in one free call: the weekly reports that finished, the findings that became open or open again (title, kind, doer, confidence, pages, status) and the changes that became ready for review, oldest first, plus every site's last report and its open finding counts by doer (you / matchstick / matchstick_with_approval). Pass since as an ISO timestamp or the next_cursor of your previous call (default: the last 24 hours) and keep next_cursor for the next poll. Nothing happened → changed: false and empty lists, so a daily routine can call this and stay quiet. One site, or every site this credential sees. Free. The same feed drives the webhooks a person sets up on Settings → Webhooks (report.finished, finding.opened, change.ready_for_review).

ParameterTypeDescription
sitestringSite id or domain; omit for every site this credential sees.
sincestringAn ISO 8601 moment (2026-10-26T12:00:00Z) or the next_cursor of the previous call. Default: 24 hours ago.
limitintegerItems across reports, findings and changes, at most 500 (default 200); more: true means call again with next_cursor.

list_needs_you Everything waiting on a person

read-onlyidempotent scope sites:read

What the web app's Needs review queue lists, in one free call, for every site this credential sees or one site: changes open for review, merged changes not seen live yet (with the last look), checks that ended in needs_you or draft_pr and weren't acted on, open findings whose doer is you or matchstick_with_approval with no pull request standing in for them, and sites the crawler can't reach. Each item names the tools that settle it. Open finding counts alone miss the checks and the deploys; this is the count the person sees. Free.

ParameterTypeDescription
sitestringSite id or domain; omit for every site this credential sees.
limitintegerRows per page, at most 500.

get_site_overview Site overview

read-onlyidempotent scope sites:read

One site's last 28 stored days against the 28 before: clicks, impressions, CTR and average position with the change, the plan's credits, what awaits review or a deploy, the pages frozen under evaluation (path, until when, the change behind it) and connection problems; plus the Search Console property kind, how far back the stored data reaches and the history import's state. Free.

ParameterTypeDescription
sitestringrequiredSite id or domain, e.g. calculator.pro.

get_agent_policy What this credential may do here

read-onlyidempotent scope sites:read

The site's autonomy level and what this credential can propose, review and approve there, the weekly budget left, the merge cap, quiet hours, and who the human approvers are. Free. A refusal from another tool (policy_denied, budget_exhausted) is explained here.

ParameterTypeDescription
sitestringrequiredSite id or domain, e.g. calculator.pro.

search_performance Search Console rows

read-onlyidempotent scope data:read

Clicks, impressions, CTR and average position by page, by query, or by page and query, for a date range of at most 16 months (default the last 28 stored days). The page and query filters apply on every dimension: dimension page with a query lists the pages that rank for it. Sort by clicks (default), impressions, position, CTR or opportunity (high impressions at positions 5–20 with a CTR below the typical one, by missed clicks); min_impressions drops the long tail. Up to 1,000 rows; total_rows says how many the filters leave in all. Free.

ParameterTypeDescription
sitestringrequiredSite id or domain, e.g. calculator.pro.
fromstringYYYY-MM-DD
tostringYYYY-MM-DD
dimension"page" | "query" | "page_query"
pagestringOnly this page path.
querystringOnly this query (exact, or a substring with query_match contains).
query_match"exact" | "contains"
sort"clicks" | "impressions" | "position" | "ctr" | "opportunity"
min_impressionsintegerOnly rows with at least this many impressions over the range.
limitintegerRows per page, at most 1000.

get_performance_totals Site-wide totals

read-onlyidempotent scope data:read

Clicks, impressions, CTR and average position for the whole site over a date range (default the last 28 stored days), with the previous period of the same length and the change: absolute, percent and a z-score (|z| under 3 is week-to-week noise). Free.

ParameterTypeDescription
sitestringrequiredSite id or domain, e.g. calculator.pro.
fromstringYYYY-MM-DD; default 27 days before to.
tostringYYYY-MM-DD; default the last stored day.
comparebooleanfalse skips the previous period (default true).

get_page One page

read-onlyidempotent scope data:read

One page of the site: 28-day metrics against the 28 before, its top 20 queries, the latest crawl facts (status, title, description, H1, canonical, robots), the on-page content (word count, headings, images, schema types), who links to it from the latest crawl (inlinks, orphan or not, anchor text where the crawl kept it), Google's indexing verdict, Core Web Vitals, whether it is frozen under evaluation, and the changes touching it. Free.

ParameterTypeDescription
sitestringrequiredSite id or domain, e.g. calculator.pro.
pathstringrequiredThe page path, e.g. /uuid.

get_indexing Indexing by URL

read-onlyidempotent scope data:read

Google's latest URL Inspection verdict for every inspected page of the site: not-indexed pages first with the coverage reason ("Crawled - currently not indexed", "Excluded by 'noindex' tag", …), Google's canonical against the page's own (canonical_mismatch), and counts by coverage state. From the stored inspections (the daily and weekly runs inspect; nothing is fetched now). Free.

ParameterTypeDescription
sitestringrequiredSite id or domain, e.g. calculator.pro.
state"all" | "indexed" | "not_indexed" | "canonical_mismatch"all (default), indexed, not_indexed or canonical_mismatch.
limitintegerRows per page, at most 500; default 50. Page on with cursor.
cursorstringThe next_cursor of the previous page.

get_cannibalization Cannibalization

read-onlyidempotent scope data:read

Queries where two or more pages each take a real share of the impressions over the range (default the last 28 stored days; a page counts from 20% of a query with 200+ impressions), with each page's impressions, clicks, CTR, position and share. Free.

ParameterTypeDescription
sitestringrequiredSite id or domain, e.g. calculator.pro.
fromstringYYYY-MM-DD; default 27 days before to.
tostringYYYY-MM-DD; default the last stored day.
min_impressionsintegerPer query over the range; default 200.
min_sharenumberA page's share of the query's impressions to count as a contender; default 0.2.
limitintegerRows per page, at most 200.

get_content_gaps Content gaps

read-onlyidempotent scope data:read

Searches with impressions that no crawled page is about, from the last 28 stored days and the latest crawl (the weekly pass's opportunities detector, run now): the query and how many variants it groups, impressions, clicks and position, the page that ranks for it today, the closest page and how much of the query's words it covers. Search volume, difficulty and SERP competitors aren't included: Matchstick has no keyword-data connector. Free.

ParameterTypeDescription
sitestringrequiredSite id or domain, e.g. calculator.pro.
min_impressionsintegerPer query group over the window; default 200, as low as 2 for a small site.
limitintegerRows per page, at most 100.

list_findings List findings

read-onlyidempotent scope findings:read

The site's findings (open by default): what Matchstick spotted, who should act (doer), confidence, the pages, and the live check or change behind each. Filter by status, kind or page; sort newest or by estimated gain. Free.

ParameterTypeDescription
sitestringrequiredSite id or domain, e.g. calculator.pro.
statusstring[]open (default), accepted, done, dismissed, snoozed, verifying
kindstring[]Action kinds, e.g. content_edit, code_fix, indexing
pagestring
sort"newest" | "estimated_gain"
limitintegerRows per page, at most 100.
cursorstringThe next_cursor of the previous page.

get_finding One finding

read-onlyidempotent scope findings:read

A finding in full: why, the evidence, the detector facts behind it, the safest next step, and every check and change made for it. Free.

ParameterTypeDescription
finding_idstringrequired

list_changes List changes

read-onlyidempotent scope changes:read

Pull requests Matchstick drafted, newest first, across the sites this credential sees or for one site. Real statuses: queued, drafting, open, merged, closed, failed, no_change; open means awaiting review. Free.

ParameterTypeDescription
sitestringSite id or domain; omit for every site.
statusstring[]queued, drafting, open, merged, closed, failed, no_change
limitintegerRows per page, at most 100.
cursorstringThe next_cursor of the previous page.

get_change One change

read-onlyidempotent scope changes:read

A change in full: why, what changes (before → after per page), the claims checked against the code, the before-merging items to acknowledge, expected effect and measurement, the computed risk, GitHub's live CI and mergeable answer, the head sha to pass to approve_change, the gates, and what this credential may do next. Free; asks GitHub unless live is false.

ParameterTypeDescription
change_idstringrequired
livebooleanfalse skips asking GitHub for CI and mergeable.

get_change_diff The diff

read-onlyidempotent scope changes:read

The unified patches of a change, file by file, paginated at about 200 KB. Free.

ParameterTypeDescription
change_idstringrequired
cursorstringThe next_cursor of the previous page.

get_report A weekly report

read-onlyidempotent scope reports:read

The weekly report: latest by default, or a report id, or a date (YYYY-MM-DD) for the one on or before that day. Headline, scorecard, what changed, the findings and next actions, and the markdown. Free.

ParameterTypeDescription
sitestringrequiredSite id or domain, e.g. calculator.pro.
reportstring"latest" (default), a report id, or YYYY-MM-DD.

get_activity The audit trail

read-onlyidempotent scope activity:read

Who did what: every action by people, agents and Matchstick itself, newest first, with the result, the credits it cost and the request id. Filter by site, actor type, client name or action. Free.

ParameterTypeDescription
sitestring
actor_type"human" | "api_key" | "oauth_client" | "system"
client_namestring
actionstring
limitintegerRows per page, at most 200.
cursorstringThe next_cursor of the previous page.

get_run Poll a run

read-onlyidempotent scope runs:read

The state of an asynchronous run: run_<id> (a research pass or daily loop), pr_<id> (a draft, revision or rebase; the change itself) or check_<id>. Poll until status is succeeded, failed or no_op. Free.

ParameterTypeDescription
run_idstringrequired

get_usage Credits

read-onlyidempotent scope sites:read

Two budgets, told apart: the plan's (account: monthly allowance, spent, balance; exempt accounts are not metered) and this connection's (connection: the weekly credits granted, spent and left on each site, with the week's start and reset). A run needs both. What this month's runs cost by kind, the recent ledger, and why a balance can go below zero. Free.

ParameterTypeDescription
monthstringYYYY-MM, default the current month.

run_research_pass Run a research pass

writesidempotent scope runs:write

The weekly research pass on a site, now: Search Console, crawl, rankings and the report with new findings. About 2 credits from the connection's weekly budget; one at a time per site (a pass already running is returned instead). Poll get_run until it succeeds, then get_report. dry_run: the estimate and what would happen, spending nothing.

ParameterTypeDescription
sitestringrequiredSite id or domain, e.g. calculator.pro.
idempotency_keystringrequiredA key you generate (≤ 64 chars); the same key with the same arguments replays the first answer for 24 hours.
dry_runbooleantrue: the credit estimate and what would happen (including a refusal and why), spending and creating nothing.

investigate_finding Investigate a finding

writesidempotent scope runs:write

A check on a finding: a read-only investigation that ends in a verdict and a recommendation (nothing to change, draft a fix, needs the person, watch). About 4 credits from the weekly budget. A host-variant (www/apex/http redirect) or email-obfuscation finding is settled on the spot by a free probe of the live site instead: status done, the check recorded, the finding resolved when the site passes. The same gate as the daily triage: a check that already ran stands, read it with get_finding. Poll get_run. dry_run spends nothing.

ParameterTypeDescription
finding_idstringrequired
idempotency_keystringrequiredA key you generate (≤ 64 chars); the same key with the same arguments replays the first answer for 24 hours.
dry_runbooleantrue: the credit estimate and what would happen (including a refusal and why), spending and creating nothing.

inspect_pages Inspect pages now

writesidempotent scope runs:write

Ask Google's URL Inspection API about up to fifty pages now and store the verdicts (indexed or not, the coverage reason, Google's canonical). Free: the API costs nothing and the quota is 2,000 a day per property, shared with the daily run (which inspects new sitemap URLs on its own) and the weekly pass. Needs the site's Search Console connection (connection_broken otherwise). dry_run lists what would be inspected.

ParameterTypeDescription
sitestringrequiredSite id or domain, e.g. calculator.pro.
pathsstring[]requiredPage paths on the site, e.g. /guides/colors; at most 50.
idempotency_keystringrequiredA key you generate (≤ 64 chars); the same key with the same arguments replays the first answer for 24 hours.
dry_runbooleantrue: the credit estimate and what would happen (including a refusal and why), spending and creating nothing.

draft_change Draft a fix

writesidempotent scope runs:write

Ask Matchstick to draft the fix for a finding as a pull request on a matchstick/* branch. About 10 credits from the weekly budget. Held by the same gate as the daily triage: the site's dial, the owner's not-now, stale data, protected and frozen pages (page_frozen tells you until when), the open-PR and per-day budgets. Optional guidance (≤ 1000 chars) reaches the drafter as untrusted input: it can narrow the change (which section, how compact, what to keep) but never bends the rules, the risk scoring or the gates. One live draft per finding; an open change is returned to review instead. Poll get_run; the change id is the run id. dry_run spends nothing.

ParameterTypeDescription
finding_idstringrequired
guidancestringHow to narrow the change; untrusted, cannot override the site's rules.
idempotency_keystringrequiredA key you generate (≤ 64 chars); the same key with the same arguments replays the first answer for 24 hours.
dry_runbooleantrue: the credit estimate and what would happen (including a refusal and why), spending and creating nothing.

suggest_finding Suggest a finding

writesidempotent scope findings:write

Bring your own idea for a page ("add a baker's-percentage table to /sourdough-hydration-calculator"). Matchstick checks it against Search Console (28 days of impressions for the page and the queries you name) and the last crawl (the page exists, returns 200, isn't noindex). Free. Accepted, it is a finding with source agent that draft_change can take up and the person sees in the app; rejected, a reason. dry_run checks without storing.

ParameterTypeDescription
sitestringrequiredSite id or domain, e.g. calculator.pro.
pagestringrequiredThe page path, e.g. /uuid.
ideastringrequiredWhat to add or change, and why; 10–500 characters.
queriesstring[]Search queries the idea serves; their impressions and positions on the page are checked.
kind"content_edit" | "code_fix"
idempotency_keystringrequiredA key you generate (≤ 64 chars); the same key with the same arguments replays the first answer for 24 hours.
dry_runbooleantrue: the credit estimate and what would happen (including a refusal and why), spending and creating nothing.

update_finding_status Mark a finding done, dismissed, snoozed or open

writesidempotent scope findings:write

Settle a finding from outside Matchstick's own loops: the person did it in Search Console, Cloudflare or by hand, or it should be left alone. status done (optional note, e.g. "Indexed in GSC on 2026-10-26"), dismissed (reason: wont_fix, false_positive, duplicate, other; the daily triage reads it as never), snoozed (snooze_days, default 30, at most 365; read as not now) or open (back into Needs you). Free. With verify: true on done, the claim alone doesn't close it: an indexing finding is checked against Google's latest stored inspection of its pages (call inspect_pages first) and is done only when every page is indexed by an inspection newer than the finding; otherwise it goes to verifying and the daily run asks Google every three days and closes it then. A host-variant or email-obfuscation finding is probed live: done when the site passes, unchanged (applied false) otherwise. A finding with a queued, drafting or open change is settled through the change (approve_change, reject_change with not_now_days, request_changes). The note and the reason are kept with the site's memory, where the playbooks read them.

ParameterTypeDescription
finding_idstringrequired
status"done" | "dismissed" | "snoozed" | "open"required
notestringWhy, or what was done (≤ 500 chars); kept with the item and the site's memory.
reason"wont_fix" | "false_positive" | "duplicate" | "other"For dismissed: why it should never come back.
snooze_daysintegerFor snoozed: how long it stays out of Needs you (default 30).
verifybooleanFor done: let Matchstick's own evidence decide (stored inspections, a live probe) instead of the bare claim.
dry_runbooleantrue: what would happen (and, with verify, what the evidence says now), changing nothing.
idempotency_keystringrequiredA key you generate (≤ 64 chars); the same key with the same arguments replays the first answer for 24 hours.

acknowledge_check Acknowledge a check that needs a person

writesidempotent scope findings:write

A check ended in needs_you or draft_pr and its next step was done outside Matchstick (an export made, a tracker closed, a note written) or won't be done: mark it so, with a note, and it leaves Needs you. Free. The check's follow-up item is marked done (the same row Draft would have used), the note goes to the site's memory. A check whose follow-up already has a pull request is settled through the change (approve_change, reject_change, request_changes); one already applied from Matchstick, or already settled, answers applied: false.

ParameterTypeDescription
check_idstringrequired
notestringWhat was done, or why nothing will be; kept with the site's memory.
dry_runbooleantrue: the credit estimate and what would happen (including a refusal and why), spending and creating nothing.
idempotency_keystringrequiredA key you generate (≤ 64 chars); the same key with the same arguments replays the first answer for 24 hours.

comment_on_change Comment on a change

writes scope changes:review

A note on a change, mirrored as a comment on the pull request so people see it in GitHub too. Free.

ParameterTypeDescription
change_idstringrequired
bodystringrequired
idempotency_keystring

request_changes Request changes

writesidempotent scope changes:review

Feedback on an open change: Matchstick revises the pull request on the same branch (a revise run, charged in credits). Returns the run to poll with get_run; the change goes back to open when the revision lands.

ParameterTypeDescription
change_idstringrequired
feedbackstringrequired
idempotency_keystring

reject_change Reject a change

writesdestructiveidempotent scope changes:review

Close the pull request without merging. The reason is kept with the site's memory, where the playbooks read it, and Matchstick won't redraft the same item on its own for 60 days. Free.

ParameterTypeDescription
change_idstringrequired
reasonstring
not_now_daysintegerAlso snooze the finding behind it for this many days.
idempotency_keystring

approve_change Approve a change

writesdestructiveidempotent scope changes:approve

Approve a reviewed change. Merges only when the site's policy lets this credential (level 2 for low risk, level 3 for medium) and every gate passes: every before_merging item acknowledged by index, the page not frozen, GitHub mergeable and CI green, and expected_head_sha still the head. Otherwise it opens an approval request for a person and returns approval_pending with the approve_url they decide on. Free; the merge is the person's or the policy's.

ParameterTypeDescription
change_idstringrequired
expected_head_shastringrequiredThe head sha get_change showed you.
acknowledged_caveatsinteger[]Indexes into before_merging you have read and accept.
notestring
idempotency_keystring

confirm_deploy Confirm a merged change is deployed

writesidempotent scope changes:deploy

After a merge, say the site is deployed. Matchstick looks at the live pages first, always: a change visible from outside is live only once the site shows it (outcome live, measuring starts), one nothing shows from outside (code, configuration) is taken on your word (unverified, measuring starts), and a page still serving the old version answers not_yet with exactly what it saw, so the claim isn't recorded yet; deploy and call again. claim: false only looks (re-check now instead of waiting for the daily pass) and claims nothing. evidence (the URLs you fetched and what you found, up to 20) is kept on the change for a person to audit; it never decides the verdict. Free. Level 0 sites refuse.

ParameterTypeDescription
change_idstringrequired
claimbooleanfalse: look at the site now and report, claiming nothing. Default true.
evidenceobjectWhat you observed yourself: URLs and a line on what they answered.
idempotency_keystringrequiredA key you generate (≤ 64 chars); the same key with the same arguments replays the first answer for 24 hours.

request_revert Request a revert

writesidempotent scope runs:write

Open a pull request that undoes a merged change: Matchstick re-applies the inverse of the merged diff on the current base. About 10 credits from the weekly budget. The revert goes through review and approval like any change (approve_change, a person at level 1); nothing changes on the site until it is merged. One live revert per change. Poll get_run. dry_run spends nothing.

ParameterTypeDescription
change_idstringrequired
idempotency_keystringrequiredA key you generate (≤ 64 chars); the same key with the same arguments replays the first answer for 24 hours.
dry_runbooleantrue: the credit estimate and what would happen (including a refusal and why), spending and creating nothing.

The server also serves the resource matchstick://playbooks (the house rules in plain language) and the prompt weekly_seo_review, which walks these tools for one site in the order the weekly loop runs them.

Autonomy and approvals

Each site has an autonomy level, set by the account's owner in Settings → Agents. Whatever the level, a change becomes live only when its pull request is merged and the site is deployed.

LevelAgents mayMerges need
0 · ObserveRead.—
1 · Propose (default)Read, research, check, draft, suggest, review.A person's tap on matchstick.io, every time.
2 · Auto-approve low riskAs 1, and approve_change merges on its own when the change is low risk, of an allowed type (title, description, link, canonical, sitemap by default), every gate passes, the site's daily merge cap isn't reached and it isn't quiet hours.A person for medium and high risk.
3 · Trusted operatorAs 2, and medium risk too, except anything touching a shared layout or component file or more than 5 files.A person for high risk.

Levels 2 and 3 are off by default and lapse when no person has signed in for 30 days. Call get_agent_policy before acting on a site: it says what the credential may do there, the budget left and who the human approvers are.

Approvals

When approve_change needs a person, it returns approval_pending with an approve_url. Give the person the link and stop. They see the change with who asked, approve (which merges as them, with the request recorded on the pull request), deny with a note, or ask for changes. A request is bound to the head you reviewed and stands for 72 hours; if the branch moves it goes stale. A client that declares URL elicitation gets the same round as an input_required result and sees the outcome on its retry.

Gates and budgets

An agent is held by the same gates as Matchstick's own daily triage: the owner's not-now and never, stale Search Console data, protected pages, a page under evaluation from an earlier change (page_frozen, with the date), one live draft per finding, the open-pull-request cap, the agents' drafts per day, the plan's credits and the connection's weekly credit budget (set at consent; the week starts Monday 00:00 UTC). A person in the app is never gated.

Updates and webhooks: noticing new work

Matchstick's tools answer questions; these two paths tell a client that something happened. Both read the same feed, so a poller and a webhook never disagree about what counts: a weekly report finished, a finding became open or open again, a change became ready for review. Daily crawl noise and findings Matchstick handles on its own don't count.

Polling: get_updates_since

Free. Pass since as an ISO 8601 moment or the next_cursor of your previous call (default: the last 24 hours), and a site or none for every site the credential sees. The answer carries reports, findings and changes oldest first, each site's last_report and open_findings by doer (you, matchstick, matchstick_with_approval), server_time and next_cursor. Nothing happened: changed: false and empty lists. The window ends a few seconds behind the clock so a row still committing is never skipped; more: true means the window was cut at the last item and the next call continues from the cursor.

GET https://matchstick.io/api/v1/updates?site=example.com&since=2026-10-25T13:00:00Z

Every site-scoped read also carries last_report_at, last_report_id, last_report_url and open_findings_count (list_sites, list_findings, get_report, the REST mirrors), so a client can tell "a report just finished" from "the same stale items" without a second call.

Webhooks

A person adds a URL on Settings → Webhooks and picks the events. Matchstick POSTs one JSON message per event within about half a minute, oldest first, and signs each one.

EventFires whendata
report.finishedA weekly report finished.report (id, finished_at, overall_status, headline, summary, findings_opened, url), open_findings (total, by_doer), open_changes.
finding.openedA finding whose doer is you or matchstick_with_approval became open or open again and is still open.finding (id, title, why, kind, doer, confidence, urls, status, reopened, report_id, url).
change.ready_for_reviewA pull request Matchstick drafted, revised or rebased is open for review.change (id, title, status, number, risk, url, pr_url).

Headers: X-Matchstick-Event, X-Matchstick-Delivery (the event id, repeated on a retry so a receiver can drop a duplicate), X-Matchstick-Signature: t=<unix seconds>,v1=<hex> where v1 is the HMAC-SHA256 of <t>.<raw body> with the webhook's secret (shown once when it is made; reject a t older than 5 minutes). Answer 2xx within 10 seconds. A failure is retried with a growing wait (1, 2, 4 … 60 minutes) and later events wait behind it, in order; after 20 failures in a row the webhook pauses until a person resumes it. A test delivery (type: "ping", no site) is available from the same page.

{
  "id": "evt_3f9c2a7d1e8b4c6a9d0f1e2b",
  "type": "finding.opened",
  "at": "2026-10-26T13:04:11.512Z",
  "site": {
    "id": "<site id>",
    "domain": "example.com",
    "url": "https://matchstick.io/sites/<site id>"
  },
  "data": {
    "finding": {
      "id": "<finding id>",
      "title": "Request indexing for /about",
      "why": "Google hasn't indexed a page that ranks for its name.",
      "kind": "indexing",
      "doer": "you",
      "confidence": "high",
      "urls": [
        "/about"
      ],
      "status": "open",
      "reopened": false,
      "report_id": "<report id>",
      "url": "https://matchstick.io/reports/<report id>"
    }
  }
}

Limits and errors

  • 120 read calls a minute and 20 write calls an hour per key or grant; rate_limited with retry_after_s beyond that.
  • Request bodies up to 1 MB on /mcp and /api/v1; 64 KB on the OAuth endpoints.
  • search_performance returns at most 1,000 rows over at most 16 months.
  • Every write takes an idempotency key (≤ 64 characters, kept 24 hours): the same key with the same arguments replays the first answer; different arguments are idempotency_conflict.
  • Access tokens live an hour, refresh tokens thirty days (rotated on use); API keys until revoked.

Errors

Every failure is the same object, as JSON on REST and as the text of an isError result over MCP:

{ "error": { "code": "page_frozen", "message": "…", "retryable": false, "details": { "until": "2026-10-29", "change_id": "…" }, "hint": "…" }, "request_id": "…" }
CodeHTTPMeaning
unauthenticated401No, bad, revoked or expired key or token. The WWW-Authenticate header says where the authorization server is.
not_found404No such object, or one outside the sites this credential may see.
invalid_argument400A parameter is missing or malformed; the message says which.
forbidden_scope403The credential lacks the scope; details.required names it.
policy_denied403The site's autonomy level, or the owner's not-now / never, doesn't allow this.
gate_failed409A quality gate failed: caveats not acknowledged, no review, CI, mergeable, a check that already ran.
page_frozen409The page is under evaluation from an earlier change; details.until says until when.
conflict_stale_head409The pull request moved since you read it; read the diff again and pass the new head.
idempotency_conflict409The same Idempotency-Key was used with different arguments.
insufficient_credits402The plan's credits are spent; details.topup_url is where a person adds more.
budget_exhausted429This connection's weekly budget, its drafts for today, or the site's merges for today are used up.
plan_limit402The plan doesn't allow it (pages, sites).
rate_limited429Too many calls; retry after details.retry_after_s.
upstream_unavailable503GitHub, Search Console or the worker didn't answer; retry later.
connection_broken424A connection the call needs (the repository, Search Console) isn't there or lost access.
not_a_repo_change409The fix is a dashboard setting (Cloudflare), DNS or hosting, not a change in the repository: a person applies it; details name the exact setting and the matchstick.io page with the Apply button.

Quote request_id when you write to us: it is on the activity page too.

Privacy: what a connected client can see

A connected client acts as the person who connected it, on the sites they chose at consent (or that a key was minted for), and sees what that person sees of those sites: Search Console clicks, impressions and positions by page and by query; the crawl's facts about each page; findings and the evidence behind them; changes, their diffs and reviews; weekly reports; and the account's activity trail. It never sees credentials, tokens, other accounts, billing details or the person's sign-in.

Every call is a row on the site's Activity page with the client's name, so the people on the account can see what an agent did. There is no public page of this: nothing about a site is published anywhere. A person revokes a client in Settings → Connected agents (OAuth) or Settings → API keys; revocation ends access immediately.

The data a client receives is subject to its own operator's terms. Matchstick's handling of your data is in the privacy policy; the connection and token design is in security.

Changelog

The MCP server announces version 1.5.0.

1.5.0 · 2026-10-27

  • list_needs_you / GET /needs-you: everything the web app's Needs review queue lists, in one free call: changes to review, merged changes not seen live yet (with the last look), checks that ended in needs_you or draft_pr and weren't acted on, findings only a person can do, blocked sites; counts per site and the tools that settle each item. The web queue and the sidebar badge read the same function.
  • confirm_deploy / POST /changes/{id}/confirm-deploy (scope changes:deploy): after a merge, say the site is deployed; Matchstick looks at the live pages first and records live, or unverified on the caller's word when nothing is visible from outside, and not_yet with what it saw when the old version is still served. claim: false only looks. evidence (URLs, a note) is kept on the change for a person to audit. get_change lists confirm_deployed among a merged change's allowed_actions.
  • The deploy checker reads a change whose point is an error status ("returns 404", "410 Gone", a removed file) as live once the page answers 4xx, instead of "not deployed yet" forever; the owner's Deployed button benefits too.
  • acknowledge_check / POST /checks/{id}/acknowledge: a check that ended in needs_you or draft_pr whose next step was done outside Matchstick is marked done with a note and leaves Needs you; the web app's Done on a check runs the same call.

1.4.0 · 2026-10-26

  • get_updates_since / GET /updates: what changed since a moment, in one free call: the weekly reports that finished, the findings that became open or open again (kind, doer, confidence, pages, status) and the changes ready for review, oldest first, with every site's last report and open finding counts by doer, server_time and a next_cursor to continue from. Nothing happened → changed: false and empty lists, so a daily routine can poll and stay quiet.
  • Every site-scoped read carries last_report_at, last_report_id, last_report_url and open_findings_count (list_sites per site, list_findings, get_report and the REST mirrors), so a client can tell a report that just finished from the same stale open items.
  • Webhooks (Settings → Webhooks): a URL receives report.finished, finding.opened (findings whose doer is you or matchstick_with_approval) and change.ready_for_review as signed JSON POSTs with deep links, within about half a minute, oldest first, retried with a growing wait and paused after 20 failures in a row; a test delivery from the same page. Docs: /docs/agents/sync.

1.3.0 · 2026-10-26

  • update_finding_status / POST /findings/{id}/status: mark a finding done (with a note), dismissed (reason: won't fix, false positive, duplicate), snoozed (for some days) or open again, so work done outside Matchstick (Search Console, Cloudflare, a one-off edit) stops showing up in Needs you and the digest. The note and the reason go to the site's memory; the daily triage reads dismissed as never and snoozed as not now. A finding with a queued, drafting or open change is settled through the change instead.
  • verify: true on done lets the evidence decide instead of the claim: an indexing finding is checked against Google's latest stored inspection of its pages (done when every page is indexed by an inspection newer than the finding; otherwise it goes to verifying and the daily run keeps asking Google), a host-variant or email-obfuscation finding is probed live.
  • The auto-close loop: an open or verifying indexing finding closes itself when inspect_pages, the daily run or the weekly pass stores an inspection showing every one of its pages indexed; inspect_pages answers the findings it resolved (resolved).
  • The web app's Mark done and Not now forms run through the same service call; Not now now takes a reason for never (won't fix, false positive, duplicate) and can mark the item done with the note.

1.2.0 · 2026-10-21

  • Coverage on every number: get_performance_totals and get_site_overview say how many days of a range are known (a day with no row inside the stored history had zero impressions, not missing data), carry the previous period even when it was zero, and the stored lifetime totals (history.lifetime); the weekly report's facts and the analyst's rules say the same, so "first clicks ever" can't come from a short window.
  • Internal links from the last two weeks of crawls, with coverage against the sitemap: orphan is null (unknown) on a partial crawl, never true; get_page.inlinks and the weekly underlinked detector follow the same rule.
  • A trimmed text block now says how to get the rest: the limit to pass and next_cursor to page with; get_indexing pages at 50 by default and says how many sitemap URLs were ever inspected.
  • Findings are deduplicated across the report, the daily triage and checks (same title, kind and pages → one item); host-variant and email-obfuscation findings are re-checked daily against the live site and resolved when it passes, and investigate_finding settles them on the spot (status done, free).
  • inspect_pages / POST /sites/{site}/inspect: Google's URL Inspection verdicts for up to fifty pages now (free); the daily run inspects new sitemap URLs on its own.
  • A change in a shared file freezes the whole site only when a title, description, heading, copy, link, canonical, redirect, sitemap or robots change lands on every page; alt text, structured data, code or config in a shared component freeze only the pages named.
  • search_performance and get_performance_totals report the anonymized share (what Google counted for the site or page but attributed to no query); get_content_gaps accepts a lower min_impressions and says how many gaps sit below the floor.

1.1.1 · 2026-10-21

  • get_usage and GET /usage scope spent and the ledger to the sites the credential can see (a site-restricted key or grant, a member's teams); the plan's own rows (allowance, top-up, adjustment) stay, the balance and allowance stay the whole plan's, and account.note says so. Ledger rows no longer carry account_id.

1.1.0 · 2026-10-20

  • Every tool result's text now carries the whole answer: the one-line summary, a data-freshness line (data_through, synced_at, crawled_at, also fields on every site-scoped object), then the structured object as compact JSON with every id and row (trimmed only when stated).
  • search_performance: the query filter applies on every dimension (dimension page with a query lists the pages ranking for it), query_match contains, sort by clicks, impressions, position, ctr or opportunity (missed clicks at positions 5–20), min_impressions, total_rows.
  • New read tools: get_performance_totals (site-wide totals with the previous period and the change), get_indexing (Google's verdict per URL with the coverage reason and canonical mismatches), get_cannibalization, get_content_gaps, get_internal_links (inlinks, orphans, depth; one page's sources and targets), get_connection_info (which credential, scopes, site restriction, what each site has connected). REST mirrors under /api/v1.
  • get_site_overview adds impressions, CTR, position and the change (delta), names frozen pages with until and change_id, and says which Search Console property kind and how far back the stored data reaches (data_since, history); get_report adds headline, summary, windows, freshness, property and data_since; get_page adds CTR per query, 20 queries, on-page content (word count, headings) and inlinks.
  • get_usage tells the plan's credits (account) and this connection's weekly budgets per site (connection) apart and explains a negative balance; get_agent_policy gains budget with the week's start and reset.
  • draft_change refuses a fix that is a dashboard setting (Cloudflare), DNS or hosting with not_a_repo_change, naming the exact setting and the matchstick.io page where a person applies it; get_finding adds fix (where the fix lives) and each check's next step with its apply block; get_run shows a check's verdict and next step.

1.0.0 · 2026-10-20

  • OAuth 2.1 for MCP clients: connect claude.ai, ChatGPT, Grok and other clients by URL alone; consent per site with a weekly credit budget.
  • Propose tools: run_research_pass, investigate_finding, draft_change (with guidance), suggest_finding, request_revert; dry runs; per-connection weekly budgets and a per-day draft budget.
  • Human approval flow: approve_change at level 1 returns an approve_url a person opens; level 2 merges low-risk changes itself and returns merge_sha and the verification window.
  • The MCP server at /mcp (2026-07-28 and the 2025 revisions) with 23 tools, the playbooks resource and the weekly_seo_review prompt; personal API keys and the REST mirror at /api/v1.