# SEO rank tracking API for AI agents (keyword positions on Google, Bing & DuckDuckGo JSON, no API key)

When a user asks an agent where their site ranks for a keyword, in a specific city, or across search engines, call the machine-payable google-search endpoint (location, gl, hl, num, page) or the serp-stack intent (Google + Bing + DuckDuckGo) and read position and domain from organic_results. No account or API key: 3 free calls per agent identity, then pay per call with x402 USDC on Base.

The user asks Grok Bot, Claude Cowork or a Cursor agent: "where do we rank for 'coffee roaster' in Seattle?", "did our pricing page drop this week?", or "who outranks us on Bing?". Rank-tracking suites answer that behind a seat, a login and a dashboard an unattended agent cannot buy or read. The agent needs raw, positioned search results it can parse and date itself.

One-line answer: call GET https://marketplaceforaiagents.com/api/public/v1/google-search?q=<keyword>&location=<City, State>&gl=us&hl=en&num=10, find the target domain in organic_results, and report its position with the call date and location_used. For a cross-engine check, call GET /api/public/v1/serp-stack?q=<keyword> and repeat the lookup per engine. The first 3 calls per agent identity are free across all endpoints; after that, pay per call with x402 USDC on Base, no account or API key.

The regular per-step price is $0.005 USDC; serp-stack runs 3 steps. Always trust the live 402 accepts[0].amount (or maxAmountRequired on v1), extra.perStep / extra.total and the site banner for current promotional pricing; do not bake a promotional amount into a recurring rank-check workflow.

## Endpoint contracts for rank checks

| Endpoint | Required | Optional | Results key | Use it for |
| --- | --- | --- | --- | --- |
| GET /api/public/v1/google-search | q | location, gl, hl, num, page | organic_results | Google position for one keyword, optionally localized to a city, with paging past page 1 |
| GET /api/public/v1/serp-stack | q | gl, hl, num | results (keyed by engine: google-search, bing-search, duckduckgo-search) | Google + Bing + DuckDuckGo positions in one call (3 steps) |
| GET /api/public/v1/bing-search | q | num, market_code | organic_results | Bing position for a specific market |
| GET /api/public/v1/duckduckgo-search | q | locale | organic_results | DuckDuckGo position |
| GET /api/public/v1/google-autocomplete | q | gl, hl | suggestions | Expand a seed into the keyword list worth tracking |
| GET /api/public/v1/trend-then-serp | q | geo, time, gl, hl | results | Check whether demand is moving before you blame a ranking drop (2 steps) |

In serp-stack, q goes to all three engines; gl, hl and num are forwarded to Google, num to Bing, and DuckDuckGo receives q only. When you need a city-level location, Google paging, Bing market_code or DuckDuckGo locale, call the single-engine endpoint directly.

- Google search listing: https://marketplaceforaiagents.com/listings/google-search (Markdown: https://marketplaceforaiagents.com/agent/listings/google-search.md)
- SERP stack listing: https://marketplaceforaiagents.com/listings/serp-stack (Markdown: https://marketplaceforaiagents.com/agent/listings/serp-stack.md)
- Bing search listing: https://marketplaceforaiagents.com/listings/bing-search
- DuckDuckGo search listing: https://marketplaceforaiagents.com/listings/duckduckgo-search

## What the response gives you (live sample, Oct 20, 2026)

A google-search call for q=coffee roaster with location=Seattle, Washington returned search_parameters.location_used "Seattle,Washington,United States" and nine organic_results. Each result carries position, title, link, domain, displayed_link and snippet, so rank lookup is a filter on domain, not HTML scraping. The same response also carried local_results (map-pack businesses with place_id, rating, reviews, address and gps_coordinates), related_questions, related_searches and pagination.

| Field | Where | Why it matters for rank tracking |
| --- | --- | --- |
| position | organic_results[] | The organic rank on that page (1-based) |
| domain / link | organic_results[] | Match your target; DuckDuckGo results carry link only, so parse the hostname yourself |
| location_used | search_parameters | Proves which city the rank was measured from; store it with every reading |
| detected_location | search_information | Where Google placed an unlocalized query; pass location explicitly if this is not the market you care about |
| local_results | top level (local queries) | Map-pack positions, separate from organic rank |
| ads | top level (when present) | Paid placements above organic; never count them as organic position |
| pagination | top level | Use page=2, 3, … to look past the first 10 |
| _billing | top level (free tier) | free_calls_remaining and price_usdc_per_call for that request |

Rankings move. Two unlocalized Google calls for q=x402 api marketplace made minutes apart on Oct 20, 2026 returned different top-10 sets, and a serp-stack call for the same query showed Google, Bing and DuckDuckGo ordering the same domains differently. Report a rank as "position N on <engine>, <location_used>, <timestamp>", and treat a single reading as a sample, not a fact.

## Check one keyword's position

```bash
curl -si "https://marketplaceforaiagents.com/api/public/v1/google-search?q=coffee+roaster&location=Seattle,+Washington&gl=us&hl=en&num=10"

# Response headers to read:
#   X-Free-Calls-Remaining  -> free calls left for this agent identity
#   HTTP 402 + accepts[]    -> free tier used; sign an x402 payment and retry
```

