Docs

Three endpoints. Each one takes a Search Console property you have connected and returns an answer, not a table of rows to parse. There is no report to choose and no schema to fetch first.

FUTIA is not live yet. The endpoints below work and are documented as built; Google OAuth verification is pending, so keys are not being issued to the public yet.

Getting started

  1. Connect your Google account at /account. We ask only for read-only Search Console access.
  2. Pick the properties you want to analyse. You must already have permission on them in Search Console.
  3. Create an API key. It is shown once, at that moment only.
  4. Call an endpoint, or point your agent at the MCP server.

REST

Base URL https://futia.io/api/v1. All endpoints are POST, take JSON, and authenticate with a bearer token.

curl -s https://futia.io/api/v1/page_yield \
  -H "Authorization: Bearer $FUTIA_KEY" \
  -H "Content-Type: application/json" \
  -d '{"site":"sc-domain:example.com"}'

POST /v1/index_coverage

Has Google seen my pages?

Needs no traffic data, so it works from a site's first day. Reads the sitemap (or a list of URLs you pass), asks Google's URL Inspection API about each one in parallel, and reports what Google actually did with them.

Request

{
  "site": "sc-domain:example.com",
  "urls": ["https://example.com/a"]   // optional
}

Response

{
  "site": "sc-domain:example.com",
  "checked": 42,
  "sitemap_total": 128,
  "remaining_today": 1958,
  "indexed": 11,
  "not_indexed": 31,
  "by_state": { "Submitted and indexed": 11, "Crawled - currently not indexed": 24, "URL is unknown to Google": 7 },
  "canonical_mismatch": ["https://example.com/dup"],
  "not_indexed_urls": [{ "url": "https://example.com/a", "state": "URL is unknown to Google" }]
}
  • Google's URL Inspection quota is 2,000 per site per day. We count it for you and stop before Google does.
  • At most 60 URLs per call; run it again for the rest.
  • canonical_mismatch means Google chose a different canonical than you declared. It is a common silent cause of missing pages.

POST /v1/page_yield

Which pages are dead, why, and which do I fix first?

Splits every ranking page into four buckets, then orders a fix list by how many clicks are actually recoverable. The click-through curve is read from your own site, not borrowed from industry averages — borrowed averages inflated the estimate fourfold in our own measurement.

Request

{
  "site": "sc-domain:example.com",
  "days": 90        // 7 to 180, default 90
}

Response

{
  "site": "sc-domain:example.com",
  "period_days": 90,
  "summary": { "ranking_pages": 5442, "impressions": 167894, "clicks": 1301, "ctr": 0.77 },
  "diagnosis": { "no_demand": 3349, "title_not_earning": 1008, "ranking_too_low": 787, "healthy": 298 },
  "main_problem": "no_demand",
  "recoverable_clicks": 263,
  "fix_these_first": [
    { "url": "/example-page/", "impressions": 1382, "clicks": 0, "position": 7.1, "recoverable": 11 }
  ]
}
  • no_demand: fewer than 10 impressions. Nobody is searching for it, or Google is not showing it.
  • title_not_earning: ranks in the top 10 but gets no clicks. Usually the title and description.
  • ranking_too_low: gets impressions but sits below position 10.
  • recoverable_clicks is deliberately conservative. It will read lower than competitor estimates and it will be closer to what you actually get.

POST /v1/query_match

Which queries are one step away, and which were never mine?

Three lists: queries stuck at the top of page two, queries you rank well for but nobody clicks, and queries several of your pages compete for without any of them earning clicks.

Request

{
  "site": "sc-domain:example.com",
  "days": 90
}

Response

{
  "site": "sc-domain:example.com",
  "distinct_queries": 6879,
  "one_step_away": [{ "query": "example query", "impressions": 2778, "clicks": 3, "position": 12.2 }],
  "intent_mismatch": [{ "query": "another query", "impressions": 15566, "clicks": 51, "position": 2.1, "ctr": 0.33 }],
  "competing_pages": [{ "query": "third query", "pages": 3, "impressions": 4168, "clicks": 12, "best_position": 2.1, "ctr": 0.29 }]
}
  • one_step_away is usually the cheapest win on the whole site: moving one position beats writing a new page.
  • intent_mismatch means you rank but the searcher is not your customer. The right move is often to leave it alone.
  • competing_pages is judged by click-through rate relative to position, not by how many pages compete. Brand queries legitimately pull many pages.

POST /v1/competitor_gap

What does my competitor rank for that I do not?

Picks a competitor by how much of their keyword set overlaps with yours, not by raw overlap count — raw count returns giants that rank for everything. Then returns the keywords they earn and you do not, filtered against your own vocabulary and merged across spelling variants.

Request

{
  "site": "sc-domain:example.com",
  "competitor": "rival.com"    // optional; we pick one if you omit it
}

Response

{
  "site": "example.com",
  "competitor": "rival.com",
  "competitor_chosen_by": "overlap_ratio",
  "competitor_keywords_seen": 300,
  "competitor_keywords_total": 149251,
  "competitor_coverage_percent": 0.2,
  "your_keywords_seen": 296,
  "your_keywords_total": 296,
  "reliable_above_volume": 0,
  "gap_relevant": 110,
  "gap_variants_merged": 18,
  "gap_ideas": 92,
  "keywords": [{ "keyword": "pancake recipe", "volume": 823000, "variants": 2 }]
}
  • We read the competitor's highest-volume keywords first, not an arbitrary slice. Their default order is alphabetical, which fills a gap list with junk.
  • reliable_above_volume: if your own list had to be truncated, we only report gaps above this volume. Below it we cannot be sure you do not already rank, so we stay quiet instead of guessing.
  • variants tells you how many spellings of the same idea were merged into one row.
  • Paid endpoint. Counts against your monthly competitor allowance.

POST /v1/keyword_volume

Is this keyword worth it, for this site specifically?

Volume and difficulty, with a verdict attached. Difficulty is judged against the hardest keyword your site already ranks for, not a universal threshold: the same keyword can be reachable for one site and out of reach for another.

Request

{
  "site": "sc-domain:example.com",
  "keywords": ["lasagna recipe", "how to make risotto"]
}

Response

{
  "site": "example.com",
  "carrying_difficulty": 34,
  "verdict": "This site already ranks for keywords up to difficulty 34. Anything at or below that is reachable.",
  "keywords": [{ "keyword": "lasagna recipe", "volume": 40500, "difficulty": 28, "reachable": true }]
}
  • If your site has too little ranking history to judge difficulty against, we say site_too_new_to_judge rather than inventing a verdict. Volume is still real.
  • At most 100 keywords per call.
  • Paid endpoint. Counts against your monthly competitor allowance.

POST /v1/backlink_profile

Who links to me, and does it mean anything?

Summary figures and what they imply. We deliberately do not return the raw link list: a thousand rows of links fills an agent's context without producing a decision.

Request

{
  "site": "sc-domain:example.com"
}

Response

{
  "site": "example.com",
  "referring_domains": 48,
  "backlinks": 643,
  "domain_rank": 236,
  "nofollow_domain_share": 22.9,
  "reading": ["643 links from 48 domains. Domain count matters more than link count: a hundred links from one site is one vote, not a hundred."]
}
  • Domain count is the number that moves rankings; link count mostly moves dashboards.
  • Paid endpoint. Counts against your monthly competitor allowance.

POST /v1/serp_position

Who is above me on this query, and by how much?

The only endpoint that looks outward. Still a reading rather than a raw list: how many competitors, where you sit among them, and who is consistently ahead.

Request

{
  "site": "sc-domain:example.com",
  "keywords": ["lasagna recipe"]
}

Response

{
  "site": "example.com",
  "your_position": null,
  "domains_above": 20,
  "competitors": [{ "domain": "rival.com", "median_position": 2, "keywords_matched": 1 }],
  "reading": ["Your domain does not appear in the results for these queries at all. The gap is not a ranking problem yet; there is nothing to rank."]
}
  • your_position of null is an answer, not an error: you are not in these results at all.
  • At most 10 queries per call.
  • Paid endpoint. Counts against your monthly competitor allowance.

MCP

The MCP server exposes the same three endpoints as tools, under the same names. Your agent does not need to learn a second vocabulary, and a fix on our side reaches both interfaces at once.

Claude Code, Cursor, and other MCP clients

{
  "mcpServers": {
    "futia": {
      "type": "http",
      "url": "https://futia.io/api/mcp",
      "headers": { "Authorization": "Bearer futia_live_..." }
    }
  }
}

Transport is streamable HTTP, JSON-RPC 2.0. Tool discovery (initialize, tools/list) needs no key, so a client can see what is on offer before connecting. Only tools/call is authenticated.

Errors

Every error returns { "error": "<code>" } with the HTTP status below. MCP returns the same codes, so an agent can branch on one vocabulary regardless of which interface it used.

HTTPCodeMeaning
401invalid_keyMissing, malformed, revoked or unknown API key.
403mulk_yetkisi_yokThe key is valid but this property is not one you have connected.
409baglanti_yokNo Google account connected yet. Connect one at /en/account.
401yeniden_baglaGoogle access was revoked. Reconnect at /en/account.
429daily_quota_exhaustedGoogle's per-site URL Inspection quota for today is used up.
429google_kotasiGoogle rate-limited the request. Retry later.
502google_hatasiGoogle returned an error. Usually transient.
402upgrade_requiredThis endpoint buys data we pay for, and the free plan does not include it. The Search Console endpoints stay unmetered.
402monthly_budget_exhaustedYour plan's competitor allowance for this month is used up. It resets on the first of the month.
404no_competitor_foundWe could not find a competitor with meaningful keyword overlap. Pass one explicitly with "competitor".

Limits and honesty

  • Search Console keeps roughly 16 months of history, so neither can we go back further.
  • We read your own site only. FUTIA cannot tell you anything about a competitor's keywords, backlinks or rankings — that is a different kind of product and we do not pretend to be one.
  • Nothing works until you connect Google. There is no estimate mode, because an estimate is what we are trying to replace.
  • Raw data from Google is cached for at most 30 days, then dropped.