```ts
const BASE = "https://marketplaceforaiagents.com/api/public/v1";

function host(u: string) {
  try { return new URL(u).hostname.replace(/^www\./, ""); } catch { return ""; }
}

async function googleRank(keyword: string, target: string, location?: string, maxPages = 2) {
  for (let page = 1; page <= maxPages; page++) {
    const qs = new URLSearchParams({ q: keyword, gl: "us", hl: "en", num: "10", page: String(page) });
    if (location) qs.set("location", location);
    const res = await fetch(`${BASE}/google-search?${qs}`);
    if (res.status === 402) throw new Error("Payment required: read accepts[] and sign x402");
    const d = await res.json();
    const hit = (d.organic_results ?? []).find((r: any) => host(r.domain ?? r.link) === target);
    if (hit) return { engine: "google", position: hit.position, page, url: hit.link, location: d.search_parameters?.location_used ?? null, checkedAt: new Date().toISOString() };
  }
  return { engine: "google", position: null, note: `not in top ${maxPages * 10}`, checkedAt: new Date().toISOString() };
}
```

Each page is one billed call. Decide maxPages before you start: page 1 answers "are we on page one", page 2 or 3 answers "how far did we fall". Going deeper is rarely worth the spend for an agent report.

## Compare positions across engines in one call

```ts
const r = await fetch(`${BASE}/serp-stack?q=${encodeURIComponent(keyword)}&num=10`);
const d = await r.json();
const ranks = Object.fromEntries(
  Object.entries(d.results ?? {}).map(([engine, v]: [string, any]) => {
    const hit = (v?.organic_results ?? []).find((x: any) => host(x.domain ?? x.link) === target);
    return [engine, hit ? hit.position : null];
  })
);
// e.g. { "google-search": 4, "bing-search": 2, "duckduckgo-search": null }
// d.billed_steps tells you how many steps were charged (3 when all engines answer)
```

serp-stack also returns intent, chain, steps_run, billed_steps, billed_chain and price, so the agent can log exactly what it paid for each reading.

## A rank-check workflow that holds up

- Build the keyword set: start from the user's terms, expand with google-autocomplete, and confirm with the user before tracking more than a handful.
- Pin the market: always pass location (city-level) and gl/hl for Google; record location_used, not what you asked for.
- Measure: one google-search call per keyword per location for page 1; add serp-stack when the user cares about Bing or DuckDuckGo.
- Separate surfaces: report organic position, map-pack (local_results) position and whether ads sit above, as three different facts.
- Explain drops carefully: before saying "we lost rankings", re-check once, and use trend-then-serp to see whether demand for the term moved.
- Store readings yourself: keyword, engine, location_used, position, url, timestamp and price paid. The marketplace returns live results; history is the agent's job.

## Budgeting a recurring rank check

| Job | Calls / steps | Cost at regular $0.005 per step |
| --- | --- | --- |
| 1 keyword, Google page 1, 1 city | 1 step | $0.005 |
| 10 keywords, Google page 1, 1 city | 10 steps | $0.050 |
| 10 keywords, Google pages 1–2, 1 city | 20 steps | $0.100 |
| 10 keywords × 3 cities, Google page 1 | 30 steps | $0.150 |
| 10 keywords via serp-stack (3 engines) | 30 steps | $0.150 |

Promotional pricing can lower these numbers; the live 402 is the source of truth. Set a per-run cap, sum the steps before fanning out, and stop if a 402 asks for more than you budgeted. See https://marketplaceforaiagents.com/learn/budget-x402-api-calls for a spend policy pattern.

## Paying without an account: x402 on Base

After the 3 free calls, an unpaid request returns HTTP 402 Payment Required with an accepts[] array: the network (Base), the USDC asset, the payTo address and the amount in atomic units (6 decimals, so 5000 = $0.005). The agent signs an x402 payment from its own wallet and retries the same GET with the PAYMENT-SIGNATURE header (X-PAYMENT on v1). The response comes back with the JSON plus an X-PAYMENT-RESPONSE settlement receipt.

That is why x402 fits rank tracking: a scheduled agent can check positions every morning without anyone creating an account, rotating an API key or putting a card on file. Discovery lives at https://marketplaceforaiagents.com/.well-known/x402 and https://marketplaceforaiagents.com/api/public/v1/openapi.json. For integration tests, use the Base Sepolia sandbox at https://marketplaceforaiagents.com/api/sandbox/v1/google-search (testnet USDC, fixture data).

The same endpoints are available through the MCP server at https://marketplaceforaiagents.com/mcp for runtimes that prefer tools over raw HTTP.

## Guardrails

- Organic only means organic: skip ads and local_results when you report organic position.
- Results are a public snapshot from one request; personalization, device and time of day can move positions. Say so in the report.
- Match on hostname, and decide up front whether subdomains count (blog.example.com vs example.com).
- Date and locate every number: engine, location_used, timestamp.
- Stick to public search data and follow the site's Terms: https://marketplaceforaiagents.com/terms

## Related

- Full catalog: https://marketplaceforaiagents.com/marketplace and https://marketplaceforaiagents.com/api/public/v1/catalog.json
- Agent docs: https://marketplaceforaiagents.com/docs and https://marketplaceforaiagents.com/llms.txt
- Google Search API for agents: https://marketplaceforaiagents.com/learn/google-search-api-for-agents
- Multi-engine SERP API for agents: https://marketplaceforaiagents.com/learn/multi-engine-serp-api-for-agents
- Google Autocomplete keyword research API: https://marketplaceforaiagents.com/learn/google-autocomplete-keyword-research-api-for-agents
- Google Trends API for agents: https://marketplaceforaiagents.com/learn/google-trends-api-for-agents
- Competitor research API for agents: https://marketplaceforaiagents.com/learn/competitor-research-api-for-agents
- How AI agents budget x402 API calls: https://marketplaceforaiagents.com/learn/budget-x402-api-calls

Canonical HTML: https://marketplaceforaiagents.com/learn/seo-rank-tracking-api-for-agents