# Rankfield API docs Source: https://rankfield.dev/docs Call Rankfield's SEO and web data API: Site Strength, referring domains, search suggestions and live page data, with one API key. ## Make your first request 1. Copy a key from the [API key panel](/?tab=key#playground). No sign-up. 2. Send it as a bearer token: ```bash curl https://rankfield.dev/v1/domains/wikipedia.org/site-strength \ -H "Authorization: Bearer $RANKFIELD_API_KEY" ``` 3. Read the JSON: `{"domain": "wikipedia.org", "siteStrength": 100}`. The [Quickstart](/docs/get-started/quickstart) has the same in Python and JavaScript. ## Endpoints | Endpoint | What you get | Request | |---|---|---| | [Site Strength](/docs/site-strength/get-site-strength) | A domain's 0–100 link authority score | `GET /v1/domains/{domain}/site-strength` | | [Referring Domains](/docs/referring-domains/get-referring-domains) | How many domains link to a site, and the strongest 20 | `GET /v1/domains/{domain}/referring-domains` | | [All Referring Domains](/docs/referring-domains/list-referrers) | Every linking domain, paged or as CSV | `GET /v1/domains/{domain}/referrers` | | [Link Extraction](/docs/link-extraction/extract-links) | Every link on a live page, with anchor text and rel | `GET /v1/html/link-extraction?url=` | | [Scrape](/docs/scrape/scrape-a-page) | A page as Markdown, with metadata, links and structured data | `GET /v1/scrape?url=` | | [Bot View](/docs/bot-view/check-a-page) | Which of 274 crawlers and AI bots may read a page | `GET /v1/bot-view?url=` | | [Brand](/docs/brand/get-a-brand) | A company's logos, colours, fonts and profiles | `GET /v1/brand?domain=` | | [Site Map](/docs/map/map-a-site) | Every URL of a website | `GET /v1/map?domain=` | | [Site Audit](/docs/site-audit/audit-a-website) | Every page of a website checked for technical SEO and AEO problems | `POST /v1/site-audit` | | [Bing Suggest](/docs/suggest/get-bing-suggestions) | Bing autocomplete for a keyword | `GET /v1/bing/suggest?query=` | | [DuckDuckGo Suggest](/docs/suggest/get-duckduckgo-suggestions) | DuckDuckGo autocomplete for a keyword | `GET /v1/ddg/suggest?query=` | | [Yahoo Search Predictions](/docs/suggest/get-yahoo-search-predictions) | Yahoo's predictions for a keyword | `GET /v1/yahoo/search-predictions?query=` | Every endpoint takes the same key. For many URLs, domains or keywords at once, each one also has [batch jobs](/docs/get-started/batch-jobs). ## Before you build - [Authentication](/docs/get-started/authentication): Send the key, get one from code, and keep it safe. - [Errors](/docs/get-started/errors): Status codes, error bodies and which errors to retry. - [Rate limits](/docs/get-started/rate-limits): How much one request or job holds, and what to do about a 429. - [Batch jobs](/docs/get-started/batch-jobs): Start a job, follow it, and read results as they finish. ## For AI agents - [For coding agents](/docs/agents/for-coding-agents): The whole API on one page: auth, which endpoint to pick, timeouts and gotchas. - [MCP server](/docs/agents/mcp-server): Connect Claude, Cursor or any MCP client with your key. - [Agent skill](/docs/agents/agent-skill): One SKILL.md file that teaches an agent to use Rankfield. - [llms.txt and Markdown pages](/docs/agents/llms-txt-and-markdown): Every page as Markdown, indexed in /llms.txt. ::: --- # Get started Source: https://rankfield.dev/docs/get-started Get a Rankfield API key, make your first request, and learn how keys, errors, rate limits and batch jobs work. Every Rankfield endpoint takes the same API key in the same header, answers in JSON, and returns errors with an HTTP status you can act on. Read these pages once; the endpoint pages then only describe what differs. ## Pages - [Quickstart](/docs/get-started/quickstart): Get an API key, make your first Rankfield request with cURL, Python or JavaScript, and read the answer. - [Authentication](/docs/get-started/authentication): Authenticate every Rankfield API request with your API key in the Authorization header, and keep the key safe. - [Errors](/docs/get-started/errors): Read Rankfield API errors: the HTTP status codes, the JSON error body of each endpoint family, and which errors to retry. - [Rate limits](/docs/get-started/rate-limits): See how much one Rankfield request or job can hold, what a busy server answers, and how to avoid 429 errors. - [Batch jobs](/docs/get-started/batch-jobs): Send many URLs, domains or keywords in one Rankfield batch job, follow its progress, and read results as they finish. ::: --- # Quickstart Source: https://rankfield.dev/docs/get-started/quickstart Get an API key, make your first Rankfield request with cURL, Python or JavaScript, and read the answer. ## 1. Get an API key Open the [API key panel](/?tab=key#playground) and click **Copy**. There is no sign-up, no card and no expiry date. Keys start with `ls_`. Keep the key in an environment variable rather than in your code: ```bash export RANKFIELD_API_KEY="ls_..." ``` An AI agent can get its own key with one request: ```bash curl -sS -X POST https://rankfield.dev/v1/keys -H 'Origin: https://rankfield.dev' ``` The answer's `key` field holds the key. ## 2. Make your first request Ask for the Site Strength of a domain. Send the key as a bearer token: ```bash cURL curl https://rankfield.dev/v1/domains/wikipedia.org/site-strength \ -H "Authorization: Bearer $RANKFIELD_API_KEY" ``` ```python Python import os, requests response = requests.get( "https://rankfield.dev/v1/domains/wikipedia.org/site-strength", headers={"Authorization": f"Bearer {os.environ['RANKFIELD_API_KEY']}"}, ) response.raise_for_status() print(response.json()["siteStrength"]) ``` ```javascript JavaScript const response = await fetch("https://rankfield.dev/v1/domains/wikipedia.org/site-strength", { headers: { Authorization: `Bearer ${process.env.RANKFIELD_API_KEY}` }, }); if (!response.ok) throw new Error((await response.json()).error.message); console.log((await response.json()).siteStrength); ``` ## 3. Read the answer ```json 200 { "domain": "wikipedia.org", "siteStrength": 100 } ``` `siteStrength` is a 0–100 score from the Rankfield web graph; [How Site Strength works](/docs/site-strength/how-site-strength-works) explains it. Any other status is an error with a JSON body: see [Errors](/docs/get-started/errors). The API does not send CORS headers. Call it from a server, a script or an agent, not from browser JavaScript on another site. ## Next steps - Pick an endpoint from the [docs overview](/docs). - Many URLs or keywords at once: [Batch jobs](/docs/get-started/batch-jobs). - Connect an AI agent: [MCP server](/docs/agents/mcp-server) or [Agent skill](/docs/agents/agent-skill). --- # Authentication Source: https://rankfield.dev/docs/get-started/authentication Authenticate every Rankfield API request with your API key in the Authorization header, and keep the key safe. ## Send the key Every endpoint under `https://rankfield.dev/v1/` and the [MCP server](/docs/agents/mcp-server) need the key in the `Authorization` header: ```http Authorization: Bearer ls_your_key ``` The same key works for every endpoint. Get one from the [API key panel](/?tab=key#playground). Keys do not expire. ## When the key is missing or wrong The answer is `401` with a `WWW-Authenticate: Bearer` header. Check that the header reads exactly `Bearer ` followed by the key, with no quotes or line break. ```json 401 { "error": { "message": "A valid API key is required" } } ``` ## Get a key from code Agents and scripts can ask for a key without a browser: ```bash curl -sS -X POST https://rankfield.dev/v1/keys -H 'Origin: https://rankfield.dev' ``` The `Origin` header is required. Reuse one key rather than asking for a new one per run. When key issuance is at capacity, the answer is `429`; try again later. ## Keep your key safe - Load it from an environment variable or a secret store. Never commit it, paste it in an issue, or put it in a screenshot or log. - Send it only in the `Authorization` header over HTTPS. Never put it in a URL, where it ends up in logs and browser history. - Keep it on your server. Browser JavaScript and mobile apps would expose it, and the API does not send CORS headers anyway. - An agent should never print the key in its answer. --- # Errors Source: https://rankfield.dev/docs/get-started/errors Read Rankfield API errors: the HTTP status codes, the JSON error body of each endpoint family, and which errors to retry. ## Status codes | Status | Meaning | What to do | |---|---|---| | `200` | Success. | Read the JSON. | | `202` | Accepted: a job started, or a page is still being fetched. | Read the result from `resultsUrl` or `statusUrl`. | | `400` | A parameter is missing or invalid. | Fix the request; the message says what is wrong. | | `401` | No valid API key. | See [Authentication](/docs/get-started/authentication). | | `403` | Scrape, Bot View, Brand and Site Map: the site's robots.txt disallows Rankfield-User, or the site is excluded. | Don't retry. | | `404` | Not found: the job id is unknown or expired. A domain that isn't in our link graph yet is not an error: it answers `200` with `"inGraph": false`. | Don't retry. | | `409` | Conflict: a download of a job that is not finished, or an `Idempotency-Key` reused with a different body. | Wait for the job, or use a new key. | | `410` | Gone: a page job's results expired (they are kept for 24 hours). | Start a new job. | | `413` | Too many URLs in one job. | Split the list. | | `415` | Scrape, Bot View, Brand and Site Map: the page is not HTML or text. | Don't retry. | | `422` | The website answered with an error or timed out, or its address is not public. | Retry only when `error.retryable` is `true`. | | `429` | The server is busy, or (Scrape, Bot View, Brand and Site Map) the shared batch queue is full when you start a job. | Wait for the `Retry-After` header, then retry. | | `500` | Scrape, Bot View, Brand and Site Map: our bug. | Retry. | | `502` | A lookup behind the API failed, or the website could not be reached. | Retry later. | | `503` | The service is busy or paused. | Wait for `Retry-After`, then retry. | | `504` | The website did not answer within `timeout`, or the call waited its turn for that website (`sitePace`). | Retry, or allow a longer `timeout`. | A `404` or `500` from a website you asked us to fetch is not an error from us: the page's own status is in the answer's `status` field. ## Error bodies Every error is JSON. The shape depends on the endpoint family: | Endpoints | Body | |---|---| | Site Strength, Referring Domains, exports, Link Extraction | `{"error": {"message": "..."}}`. A page that failed adds `code` (such as `SOURCE_HTTP_404`) and `retryable`. | | Scrape, Bot View, Brand and Site Map | `{"error": {"code": "...", "message": "...", "details": {...}}}`. `code` is stable, such as `robots_disallowed` or `busy`. | | Suggest (Bing, DuckDuckGo, Yahoo) | `{"error": "..."}`: the message is a string. | To handle all of them in one place, read the message like this: ```javascript const body = await response.json(); const message = typeof body.error === "string" ? body.error : body.error?.message; ``` ## Retries - Retry `429`, `503` and `504` after the number of seconds in `Retry-After` (or 2 seconds when it is missing), and back off if it happens again. - Retry `502` and a `422` with `"retryable": true` a few times at most. - Never retry `400`, `401`, `403`, `404`, `413` or `415` unchanged. - Repeat a job submission with the same `Idempotency-Key` and body: you get the same job back, not a second one. Every answer carries an `x-request-id` header. Quote it when you report a problem. --- # Rate limits Source: https://rankfield.dev/docs/get-started/rate-limits See how much one Rankfield request or job can hold, what a busy server answers, and how to avoid 429 errors. No endpoint has per-key rate limits or quotas for now. Every endpoint answers `429` with `Retry-After` when the server is busy; the table shows how much one job can hold. ## Per endpoint | Endpoint | One request or job | When the server is busy | |---|---|---| | [Site Strength](/docs/site-strength/get-site-strength) | One domain per request. | `429` with `Retry-After` | | [Referring Domains](/docs/referring-domains/get-referring-domains), [All Referring Domains](/docs/referring-domains/list-referrers), [Referrer Profile](/docs/referring-domains/get-referrer-profile) | CSV and JSONL lists over 50,000 rows become export jobs. | `429` with `Retry-After` | | [Export jobs](/docs/referring-domains/start-an-export-job) | One domain's full list per job; one job at a time per key. | `409` while another export runs | | [Link Extraction](/docs/link-extraction/extract-links) | Up to 600 URLs per job; a single-page request is a one-URL job. | `429` with `Retry-After` | | [Suggest](/docs/suggest) (each engine) | Up to 10,000 keywords per job. | `429`, or `503` while an engine cools down, with `Retry-After` | | [Scrape](/docs/scrape/scrape-a-page) | Up to 10,000 URLs per job. | `429` `busy` with `Retry-After` | | [Bot View](/docs/bot-view/check-a-page) | Up to 10,000 URLs per job. | `429` `busy` with `Retry-After` | | [Brand](/docs/brand/get-a-brand) | Up to 10,000 domains per job. | `429` `busy` with `Retry-After` | | [Site Map](/docs/map/map-a-site) | Up to 1,000 domains per job. | `429` `busy` with `Retry-After` | ## When you get a 429 The server is busy. The answer has a `Retry-After` header in seconds: wait that long, then send the request again. On Scrape, Bot View, Brand and Site Map, jobs from all keys share one queue: starting a job while it is full also answers `429` `busy`, with `Retry-After` of 60 seconds. Suggest engines also show what you have used: `GET /v1/bing/suggest/usage` (or `/v1/ddg/suggest/usage`, `/v1/yahoo/search-predictions/usage`). See [Get usage and capacity](/docs/suggest/get-usage-and-capacity). ## Avoid 429 errors - For many URLs or keywords, start a [batch job](/docs/get-started/batch-jobs) instead of sending many single requests. - Reuse answers: page endpoints accept `maxAge`, which returns a recent stored copy instead of fetching the page again. --- # Batch jobs Source: https://rankfield.dev/docs/get-started/batch-jobs Send many URLs, domains or keywords in one Rankfield batch job, follow its progress, and read results as they finish. Use a single request for one URL or keyword. Use a batch job when you have many: one request starts the job, and you read results as each item finishes, without holding a connection open. ## How a job works 1. **Start it.** `POST` the list to the endpoint's `/jobs` path. The answer is `202` with the job's `id`. 2. **Follow it.** `GET /jobs/{id}` reports its status and how many items are done. 3. **Read results.** `GET /jobs/{id}/results` returns finished items in the order they finish, each with its position in your list. Pass the returned cursor back to read the next ones. 4. **Download** (where offered). When the job is complete, `?format=jsonl` or `?format=csv` streams everything at once. Send an `Idempotency-Key` header when you start a job. If your request times out and you send it again with the same key and body, you get the same job back instead of a second one. ## The three job styles Rankfield's endpoint families each keep their own job format: | | Link Extraction | Suggest | Scrape, Bot View, Brand and Site Map | |---|---|---|---| | Body | `{"urls": [...]}`, up to 600 | `{"queries": [...]}`, up to 10,000 | `{"items": [{...}]}`, up to 10,000 (Map: 1,000) | | `Idempotency-Key` | Optional | Required | Required | | Read results | `nextCursor` until it is `null` | `cursor` while `hasMore` is true | `cursor` while `hasMore` is true | | Deadline | `delivery`: `standard` (330 s) or `priority` (90 s) | `delivery`: `standard` (300 s), `priority` (60 s) or `live` (6 s) | None: 16 items run at a time | | Cancel | Not available | `POST /jobs/{id}/cancel` | `POST /jobs/{id}/cancel` | | Kept for | 24 hours | 24 hours | 24 hours after the job finishes | Link Extraction fetches up to 5 pages a second from one website, so about 400 pages of one site fit a `priority` job and about 1,500 a `standard` one. For many pages of one site, use `standard`. Each endpoint's pages show its exact requests and answers, for example [Start a Link Extraction job](/docs/link-extraction/start-a-job). ## Example: read results as they finish ```python Python import os, time, requests BASE = "https://rankfield.dev/v1/html/link-extraction" HEADERS = {"Authorization": f"Bearer {os.environ['RANKFIELD_API_KEY']}"} job = requests.post(f"{BASE}/jobs", headers={**HEADERS, "Idempotency-Key": "crawl-2026-10-25"}, json={"urls": ["https://example.com/", "https://www.iana.org/help/example-domains"]}).json() cursor = 0 while cursor is not None: page = requests.get(f"{BASE}/jobs/{job['id']}/results", headers=HEADERS, params={"cursor": cursor, "wait": 20}).json() for result in page["pages"]: print(result["url"], result["counts"]["links"] if result["ok"] else result["error"]["message"]) cursor = page["nextCursor"] ``` ```javascript JavaScript const BASE = "https://rankfield.dev/v1/html/link-extraction"; const headers = { Authorization: `Bearer ${process.env.RANKFIELD_API_KEY}` }; const job = await (await fetch(`${BASE}/jobs`, { method: "POST", headers: { ...headers, "Content-Type": "application/json", "Idempotency-Key": "crawl-2026-10-25" }, body: JSON.stringify({ urls: ["https://example.com/", "https://www.iana.org/help/example-domains"] }), })).json(); for (let cursor = 0; cursor !== null; ) { const page = await (await fetch(`${BASE}/jobs/${job.id}/results?cursor=${cursor}&wait=20`, { headers })).json(); for (const result of page.pages) console.log(result.url, result.ok ? result.counts.links : result.error.message); cursor = page.nextCursor; } ``` --- # Site Strength Source: https://rankfield.dev/docs/site-strength Get Site Strength, Rankfield's 0–100 authority score for a domain, calculated from the Rankfield web graph. Site Strength rates how strongly the rest of the web links to a domain, on a 0–100 scale. It comes from the domain's ranks in the Rankfield web graph, so you can compare any two domains in the graph the same way. One request returns one score. There are no per-key limits. ## Pages - [Get Site Strength](/docs/site-strength/get-site-strength): Get the Site Strength of a domain: a 0–100 score of how strongly the web links to it, from the Rankfield web graph. - [How Site Strength works](/docs/site-strength/how-site-strength-works): Learn how Rankfield calculates Site Strength from web graph ranks, and how to compare and interpret scores. ::: --- # Get Site Strength Source: https://rankfield.dev/docs/site-strength/get-site-strength Get the Site Strength of a domain: a 0–100 score of how strongly the web links to it, from the Rankfield web graph. `GET https://rankfield.dev/v1/domains/{domain}/site-strength` ```bash cURL curl 'https://rankfield.dev/v1/domains/wikipedia.org/site-strength' \ -H 'Authorization: Bearer YOUR_API_KEY' ``` ```python Python import requests response = requests.get( "https://rankfield.dev/v1/domains/wikipedia.org/site-strength", headers={"Authorization": "Bearer YOUR_API_KEY"}, ) print(response.json()) ``` ```javascript JavaScript const response = await fetch("https://rankfield.dev/v1/domains/wikipedia.org/site-strength", { headers: { "Authorization": "Bearer YOUR_API_KEY", }, }); console.log(await response.json()); ``` ```json 200 { "domain": "wikipedia.org", "siteStrength": 100 } ``` ```json 200 (not in the graph) { "domain": "example-not-in-graph.dev", "inGraph": false, "siteStrength": 0 } ``` ```json 400 { "error": { "message": "Invalid domain" } } ``` ```json 401 { "error": { "message": "A valid API key is required" } } ``` ## Parameters ### Headers | Name | Type | Required | Default | Description | |---|---|---|---|---| | `Authorization` | string | Yes | – | `Bearer` followed by your API key. See [Authentication](/docs/get-started/authentication). | ### Path parameters | Name | Type | Required | Default | Description | |---|---|---|---|---| | `domain` | string | Yes | – | The domain, such as `example.com`. Case is ignored, a leading `www.` is removed, and a full URL is read as its host. URL-encode it in the path. | ## Response | Field | Type | Description | |---|---|---| | `domain` | string | The domain as it was looked up, after normalising. | | `inGraph` | boolean | Only when the domain isn't in our link graph yet: `false`, with zeros. Absent otherwise. | | `siteStrength` | integer | 0–100. Higher means stronger link evidence in the graph. | The score is Rankfield's own. It is not Moz Domain Authority or Ahrefs Domain Rating, and it does not estimate traffic. [How Site Strength works](/docs/site-strength/how-site-strength-works) explains how it is calculated. ## Errors | Status | When | |---|---| | 400 | The domain is not a valid domain name, or a parameter is out of range. | | 401 | No valid API key. | | 429 | Too many requests at once; wait for `Retry-After`. | | 502 | The graph lookup behind the API failed. Retry later. | | 503 | The graph index is unavailable for a moment. Retry after `Retry-After`. | ## Limits No per-key limits. When the server is busy you get `429` with `Retry-After`. ## Related - [Get Referring Domains](/docs/referring-domains/get-referring-domains): how many domains link to this one, with the 20 strongest. - [Get Referrer Profile](/docs/referring-domains/get-referrer-profile): the Site Strength of every referring domain, in ten ranges. ## MCP On the [MCP server](/docs/agents/mcp-server), the tool for this is `get_site_strength`. --- # How Site Strength works Source: https://rankfield.dev/docs/site-strength/how-site-strength-works Learn how Rankfield calculates Site Strength from web graph ranks, and how to compare and interpret scores. ## What the score measures Site Strength combines two standard ranks of each domain in the web graph: PageRank and harmonic centrality. Both rise when many domains link to a domain, and when the domains linking to it are themselves well linked. Rankfield turns the two rank positions into a logarithmic scale and maps them to 0–100 with a calibration checked against held-out data. The mapping only goes up: a better rank never gives a lower score. ## Read it correctly - Compare scores within the same release. Every answer is scored with the release in [Get Dataset Status](/docs/data/get-dataset-status). - Treat a difference of a few points as noise. The score summarises a graph model. - The score is not a probability, a share of the web or a count of backlinks. - It does not measure traffic, rankings, relevance or trust. - It is not interchangeable with Moz Domain Authority, Ahrefs Domain Rating or any other vendor's score: they use other data and methods. ## Which domains have a score Only domains in the current web graph release, about 118 million. Others answer `200` with `"inGraph": false` and a `siteStrength` of `0`: the domain isn't in our link graph yet, so treat it as no data, not a score. The field is absent for every other domain, and a malformed domain gets `400`. A domain can be missing because the crawl data the graph is built from has no page linking to it, not because it has no links. [Coverage and dates](/docs/data/coverage-and-dates) has the details. --- # Referring Domains Source: https://rankfield.dev/docs/referring-domains Count the domains that link to a site, list every one of them with its Site Strength, and export the full list as CSV or JSONL. A referring domain is a distinct domain that links to the domain you ask about, as recorded in the Rankfield web graph. These endpoints give you the count, the full list in either direction, its Site Strength profile, and exports of any size. | You want | Use | |---|---| | The count and the 20 strongest referring domains | [Get Referring Domains](/docs/referring-domains/get-referring-domains) | | Every referring domain, page by page | [List All Referring Domains](/docs/referring-domains/list-referrers) | | Every domain the site links to | [List Outbound Domains](/docs/referring-domains/list-outbound-domains) | | How many referring domains fall in each Site Strength range | [Get Referrer Profile](/docs/referring-domains/get-referrer-profile) | | A list over 50,000 rows as one file | [Start an export job](/docs/referring-domains/start-an-export-job) | The lists hold domains, not pages: source URLs and anchor text are not part of the graph. ## Pages - [Get Referring Domains](/docs/referring-domains/get-referring-domains): Get how many distinct domains link to a domain, and the 20 strongest of them with their Site Strength. - [List All Referring Domains](/docs/referring-domains/list-referrers): List every domain that links to a domain, with its Site Strength, page by page or as one CSV or JSONL file. - [List Outbound Domains](/docs/referring-domains/list-outbound-domains): List every domain a site links to, with its Site Strength, page by page or as one CSV or JSONL file. - [Get Referrer Profile](/docs/referring-domains/get-referrer-profile): Get how a domain's referring domains spread across ten Site Strength ranges, counted over the complete list. - [Start an export job](/docs/referring-domains/start-an-export-job): Export every referring or outbound domain of a site as a CSV or JSONL file, however long the list. - [Get an export job](/docs/referring-domains/get-an-export-job): Check an export job's progress and, once it is complete, its row count, file parts, checksums and download link. ::: --- # Get Referring Domains Source: https://rankfield.dev/docs/referring-domains/get-referring-domains Get how many distinct domains link to a domain, and the 20 strongest of them with their Site Strength. `GET https://rankfield.dev/v1/domains/{domain}/referring-domains` ```bash cURL curl 'https://rankfield.dev/v1/domains/cloro.dev/referring-domains' \ -H 'Authorization: Bearer YOUR_API_KEY' ``` ```python Python import requests response = requests.get( "https://rankfield.dev/v1/domains/cloro.dev/referring-domains", headers={"Authorization": "Bearer YOUR_API_KEY"}, ) print(response.json()) ``` ```javascript JavaScript const response = await fetch("https://rankfield.dev/v1/domains/cloro.dev/referring-domains", { headers: { "Authorization": "Bearer YOUR_API_KEY", }, }); console.log(await response.json()); ``` ```json 200 { "domain": "cloro.dev", "referringDomains": 68, "topReferrers": [ { "domain": "vercel.app", "siteStrength": 89, "observations": 1 }, { "domain": "it.com", "siteStrength": 88, "observations": 1 }, { "domain": "langchain.com", "siteStrength": 75, "observations": 1 }, { "domain": "artdaily.com", "siteStrength": 70, "observations": 1 } ] } ``` ## Parameters ### Headers | Name | Type | Required | Default | Description | |---|---|---|---|---| | `Authorization` | string | Yes | – | `Bearer` followed by your API key. See [Authentication](/docs/get-started/authentication). | ### Path parameters | Name | Type | Required | Default | Description | |---|---|---|---|---| | `domain` | string | Yes | – | The domain, such as `example.com`. Case is ignored, a leading `www.` is removed, and a full URL is read as its host. URL-encode it in the path. | ## Response | Field | Type | Description | |---|---|---| | `domain` | string | The domain as it was looked up. | | `inGraph` | boolean | Only when the domain isn't in our link graph yet: `false`, with zeros. Absent otherwise. | | `referringDomains` | integer \| null | How many distinct domains link to it in the graph. `null` when the count is unavailable: keep it as unknown, not zero. | | `topReferrers` | array | Up to 20 referring domains, strongest first (the example shows 4). | | `topReferrers[].domain` | string | The linking domain. | | `topReferrers[].siteStrength` | integer \| null | Its Site Strength, `null` when it has no score. | | `topReferrers[].observations` | integer | `1` marks one domain-to-domain link in the graph. It is not a count of pages or backlinks. | To get every referring domain, not only 20, use [List All Referring Domains](/docs/referring-domains/list-referrers). ## Errors | Status | When | |---|---| | 400 | The domain is not a valid domain name, or a parameter is out of range. | | 401 | No valid API key. | | 429 | Too many requests at once; wait for `Retry-After`. | | 502 | The graph lookup behind the API failed. Retry later. | | 503 | The graph index is unavailable for a moment. Retry after `Retry-After`. | ## Limits No per-key limits. When the server is busy you get `429` with `Retry-After`. ## MCP On the [MCP server](/docs/agents/mcp-server), the tool for this is `get_referring_domains`. --- # List All Referring Domains Source: https://rankfield.dev/docs/referring-domains/list-referrers List every domain that links to a domain, with its Site Strength, page by page or as one CSV or JSONL file. `GET https://rankfield.dev/v1/domains/{domain}/referrers` ```bash cURL curl 'https://rankfield.dev/v1/domains/wikipedia.org/referrers?limit=3' \ -H 'Authorization: Bearer YOUR_API_KEY' ``` ```python Python import requests response = requests.get( "https://rankfield.dev/v1/domains/wikipedia.org/referrers?limit=3", headers={"Authorization": "Bearer YOUR_API_KEY"}, ) print(response.json()) ``` ```javascript JavaScript const response = await fetch("https://rankfield.dev/v1/domains/wikipedia.org/referrers?limit=3", { headers: { "Authorization": "Bearer YOUR_API_KEY", }, }); console.log(await response.json()); ``` ```json 200 { "domain": "wikipedia.org", "crawlRelease": "2026-may-jun-jul", "buildId": "graph-2026mayjunjul-v3", "asOf": "2026-08-19T00:00:00Z", "source": "Rankfield web graph", "siteStrengthVersion": "authority-continuous-counts-0.3.1", "totalGraphRelationships": 2154598, "publishedSummaryCount": 2154598, "countReconciled": true, "evidenceCoverage": "domain-graph", "sort": "strength_desc", "minSiteStrength": 0, "referrers": [ { "domain": "adobe.com", "siteStrength": 100 }, { "domain": "amazon.com", "siteStrength": 100 }, { "domain": "amazonaws.com", "siteStrength": 100 } ], "nextCursor": "eyJ2IjoxLCJkb21haW4iOiJ3aWtpcGVkaWEub3JnIiwiZGlyZWN0aW9uIjoiaW5ib3VuZCIsInJlbGVhc2UiOiIyMDI2LW1heS1qdW4tanVsIiwiYnVpbGRJZCI6ImdyYXBoLTIwMjZtYXlqdW5qdWwtdjMiLCJhc09mIjoiMjAyNi0wOC0xOVQwMDowMDowMFoiLCJtaW5TaXRlU3RyZW5ndGgiOjAsInNvcnQiOiJzdHJlbmd0aF9kZXNjIiwicG9zaXRpb24iOjN9.488e7b22c11bc97ca2bf07159ab6406d7dbb326ace55b098cb233387413ea504" } ``` ```csv 200 CSV domain,siteStrength,crawlRelease,asOf,evidenceCoverage vercel.app,89,2026-may-jun-jul,2026-08-19T00:00:00Z,domain-graph it.com,88,2026-may-jun-jul,2026-08-19T00:00:00Z,domain-graph langchain.com,75,2026-may-jun-jul,2026-08-19T00:00:00Z,domain-graph ``` ## Parameters ### Headers | Name | Type | Required | Default | Description | |---|---|---|---|---| | `Authorization` | string | Yes | – | `Bearer` followed by your API key. See [Authentication](/docs/get-started/authentication). | ### Path parameters | Name | Type | Required | Default | Description | |---|---|---|---|---| | `domain` | string | Yes | – | The domain, such as `example.com`. Case is ignored, a leading `www.` is removed, and a full URL is read as its host. URL-encode it in the path. | ### Query parameters | Name | Type | Required | Default | Description | |---|---|---|---|---| | `limit` | integer | No | `100` | Rows per page, 1–1,000. | | `cursor` | string | No | – | The `nextCursor` of the previous page. Leave it out for the first page. | | `minSiteStrength` | integer | No | `0` | Only domains with at least this Site Strength, 0–100. | | `sort` | string | No | `strength_desc` | `strength_desc` (strongest first) or `strength_asc`. | | `format` | string | No | `json` | `json`, or `csv` or `jsonl` to download the whole list in one answer (no `cursor`). | ## Response | Field | Type | Description | |---|---|---| | `domain` | string | The domain you asked about. | | `inGraph` | boolean | Only when the domain isn't in our link graph yet: `false`, with an empty list. Absent otherwise. | | `crawlRelease` | string | The web graph release the list comes from, such as `2026-may-jun-jul`. | | `asOf` | string | The release date (UTC). | | `buildId` | string | The index build. A cursor only works with the build it came from. | | `siteStrengthVersion` | string | The Site Strength scoring version. | | `totalGraphRelationships` | integer | How many domains are in the whole list, before `minSiteStrength`. | | `evidenceCoverage` | string | `domain-graph`: domain-to-domain links. Source page URLs and anchor text are not part of this list. | | `sort` | string | The order used. | | `minSiteStrength` | integer | The filter used. | | `referrers` | array | This page's domains, each `{domain, siteStrength}`. `siteStrength` is `null` when the domain has no score. | | `nextCursor` | string \| null | Pass it as `cursor` for the next page. `null` on the last page. | The answer also carries `source`, and `publishedSummaryCount` with `countReconciled`: whether `totalGraphRelationships` matches the count in [Get Referring Domains](/docs/referring-domains/get-referring-domains). ## Page through the list Request the first page, then pass each `nextCursor` back as `cursor` until it is `null`. Keep `minSiteStrength` and `sort` the same on every page: a cursor is tied to them and to the release, and a changed filter answers `400` (start again without `cursor`). ## Download the whole list Add `format=csv` or `format=jsonl` to get every row in one answer, as a file download (`Content-Disposition: attachment`). CSV columns: `domain, siteStrength, crawlRelease, asOf, evidenceCoverage`. When the domain has more than 50,000 referring domains, the answer is `202` with an export job instead: follow it as in [Get an export job](/docs/referring-domains/get-an-export-job). ## Errors | Status | When | |---|---| | 400 | The domain is not a valid domain name, or a parameter is out of range. | | 401 | No valid API key. | | 429 | Too many requests at once; wait for `Retry-After`. | | 502 | The graph lookup behind the API failed. Retry later. | | 503 | The graph index is unavailable for a moment. Retry after `Retry-After`. | ## Limits No per-key limits. When the server is busy you get `429` with `Retry-After`. File downloads over 50,000 rows become [export jobs](/docs/referring-domains/start-an-export-job), one at a time per key. ## MCP On the [MCP server](/docs/agents/mcp-server), the tool for this is `list_referring_domains`. --- # List Outbound Domains Source: https://rankfield.dev/docs/referring-domains/list-outbound-domains List every domain a site links to, with its Site Strength, page by page or as one CSV or JSONL file. `GET https://rankfield.dev/v1/domains/{domain}/outbound-domains` ```bash cURL curl 'https://rankfield.dev/v1/domains/wikipedia.org/outbound-domains?limit=3' \ -H 'Authorization: Bearer YOUR_API_KEY' ``` ```python Python import requests response = requests.get( "https://rankfield.dev/v1/domains/wikipedia.org/outbound-domains?limit=3", headers={"Authorization": "Bearer YOUR_API_KEY"}, ) print(response.json()) ``` ```javascript JavaScript const response = await fetch("https://rankfield.dev/v1/domains/wikipedia.org/outbound-domains?limit=3", { headers: { "Authorization": "Bearer YOUR_API_KEY", }, }); console.log(await response.json()); ``` ```json 200 { "domain": "wikipedia.org", "crawlRelease": "2026-may-jun-jul", "buildId": "graph-2026mayjunjul-v3", "asOf": "2026-08-19T00:00:00Z", "source": "Rankfield web graph", "siteStrengthVersion": "authority-continuous-counts-0.3.1", "totalGraphRelationships": 1470604, "publishedSummaryCount": null, "countReconciled": null, "evidenceCoverage": "domain-graph", "sort": "strength_desc", "minSiteStrength": 0, "destinations": [ { "domain": "adobe.com", "siteStrength": 100 }, { "domain": "amazon.com", "siteStrength": 100 }, { "domain": "amazonaws.com", "siteStrength": 100 } ], "nextCursor": "eyJ2IjoxLCJkb21haW4iOiJ3aWtpcGVkaWEub3JnIiwiZGlyZWN0aW9uIjoib3V0Ym91bmQiLCJyZWxlYXNlIjoiMjAyNi1tYXktanVuLWp1bCIsImJ1aWxkSWQiOiJncmFwaC0yMDI2bWF5anVuanVsLXYzIiwiYXNPZiI6IjIwMjYtMDgtMTlUMDA6MDA6MDBaIiwibWluU2l0ZVN0cmVuZ3RoIjowLCJzb3J0Ijoic3RyZW5ndGhfZGVzYyIsInBvc2l0aW9uIjozfQ.50bb56edb0361191e86228dccfc29b01710a19ce5c7c654d89ce920a2f59927f" } ``` The same list as [List All Referring Domains](/docs/referring-domains/list-referrers), in the other direction: the domains that the site links to. ## Parameters ### Headers | Name | Type | Required | Default | Description | |---|---|---|---|---| | `Authorization` | string | Yes | – | `Bearer` followed by your API key. See [Authentication](/docs/get-started/authentication). | ### Path parameters | Name | Type | Required | Default | Description | |---|---|---|---|---| | `domain` | string | Yes | – | The domain, such as `example.com`. Case is ignored, a leading `www.` is removed, and a full URL is read as its host. URL-encode it in the path. | ### Query parameters | Name | Type | Required | Default | Description | |---|---|---|---|---| | `limit` | integer | No | `100` | Rows per page, 1–1,000. | | `cursor` | string | No | – | The `nextCursor` of the previous page. Leave it out for the first page. | | `minSiteStrength` | integer | No | `0` | Only domains with at least this Site Strength, 0–100. | | `sort` | string | No | `strength_desc` | `strength_desc` (strongest first) or `strength_asc`. | | `format` | string | No | `json` | `json`, or `csv` or `jsonl` to download the whole list in one answer (no `cursor`). | ## Response | Field | Type | Description | |---|---|---| | `domain` | string | The domain you asked about. | | `inGraph` | boolean | Only when the domain isn't in our link graph yet: `false`, with an empty list. Absent otherwise. | | `crawlRelease` | string | The web graph release the list comes from, such as `2026-may-jun-jul`. | | `asOf` | string | The release date (UTC). | | `buildId` | string | The index build. A cursor only works with the build it came from. | | `siteStrengthVersion` | string | The Site Strength scoring version. | | `totalGraphRelationships` | integer | How many domains are in the whole list, before `minSiteStrength`. | | `evidenceCoverage` | string | `domain-graph`: domain-to-domain links. Source page URLs and anchor text are not part of this list. | | `sort` | string | The order used. | | `minSiteStrength` | integer | The filter used. | | `destinations` | array | This page's domains, each `{domain, siteStrength}`. `siteStrength` is `null` when the domain has no score. | | `nextCursor` | string \| null | Pass it as `cursor` for the next page. `null` on the last page. | `publishedSummaryCount` and `countReconciled` are always `null` here: there is no separate outbound count to check against. Paging, CSV and JSONL work as in [List All Referring Domains](/docs/referring-domains/list-referrers). Over 50,000 rows, a file download becomes an export job. ## Errors | Status | When | |---|---| | 400 | The domain is not a valid domain name, or a parameter is out of range. | | 401 | No valid API key. | | 429 | Too many requests at once; wait for `Retry-After`. | | 502 | The graph lookup behind the API failed. Retry later. | | 503 | The graph index is unavailable for a moment. Retry after `Retry-After`. | ## Limits No per-key limits. When the server is busy you get `429` with `Retry-After`. File downloads over 50,000 rows become [export jobs](/docs/referring-domains/start-an-export-job), one at a time per key. ## MCP On the [MCP server](/docs/agents/mcp-server), the tool for this is `list_outbound_domains`. --- # Get Referrer Profile Source: https://rankfield.dev/docs/referring-domains/get-referrer-profile Get how a domain's referring domains spread across ten Site Strength ranges, counted over the complete list. `GET https://rankfield.dev/v1/domains/{domain}/referrer-profile` ```bash cURL curl 'https://rankfield.dev/v1/domains/wikipedia.org/referrer-profile' \ -H 'Authorization: Bearer YOUR_API_KEY' ``` ```python Python import requests response = requests.get( "https://rankfield.dev/v1/domains/wikipedia.org/referrer-profile", headers={"Authorization": "Bearer YOUR_API_KEY"}, ) print(response.json()) ``` ```javascript JavaScript const response = await fetch("https://rankfield.dev/v1/domains/wikipedia.org/referrer-profile", { headers: { "Authorization": "Bearer YOUR_API_KEY", }, }); console.log(await response.json()); ``` ```json 200 { "domain": "wikipedia.org", "crawlRelease": "2026-may-jun-jul", "asOf": "2026-08-19T00:00:00Z", "buildId": "graph-2026mayjunjul-v3", "siteStrengthVersion": "authority-continuous-counts-0.3.1", "evidenceCoverage": "domain-graph", "totalGraphReferringDomains": 2154598, "scoreDistribution": [ { "minSiteStrength": 0, "maxSiteStrength": 9, "referringDomains": 453583, "share": 0.21051862110704642 }, { "minSiteStrength": 10, "maxSiteStrength": 19, "referringDomains": 820486, "share": 0.3808069997280235 }, { "minSiteStrength": 20, "maxSiteStrength": 29, "referringDomains": 528569, "share": 0.24532140102237168 }, { "minSiteStrength": 30, "maxSiteStrength": 39, "referringDomains": 218766, "share": 0.10153448578342689 }, { "minSiteStrength": 40, "maxSiteStrength": 49, "referringDomains": 85750, "share": 0.039798607443244635 }, { "minSiteStrength": 50, "maxSiteStrength": 59, "referringDomains": 33062, "share": 0.015344857834268852 }, { "minSiteStrength": 60, "maxSiteStrength": 69, "referringDomains": 10674, "share": 0.004954056394742778 }, { "minSiteStrength": 70, "maxSiteStrength": 79, "referringDomains": 2830, "share": 0.0013134700765525635 }, { "minSiteStrength": 80, "maxSiteStrength": 89, "referringDomains": 700, "share": 0.0003248865913734256 }, { "minSiteStrength": 90, "maxSiteStrength": 100, "referringDomains": 178, "share": 0.0000826140189492425 } ], "unscoredReferringDomains": 0, "unscoredShare": 0 } ``` ## Parameters ### Headers | Name | Type | Required | Default | Description | |---|---|---|---|---| | `Authorization` | string | Yes | – | `Bearer` followed by your API key. See [Authentication](/docs/get-started/authentication). | ### Path parameters | Name | Type | Required | Default | Description | |---|---|---|---|---| | `domain` | string | Yes | – | The domain, such as `example.com`. Case is ignored, a leading `www.` is removed, and a full URL is read as its host. URL-encode it in the path. | ## Response | Field | Type | Description | |---|---|---| | `domain` | string | The domain you asked about. | | `inGraph` | boolean | Only when the domain isn't in our link graph yet: `false`, with empty ranges. Absent otherwise. | | `crawlRelease`, `asOf`, `buildId`, `siteStrengthVersion` | string | Which graph release and scoring version the profile comes from. | | `totalGraphReferringDomains` | integer | Every referring domain in the graph. | | `scoreDistribution` | array | Ten ranges: `minSiteStrength`, `maxSiteStrength`, the `referringDomains` in that range and their `share` of the total (0–1). | | `unscoredReferringDomains` | integer | Referring domains with no Site Strength. | | `unscoredShare` | number | Their share of the total (0–1). | The profile is a count, not a quality or spam verdict. ## Errors | Status | When | |---|---| | 400 | The domain is not a valid domain name, or a parameter is out of range. | | 401 | No valid API key. | | 429 | Too many requests at once; wait for `Retry-After`. | | 502 | The graph lookup behind the API failed. Retry later. | | 503 | The graph index is unavailable for a moment. Retry after `Retry-After`. | ## Limits No per-key limits. When the server is busy you get `429` with `Retry-After`. ## MCP On the [MCP server](/docs/agents/mcp-server), the tool for this is `get_referrer_profile`. --- # Start an export job Source: https://rankfield.dev/docs/referring-domains/start-an-export-job Export every referring or outbound domain of a site as a CSV or JSONL file, however long the list. `POST https://rankfield.dev/v1/jobs` ```bash cURL curl -X POST 'https://rankfield.dev/v1/jobs' \ -H 'Authorization: Bearer YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{"kind":"referrers-export","domain":"cloro.dev","format":"csv"}' ``` ```python Python import requests response = requests.post( "https://rankfield.dev/v1/jobs", headers={"Authorization": "Bearer YOUR_API_KEY"}, json={"kind": "referrers-export", "domain": "cloro.dev", "format": "csv"}, ) print(response.json()) ``` ```javascript JavaScript const response = await fetch("https://rankfield.dev/v1/jobs", { method: "POST", headers: { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json", }, body: JSON.stringify({"kind":"referrers-export","domain":"cloro.dev","format":"csv"}), }); console.log(await response.json()); ``` ```json 202 { "id": "09aecabd-2230-4552-962a-c7a777724adb", "status": "queued", "kind": "referrers-export", "domain": "cloro.dev", "release": "2026-may-jun-jul", "buildId": "graph-2026mayjunjul-v3", "totalGraphRelationships": 68, "expiresAt": "2026-11-01T14:33:49.828Z", "statusUrl": "/v1/jobs/09aecabd-2230-4552-962a-c7a777724adb" } ``` Use an export job for lists of any size. For up to 50,000 rows, `?format=csv` on [List All Referring Domains](/docs/referring-domains/list-referrers) is quicker. ## Parameters ### Headers | Name | Type | Required | Default | Description | |---|---|---|---|---| | `Authorization` | string | Yes | – | `Bearer` followed by your API key. See [Authentication](/docs/get-started/authentication). | ### Body | Name | Type | Required | Default | Description | |---|---|---|---|---| | `kind` | string | Yes | – | `referrers-export` (domains linking to it) or `outbound-export` (domains it links to). | | `domain` | string | Yes | – | The domain, as for [Get Site Strength](/docs/site-strength/get-site-strength). | | `format` | string | No | `csv` | `csv` or `jsonl`. | | `minSiteStrength` | integer | No | `0` | Only domains with at least this Site Strength, 0–100. | | `sort` | string | No | `strength_desc` | `strength_desc` or `strength_asc`. | ## Response `202` with the job's `id`, `status` (`queued`), `totalGraphRelationships`, `expiresAt` and `statusUrl`. Follow `statusUrl` with [Get an export job](/docs/referring-domains/get-an-export-job). For a domain that isn't in our link graph yet, the job is created already `completed`, with `"inGraph": false` and no rows: the file holds only the CSV header (or nothing, for JSONL). ## Errors | Status | When | |---|---| | 400 | `kind`, `domain`, `format`, `minSiteStrength` or `sort` is invalid. | | 401 | No valid API key. | | 409 | This key already has an export running. Wait for it to finish. | | 503 | The export service or the graph index is unavailable. Retry later. | ## Limits No daily limit; one export job at a time per key. Finished exports are kept for 7 days. ## MCP On the [MCP server](/docs/agents/mcp-server), the tool for this is `start_domain_export`. --- # Get an export job Source: https://rankfield.dev/docs/referring-domains/get-an-export-job Check an export job's progress and, once it is complete, its row count, file parts, checksums and download link. `GET https://rankfield.dev/v1/jobs/{id}` ```bash cURL curl 'https://rankfield.dev/v1/jobs/09aecabd-2230-4552-962a-c7a777724adb' \ -H 'Authorization: Bearer YOUR_API_KEY' ``` ```python Python import requests response = requests.get( "https://rankfield.dev/v1/jobs/09aecabd-2230-4552-962a-c7a777724adb", headers={"Authorization": "Bearer YOUR_API_KEY"}, ) print(response.json()) ``` ```javascript JavaScript const response = await fetch("https://rankfield.dev/v1/jobs/09aecabd-2230-4552-962a-c7a777724adb", { headers: { "Authorization": "Bearer YOUR_API_KEY", }, }); console.log(await response.json()); ``` ```json 200 { "id": "09aecabd-2230-4552-962a-c7a777724adb", "kind": "referrers-export", "status": "completed", "error": null, "domain": "cloro.dev", "crawlRelease": "2026-may-jun-jul", "buildId": "graph-2026mayjunjul-v3", "totalGraphRelationships": 68, "scannedRelationships": 68, "exportedRows": 68, "createdAt": "2026-10-25T14:33:49.828Z", "updatedAt": "2026-10-25T14:33:49.882Z", "expiresAt": "2026-11-01T14:33:49.878Z", "result": { "schemaVersion": 1, "id": "09aecabd-2230-4552-962a-c7a777724adb", "domain": "cloro.dev", "direction": "inbound", "format": "csv", "minSiteStrength": 0, "sort": "strength_desc", "crawlRelease": "2026-may-jun-jul", "buildId": "graph-2026mayjunjul-v3", "asOf": "2026-08-19T00:00:00Z", "evidenceCoverage": "domain-graph", "pageLevelEvidence": "unavailable", "totalGraphRelationships": 68, "rowCount": 68, "byteCount": 5266, "partsDigestSha256": "9f373791615a5f16da748734084934d74808ab4a72fa00a874fe1a885491a147", "parts": [ { "index": 0, "key": "public-exports/09aecabd-2230-4552-962a-c7a777724adb/parts/00000.csv", "rows": 68, "bytes": 5266, "sha256": "88a2e7713dc632a3d69211919e444b2cfc49ee0e8fa74233b1786a1176edfdf2" } ], "completedAt": "2026-10-25T14:33:49.878Z", "expiresAt": "2026-11-01T14:33:49.878Z" }, "downloadUrl": "/v1/jobs/09aecabd-2230-4552-962a-c7a777724adb/download", "partUrlTemplate": "/v1/jobs/09aecabd-2230-4552-962a-c7a777724adb/parts/{index}" } ``` ## Parameters ### Headers | Name | Type | Required | Default | Description | |---|---|---|---|---| | `Authorization` | string | Yes | – | `Bearer` followed by your API key. See [Authentication](/docs/get-started/authentication). | ### Path parameters | Name | Type | Required | Default | Description | |---|---|---|---|---| | `id` | string | Yes | – | The job id from [Start an export job](/docs/referring-domains/start-an-export-job). | ## Response | Field | Type | Description | |---|---|---| | `status` | string | `queued`, `running`, `completed`, `failed` or `cancelled`. | | `inGraph` | boolean | Only when the domain isn't in our link graph yet: `false`, and the export has no rows. Absent otherwise. | | `scannedRelationships` | integer | How much of the list has been read so far, out of `totalGraphRelationships`. | | `exportedRows` | integer | Rows written so far (after `minSiteStrength`). | | `error` | string \| null | Why the job failed. | | `result` | object \| null | Once completed: the file's `rowCount`, `byteCount`, `parts` (each with `rows`, `bytes` and `sha256`) and `partsDigestSha256`. | | `downloadUrl` | string | Once completed: the whole file in one download. | | `partUrlTemplate` | string | Once completed: one part at a time; replace `{index}`. | ## Download the file `GET /v1/jobs/{id}/download` streams every part as one file. For very large exports, download `/v1/jobs/{id}/parts/{index}` one by one and check each part's `sha256`, so a broken connection only costs one part. Both answer `409` until the job is `completed`. ## Cancel or resume - `DELETE /v1/jobs/{id}` cancels a queued or running job (`409` once it has finished). - `POST /v1/jobs/{id}/resume` restarts a `failed` job from where it stopped. ## Errors | Status | When | |---|---| | 401 | No valid API key. | | 404 | No job with this id for this key, or it has expired. | | 409 | Download or cancel of a job in the wrong state. | ## MCP On the [MCP server](/docs/agents/mcp-server), the tool for this is `get_export_job`. --- # Suggest Source: https://rankfield.dev/docs/suggest Get the autocomplete suggestions Bing, DuckDuckGo and Yahoo show for a keyword, one at a time or for up to 10,000 keywords in a job. Each engine has its own path and the same contract: a GET for one keyword, answered at once, and jobs for keyword lists. Any Rankfield API key works, with no sign-up step. | Engine | Base path | |---|---| | Bing | `/v1/bing/suggest` | | DuckDuckGo | `/v1/ddg/suggest` | | Yahoo | `/v1/yahoo/search-predictions` | Job pages use Bing in their examples; replace the base path to use another engine. ## Pages - [Get Bing suggestions](/docs/suggest/get-bing-suggestions): Get the autocomplete suggestions Bing shows for a keyword, in one of ten markets, as a JSON list. - [Get DuckDuckGo suggestions](/docs/suggest/get-duckduckgo-suggestions): Get the autocomplete suggestions DuckDuckGo shows for a keyword, as a JSON list with the time they were collected. - [Get Yahoo Search Predictions](/docs/suggest/get-yahoo-search-predictions): Get the search predictions Yahoo shows for a keyword, as a JSON list with the time they were collected. - [Start a suggest job](/docs/suggest/start-a-job): Collect suggestions for up to 10,000 keywords in one job, with a delivery deadline of 6, 60 or 300 seconds. - [Get a suggest job](/docs/suggest/get-a-job): Check a suggest job's status and how many of its keywords are done, succeeded and failed. - [Get suggest job results](/docs/suggest/get-results): Read a suggest job's keywords as they finish, each with its engine answer, and page through them with a cursor. - [Cancel a suggest job](/docs/suggest/cancel-a-job): Stop a running suggest job. Keywords already finished stay readable; the rest end with status 499. - [Get usage and capacity](/docs/suggest/get-usage-and-capacity): See how many keywords your key has used today on a suggest engine, its jobs and requests by status, and the engine's delivery tiers. ::: --- # Get Bing suggestions Source: https://rankfield.dev/docs/suggest/get-bing-suggestions Get the autocomplete suggestions Bing shows for a keyword, in one of ten markets, as a JSON list. `GET https://rankfield.dev/v1/bing/suggest?query={keyword}` ```bash cURL curl 'https://rankfield.dev/v1/bing/suggest?query=keyword%20research&market=en-US' \ -H 'Authorization: Bearer YOUR_API_KEY' ``` ```python Python import requests response = requests.get( "https://rankfield.dev/v1/bing/suggest?query=keyword%20research&market=en-US", headers={"Authorization": "Bearer YOUR_API_KEY"}, ) print(response.json()) ``` ```javascript JavaScript const response = await fetch("https://rankfield.dev/v1/bing/suggest?query=keyword%20research&market=en-US", { headers: { "Authorization": "Bearer YOUR_API_KEY", }, }); console.log(await response.json()); ``` ```json 200 { "source": "bing-suggest", "query": "keyword research", "market": "en-US", "count": 17, "suggestions": [ "keyword research", "keyword research tool", "keyword research tool free", "keyword research tips", "keyword research report", "keyword research tools seo", "keyword research software", "keyword research free", "keyword research for seo", "keyword research google", "keyword research and analysis", "keyword research seo tool", "keyword researcher", "keyword research tools for aeo in llms", "keyword research planner", "keyword research tool google", "keyword research meaning" ] } ``` ```json 401 { "error": "A valid API key is required" } ``` One keyword per request, answered at once. For keyword lists, [start a job](/docs/suggest/start-a-job). ## Parameters ### Headers | Name | Type | Required | Default | Description | |---|---|---|---|---| | `Authorization` | string | Yes | – | `Bearer` followed by your API key. See [Authentication](/docs/get-started/authentication). | ### Query parameters | Name | Type | Required | Default | Description | |---|---|---|---|---| | `query` | string | Yes | – | The keyword, 1–200 printable characters. URL-encode it. | | `market` | string | No | `en-US` | One of `en-US`, `en-GB`, `en-CA`, `en-AU`, `en-IN`, `en-NG`, `fr-FR`, `de-DE`, `es-ES`, `pt-BR`. | | `freshness` | string | No | `fresh` | `fresh` may reuse an answer stored in the last 5 minutes (30 seconds if the list was empty); otherwise it asks the engine. `allow-stale` may also return an answer up to 35 minutes old, marked with a `Warning: 110 - "Response is stale"` header, and refreshes it in the background. | ## Response | Field | Type | Description | |---|---|---| | `source` | string | `bing-suggest`. | | `query` | string | The keyword as it was sent to the engine. | | `market` | string | The market used. | | `count` | integer | How many entries `suggestions` holds. | | `suggestions` | string[] | The engine's list, in its order. | The count varies by keyword and market. Suggestions are what the engine shows while someone types. They are not search volumes. ## Errors | Status | When | |---|---| | 400 | `query` is missing, empty, over 200 characters, or another parameter is invalid. | | 401 | No valid API key. | | 429 | The engine is busy: its collection budget is full for the moment. Wait for `Retry-After`. | | 502 | Bing answered with an error, or with data that couldn't be read. | | 503 | Bing collection is paused, cooling down, timed out or unavailable. Retry after `Retry-After`. | ## Limits No per-key limits for now. When an engine is busy it answers `429`, or `503` while it cools down, with `Retry-After`. [Get usage and capacity](/docs/suggest/get-usage-and-capacity) shows what your key has used. ## MCP On the [MCP server](/docs/agents/mcp-server), the tool for this is `get_bing_suggestions`. --- # Get DuckDuckGo suggestions Source: https://rankfield.dev/docs/suggest/get-duckduckgo-suggestions Get the autocomplete suggestions DuckDuckGo shows for a keyword, as a JSON list with the time they were collected. `GET https://rankfield.dev/v1/ddg/suggest?query={keyword}` ```bash cURL curl 'https://rankfield.dev/v1/ddg/suggest?query=keyword%20research' \ -H 'Authorization: Bearer YOUR_API_KEY' ``` ```python Python import requests response = requests.get( "https://rankfield.dev/v1/ddg/suggest?query=keyword%20research", headers={"Authorization": "Bearer YOUR_API_KEY"}, ) print(response.json()) ``` ```javascript JavaScript const response = await fetch("https://rankfield.dev/v1/ddg/suggest?query=keyword%20research", { headers: { "Authorization": "Bearer YOUR_API_KEY", }, }); console.log(await response.json()); ``` ```json 200 { "source": "ddg-suggest", "query": "keyword research", "market": "default", "endpoint": "ac-list", "collectedAt": "2026-10-25T14:34:00.361Z", "count": 8, "suggestions": [ "keyword research", "keyword research tool free", "keyword research tips", "keyword research free", "keyword research report", "keyword research tools seo", "keyword researcher tool", "keyword research and analysis" ] } ``` ```json 401 { "error": "A valid API key is required" } ``` One keyword per request, answered at once. For keyword lists, [start a job](/docs/suggest/start-a-job). ## Parameters ### Headers | Name | Type | Required | Default | Description | |---|---|---|---|---| | `Authorization` | string | Yes | – | `Bearer` followed by your API key. See [Authentication](/docs/get-started/authentication). | ### Query parameters | Name | Type | Required | Default | Description | |---|---|---|---|---| | `query` | string | Yes | – | The keyword, 1–200 printable characters. URL-encode it. | | `freshness` | string | No | `fresh` | `fresh` may reuse an answer stored in the last 5 minutes (30 seconds if the list was empty); otherwise it asks the engine. `allow-stale` may also return an answer up to 35 minutes old, marked with a `Warning: 110 - "Response is stale"` header, and refreshes it in the background. | ## Response | Field | Type | Description | |---|---|---| | `source` | string | `ddg-suggest`. | | `query` | string | The keyword as it was sent to the engine. | | `market` | string | `default`. | | `endpoint` | string | Which DuckDuckGo source answered. | | `collectedAt` | string | When the suggestions were collected (UTC). | | `count` | integer | How many entries `suggestions` holds. | | `suggestions` | string[] | The engine's list, in its order. | DuckDuckGo has one market: `market=default`, or no market, is accepted; any other value or locale option answers `400`. Suggestions are what the engine shows while someone types. They are not search volumes. ## Errors | Status | When | |---|---| | 400 | `query` is missing, empty, over 200 characters, or another parameter is invalid. | | 401 | No valid API key. | | 429 | The engine is busy: its collection budget is full for the moment. Wait for `Retry-After`. | | 502 | DuckDuckGo answered with an error, or with data that couldn't be read. | | 503 | DuckDuckGo collection is paused, cooling down, timed out or unavailable. Retry after `Retry-After`. | ## Limits No per-key limits for now. When an engine is busy it answers `429`, or `503` while it cools down, with `Retry-After`. [Get usage and capacity](/docs/suggest/get-usage-and-capacity) shows what your key has used. ## MCP On the [MCP server](/docs/agents/mcp-server), the tool for this is `get_duckduckgo_suggestions`. --- # Get Yahoo Search Predictions Source: https://rankfield.dev/docs/suggest/get-yahoo-search-predictions Get the search predictions Yahoo shows for a keyword, as a JSON list with the time they were collected. `GET https://rankfield.dev/v1/yahoo/search-predictions?query={keyword}` ```bash cURL curl 'https://rankfield.dev/v1/yahoo/search-predictions?query=seo%20tools' \ -H 'Authorization: Bearer YOUR_API_KEY' ``` ```python Python import requests response = requests.get( "https://rankfield.dev/v1/yahoo/search-predictions?query=seo%20tools", headers={"Authorization": "Bearer YOUR_API_KEY"}, ) print(response.json()) ``` ```javascript JavaScript const response = await fetch("https://rankfield.dev/v1/yahoo/search-predictions?query=seo%20tools", { headers: { "Authorization": "Bearer YOUR_API_KEY", }, }); console.log(await response.json()); ``` ```json 200 { "source": "yahoo-search-predictions", "query": "seo tools", "market": "default", "endpoint": "gossip-us-fp", "collectedAt": "2026-10-25T14:34:16.607Z", "count": 10, "predictions": [ "seo tools plagiarism checker", "seo tools free", "seo tools adda", "seo tools meaning", "seo tools for website", "seo tools list", "seo tools plagiarism", "seo tools centre paraphrasing tool", "seo tools group buy", "seo tools bd" ] } ``` ```json 401 { "error": "A valid API key is required" } ``` One keyword per request, answered at once. For keyword lists, [start a job](/docs/suggest/start-a-job). ## Parameters ### Headers | Name | Type | Required | Default | Description | |---|---|---|---|---| | `Authorization` | string | Yes | – | `Bearer` followed by your API key. See [Authentication](/docs/get-started/authentication). | ### Query parameters | Name | Type | Required | Default | Description | |---|---|---|---|---| | `query` | string | Yes | – | The keyword, 1–200 printable characters. URL-encode it. | | `freshness` | string | No | `fresh` | `fresh` may reuse an answer stored in the last 5 minutes (30 seconds if the list was empty); otherwise it asks the engine. `allow-stale` may also return an answer up to 35 minutes old, marked with a `Warning: 110 - "Response is stale"` header, and refreshes it in the background. | ## Response | Field | Type | Description | |---|---|---| | `source` | string | `yahoo-search-predictions`. | | `query` | string | The keyword as it was sent to the engine. | | `market` | string | `default`. | | `endpoint` | string | Which Yahoo source answered. | | `collectedAt` | string | When the predictions were collected (UTC). | | `count` | integer | How many entries `predictions` holds. | | `predictions` | string[] | The engine's list, in its order. | The list is called `predictions`, not `suggestions`. Yahoo returns no predictions for some keywords: `count` is then `0` and the list is empty. `market=default`, or no market, is accepted; any other value or locale option answers `400`. Suggestions are what the engine shows while someone types. They are not search volumes. ## Errors | Status | When | |---|---| | 400 | `query` is missing, empty, over 200 characters, or another parameter is invalid. | | 401 | No valid API key. | | 429 | The engine is busy: its collection budget is full for the moment. Wait for `Retry-After`. | | 502 | Yahoo answered with an error, or with data that couldn't be read. | | 503 | Yahoo collection is paused, cooling down, timed out or unavailable. Retry after `Retry-After`. | ## Limits No per-key limits for now. When an engine is busy it answers `429`, or `503` while it cools down, with `Retry-After`. [Get usage and capacity](/docs/suggest/get-usage-and-capacity) shows what your key has used. ## MCP On the [MCP server](/docs/agents/mcp-server), the tool for this is `get_yahoo_search_predictions`. --- # Start a suggest job Source: https://rankfield.dev/docs/suggest/start-a-job Collect suggestions for up to 10,000 keywords in one job, with a delivery deadline of 6, 60 or 300 seconds. `POST https://rankfield.dev/v1/bing/suggest/jobs` ```bash cURL curl -X POST 'https://rankfield.dev/v1/bing/suggest/jobs' \ -H 'Authorization: Bearer YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -H 'Idempotency-Key: keywords-2026-10-25' \ -d '{"queries":["keyword research","seo tools"],"delivery":"priority"}' ``` ```python Python import requests response = requests.post( "https://rankfield.dev/v1/bing/suggest/jobs", headers={"Authorization": "Bearer YOUR_API_KEY", "Idempotency-Key": "keywords-2026-10-25"}, json={"queries": ["keyword research", "seo tools"], "delivery": "priority"}, ) print(response.json()) ``` ```javascript JavaScript const response = await fetch("https://rankfield.dev/v1/bing/suggest/jobs", { method: "POST", headers: { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json", "Idempotency-Key": "keywords-2026-10-25", }, body: JSON.stringify({"queries":["keyword research","seo tools"],"delivery":"priority"}), }); console.log(await response.json()); ``` ```json 202 { "id": "1e3aff33-2831-4112-b934-cb727ab6c23a", "status": "running", "total": 2, "completed": 0, "succeeded": 0, "failed": 0, "createdAt": 1792938856915, "deadlineAt": 1792938916915, "completedAt": null, "delivery": "priority", "keywordCount": 2, "workUnits": 2 } ``` ```json 400 { "error": "Supply an Idempotency-Key of 1–128 letters, numbers, dots, colons, underscores or hyphens" } ``` The same request works on every engine: POST to `{base path}/jobs`. | Engine | Base path | |---|---| | Bing | `/v1/bing/suggest` | | DuckDuckGo | `/v1/ddg/suggest` | | Yahoo | `/v1/yahoo/search-predictions` | ## Parameters ### Headers | Name | Type | Required | Default | Description | |---|---|---|---|---| | `Authorization` | string | Yes | – | `Bearer` followed by your API key. See [Authentication](/docs/get-started/authentication). | | `Idempotency-Key` | string | Yes | – | 1–128 letters, numbers, dots, colons, underscores or hyphens. Use a new one for each new job. Sending the same key with the same body again returns the same job instead of a new one. | ### Body | Name | Type | Required | Default | Description | |---|---|---|---|---| | `queries` | string[] | Yes | – | 1–10,000 keywords, each 1–200 printable characters. | | `delivery` | string | No | `standard` | When the whole job must be done: `live` (6 seconds), `priority` (60 seconds) or `standard` (300 seconds). | | `market` | string | No | `en-US` | Bing only. One market for every keyword: see [Get Bing suggestions](/docs/suggest/get-bing-suggestions). | | `freshness` | string | No | `fresh` | `fresh` may reuse an answer stored in the last 5 minutes (30 seconds if the list was empty); otherwise it asks the engine. `allow-stale` may also return an answer up to 35 minutes old, marked with a `Warning: 110 - "Response is stale"` header, and refreshes it in the background. | ## Delivery A deadline covers the whole job, retries included. A job that cannot finish in time under current load is refused with `429` and a `Retry-After`, rather than accepted and finished late. Try again later or choose a longer tier. Keywords not finished by the deadline end with status `504`. ## Response `202` with the job, as in [Get a suggest job](/docs/suggest/get-a-job). Sending the same `Idempotency-Key` and body again answers `200` with the same job and an `Idempotency-Replayed: true` header. ## Errors | Status | When | |---|---| | 400 | No `Idempotency-Key`, more than 10,000 keywords, an invalid keyword, an unknown `delivery` or `freshness`, an unsupported `market`, or locale options (`language`, `country`, `client`, `cursor`, and `kl` on DuckDuckGo) on DuckDuckGo and Yahoo. | | 401 | No valid API key. | | 409 | The `Idempotency-Key` was used before with a different body. | | 429 | The deadline can't be met under current load: the engine's capacity or the time left. Wait for `Retry-After`, or choose a longer tier. | ## Limits Up to 10,000 keywords per job. No per-key limits for now. When an engine is busy it answers `429`, or `503` while it cools down, with `Retry-After`. ## MCP On the [MCP server](/docs/agents/mcp-server), the tools for this are `start_bing_suggest_job`, `start_duckduckgo_suggest_job` and `start_yahoo_search_predictions_job`. --- # Get a suggest job Source: https://rankfield.dev/docs/suggest/get-a-job Check a suggest job's status and how many of its keywords are done, succeeded and failed. `GET https://rankfield.dev/v1/bing/suggest/jobs/{id}` ```bash cURL curl 'https://rankfield.dev/v1/bing/suggest/jobs/1e3aff33-2831-4112-b934-cb727ab6c23a' \ -H 'Authorization: Bearer YOUR_API_KEY' ``` ```python Python import requests response = requests.get( "https://rankfield.dev/v1/bing/suggest/jobs/1e3aff33-2831-4112-b934-cb727ab6c23a", headers={"Authorization": "Bearer YOUR_API_KEY"}, ) print(response.json()) ``` ```javascript JavaScript const response = await fetch("https://rankfield.dev/v1/bing/suggest/jobs/1e3aff33-2831-4112-b934-cb727ab6c23a", { headers: { "Authorization": "Bearer YOUR_API_KEY", }, }); console.log(await response.json()); ``` ```json 200 { "id": "1e3aff33-2831-4112-b934-cb727ab6c23a", "status": "completed", "total": 2, "completed": 2, "succeeded": 2, "failed": 0, "createdAt": 1792938856915, "deadlineAt": 1792938916915, "completedAt": 1792938857066, "delivery": "priority", "keywordCount": 2, "workUnits": 2 } ``` ## Parameters ### Headers | Name | Type | Required | Default | Description | |---|---|---|---|---| | `Authorization` | string | Yes | – | `Bearer` followed by your API key. See [Authentication](/docs/get-started/authentication). | ### Path parameters | Name | Type | Required | Default | Description | |---|---|---|---|---| | `id` | string | Yes | – | The job id from [Start a suggest job](/docs/suggest/start-a-job). | ## Response | Field | Type | Description | |---|---|---| | `id` | string | The job id. | | `status` | string | `running`, `completed` or `cancelled`. | | `total` | integer | Keywords in the job. | | `completed`, `succeeded`, `failed` | integer | Keywords finished, and how they ended. | | `createdAt`, `deadlineAt`, `completedAt` | integer \| null | Milliseconds since 1970 (UTC). `completedAt` is `null` while running. | | `delivery` | string | The deadline tier. | | `keywordCount`, `workUnits` | integer | What the job counts against your daily keywords. | You don't need to wait for `completed`: [Get suggest job results](/docs/suggest/get-results) returns keywords as they finish. ## Errors | Status | When | |---|---| | 401 | No valid API key. | | 404 | No job with this id for this key. | --- # Get suggest job results Source: https://rankfield.dev/docs/suggest/get-results Read a suggest job's keywords as they finish, each with its engine answer, and page through them with a cursor. `GET https://rankfield.dev/v1/bing/suggest/jobs/{id}/results` ```bash cURL curl 'https://rankfield.dev/v1/bing/suggest/jobs/1e3aff33-2831-4112-b934-cb727ab6c23a/results?cursor=0' \ -H 'Authorization: Bearer YOUR_API_KEY' ``` ```python Python import requests response = requests.get( "https://rankfield.dev/v1/bing/suggest/jobs/1e3aff33-2831-4112-b934-cb727ab6c23a/results?cursor=0", headers={"Authorization": "Bearer YOUR_API_KEY"}, ) print(response.json()) ``` ```javascript JavaScript const response = await fetch("https://rankfield.dev/v1/bing/suggest/jobs/1e3aff33-2831-4112-b934-cb727ab6c23a/results?cursor=0", { headers: { "Authorization": "Bearer YOUR_API_KEY", }, }); console.log(await response.json()); ``` ```json 200 { "id": "1e3aff33-2831-4112-b934-cb727ab6c23a", "status": "completed", "total": 2, "completed": 2, "succeeded": 2, "failed": 0, "createdAt": 1792938856915, "deadlineAt": 1792938916915, "completedAt": 1792938857066, "delivery": "priority", "keywordCount": 2, "workUnits": 2, "cursor": 2, "hasMore": false, "items": [ { "index": 0, "query": "keyword research", "market": "en-US", "status": 200, "result": { "source": "bing-suggest", "query": "keyword research", "market": "en-US", "count": 17, "suggestions": ["keyword research", "keyword research tool", "keyword research tool free"] }, "attempts": 1, "cache": "hit", "ageSeconds": 17, "durationMs": 0 } ] } ``` ## Parameters ### Headers | Name | Type | Required | Default | Description | |---|---|---|---|---| | `Authorization` | string | Yes | – | `Bearer` followed by your API key. See [Authentication](/docs/get-started/authentication). | ### Path parameters | Name | Type | Required | Default | Description | |---|---|---|---|---| | `id` | string | Yes | – | The job id from [Start a suggest job](/docs/suggest/start-a-job). | ### Query parameters | Name | Type | Required | Default | Description | |---|---|---|---|---| | `cursor` | integer | No | `0` | Where to continue: the `cursor` of the previous answer. | | `limit` | integer | No | `100` | Items per answer, 1–500. | ## Read results Start with `cursor=0`. Read `items`, then ask again with the returned `cursor` until `status` is no longer `running` and `hasMore` is `false`. Items arrive in the order they finish; `index` is the keyword's position in your `queries`, from 0. | Field | Type | Description | |---|---|---| | `items[].index` | integer | Position in `queries`. | | `items[].query`, `market` | string | The keyword and market. | | `items[].status` | integer | `200`, or the error status for this keyword: `499` after a cancel, `504` when it was not finished by the deadline. | | `items[].result` | object | The same answer as the single-keyword GET. | | `items[].attempts` | integer | Tries it took. | | `items[].cache`, `ageSeconds` | string, integer | Whether a stored answer was used, and its age. | | `cursor` | integer | Where to continue. | | `hasMore` | boolean | Whether more finished items are ready to read now. While the job runs and you have caught up, it is `false`. | ## Errors | Status | When | |---|---| | 400 | `cursor` or `limit` is invalid. | | 401 | No valid API key. | | 404 | No job with this id for this key. | ## MCP On the [MCP server](/docs/agents/mcp-server), the tools for this are `get_bing_suggest_results`, `get_duckduckgo_suggest_results` and `get_yahoo_search_predictions_results`. --- # Cancel a suggest job Source: https://rankfield.dev/docs/suggest/cancel-a-job Stop a running suggest job. Keywords already finished stay readable; the rest end with status 499. `POST https://rankfield.dev/v1/bing/suggest/jobs/{id}/cancel` ```bash cURL curl -X POST 'https://rankfield.dev/v1/bing/suggest/jobs/1e3aff33-2831-4112-b934-cb727ab6c23a/cancel' \ -H 'Authorization: Bearer YOUR_API_KEY' ``` ```python Python import requests response = requests.post( "https://rankfield.dev/v1/bing/suggest/jobs/1e3aff33-2831-4112-b934-cb727ab6c23a/cancel", headers={"Authorization": "Bearer YOUR_API_KEY"}, ) print(response.json()) ``` ```javascript JavaScript const response = await fetch("https://rankfield.dev/v1/bing/suggest/jobs/1e3aff33-2831-4112-b934-cb727ab6c23a/cancel", { method: "POST", headers: { "Authorization": "Bearer YOUR_API_KEY", }, }); console.log(await response.json()); ``` ```json 200 { "id": "1e3aff33-2831-4112-b934-cb727ab6c23a", "status": "cancelled", "total": 2, "completed": 2, "succeeded": 1, "failed": 1, "createdAt": 1792938856915, "deadlineAt": 1792938916915, "completedAt": 1792938857066, "delivery": "priority", "keywordCount": 2, "workUnits": 2 } ``` ## Parameters ### Headers | Name | Type | Required | Default | Description | |---|---|---|---|---| | `Authorization` | string | Yes | – | `Bearer` followed by your API key. See [Authentication](/docs/get-started/authentication). | ### Path parameters | Name | Type | Required | Default | Description | |---|---|---|---|---| | `id` | string | Yes | – | The job id from [Start a suggest job](/docs/suggest/start-a-job). | ## Response The job, as in [Get a suggest job](/docs/suggest/get-a-job). A job that has already finished is returned unchanged. ## Errors | Status | When | |---|---| | 401 | No valid API key. | | 404 | No job with this id for this key. | --- # Get usage and capacity Source: https://rankfield.dev/docs/suggest/get-usage-and-capacity See how many keywords your key has used today on a suggest engine, its jobs and requests by status, and the engine's delivery tiers. `GET https://rankfield.dev/v1/bing/suggest/usage` ```bash cURL curl 'https://rankfield.dev/v1/bing/suggest/usage' \ -H 'Authorization: Bearer YOUR_API_KEY' ``` ```python Python import requests response = requests.get( "https://rankfield.dev/v1/bing/suggest/usage", headers={"Authorization": "Bearer YOUR_API_KEY"}, ) print(response.json()) ``` ```javascript JavaScript const response = await fetch("https://rankfield.dev/v1/bing/suggest/usage", { headers: { "Authorization": "Bearer YOUR_API_KEY", }, }); console.log(await response.json()); ``` ```json 200 { "day": "2026-10-26", "admittedKeywords": 10240, "limits": "off", "jobs": [{ "status": "completed", "jobs": 5, "keywords": 10120, "succeeded": 10120 }], "live": [{ "status": 200, "requests": 120 }] } ``` Each engine has its own `/usage` and `/capacity`: `{base path}/usage`, `{base path}/capacity`. ## Usage | Field | Type | Description | |---|---|---| | `day` | string | The UTC day the counts cover. | | `admittedKeywords` | integer | Keywords used today. | | `limits` | string | `off`: no per-key limits for now. | | `jobs` | array | Every job this key has started on this engine, counted by status: `{status, jobs, keywords, succeeded}`. | | `live` | array | Single-keyword requests by status, `{status, requests}`, counted since the key first used this engine (not reset daily). Requests still in flight come first, with `status: null`. | ## Capacity `GET {base path}/capacity` describes the engine: `"limits": "off"` (no per-key limits), and `deliveryTiers` with each tier's target (`live` 6 s, `priority` 60 s, `standard` 300 s). Targets are design values, not a measured guarantee. --- # Link Extraction Source: https://rankfield.dev/docs/link-extraction Extract every link on a live web page, with anchor text and rel values, for one page at a time or up to 600 pages in a job. Fetch a public page live and list its links: the target URL, the anchor text, the `rel` values (nofollow, sponsored, ugc), whether each link is internal or external, and how often it appears. Use [Extract links from a page](/docs/link-extraction/extract-links) for one page, or a job for up to 600 pages at once. ## Pages - [Extract links from a page](/docs/link-extraction/extract-links): Fetch a live web page and list every link on it: URL, anchor text, rel values, internal or external, and how often it appears. - [Start a Link Extraction job](/docs/link-extraction/start-a-job): Run Link Extraction on up to 600 pages in one background job, then read each page's result as soon as it is ready. - [Get a Link Extraction job](/docs/link-extraction/get-a-job): Check a Link Extraction job: its status, how many pages are done, succeeded and failed, and its progress from 0 to 1. - [Get Link Extraction results](/docs/link-extraction/get-results): Read a Link Extraction job's pages as each one finishes, page through them with a cursor, or download the finished job as JSONL or CSV. ::: --- # Extract links from a page Source: https://rankfield.dev/docs/link-extraction/extract-links Fetch a live web page and list every link on it: URL, anchor text, rel values, internal or external, and how often it appears. `GET https://rankfield.dev/v1/html/link-extraction?url={url}` ```bash cURL curl 'https://rankfield.dev/v1/html/link-extraction?url=https://www.iana.org/help/example-domains' \ -H 'Authorization: Bearer YOUR_API_KEY' ``` ```python Python import requests response = requests.get( "https://rankfield.dev/v1/html/link-extraction?url=https://www.iana.org/help/example-domains", headers={"Authorization": "Bearer YOUR_API_KEY"}, ) print(response.json()) ``` ```javascript JavaScript const response = await fetch("https://rankfield.dev/v1/html/link-extraction?url=https://www.iana.org/help/example-domains", { headers: { "Authorization": "Bearer YOUR_API_KEY", }, }); console.log(await response.json()); ``` ```json 200 { "url": "https://www.iana.org/help/example-domains", "finalUrl": "https://www.iana.org/help/example-domains", "status": 200, "title": "Example Domains", "redirects": [], "collectedAt": "2026-10-25T14:34:22.639Z", "cached": false, "counts": { "links": 26, "internal": 22, "external": 4, "occurrences": 31 }, "links": [ { "href": "https://www.iana.org/", "anchor": "Homepage", "internal": true, "rel": [], "nofollow": false, "sponsored": false, "ugc": false, "occurrences": 1 }, { "href": "https://www.iana.org/domains", "anchor": "Domains", "internal": true, "rel": [], "nofollow": false, "sponsored": false, "ugc": false, "occurrences": 2 } ], "view": "unique", "jobId": "01a1737b-85c9-781b-a66f-03241b7c96fd" } ``` ```json 202 { "id": "01a1737c-067c-738f-a990-07518fe64d5e", "status": "running", "url": "https://www.iana.org/help/example-domains", "message": "The page is still being collected; read it from resultsUrl.", "statusUrl": "/v1/html/link-extraction/jobs/01a1737c-067c-738f-a990-07518fe64d5e", "resultsUrl": "/v1/html/link-extraction/jobs/01a1737c-067c-738f-a990-07518fe64d5e/results", "retryAfterMs": 2000 } ``` ```json 401 { "error": { "message": "A valid API key is required" } } ``` The page is fetched live, as plain HTML without running JavaScript, and the answer usually arrives within a few seconds. For many pages, [start a job](/docs/link-extraction/start-a-job). ## Parameters ### Headers | Name | Type | Required | Default | Description | |---|---|---|---|---| | `Authorization` | string | Yes | – | `Bearer` followed by your API key. See [Authentication](/docs/get-started/authentication). | ### Query parameters | Name | Type | Required | Default | Description | |---|---|---|---|---| | `url` | string | Yes | – | A public page. `http://` is upgraded to HTTPS and a URL without a scheme gets `https://`. Credentials, custom ports and private hosts are refused. | | `view` | string | No | `unique` | `unique`: one entry per distinct target, with `occurrences`. `occurrences`: one entry per link, in page order. | | `type` | string | No | `all` | `all`, `internal` (same hostname as the final URL) or `external`. `counts` always cover the whole page. | | `maxAge` | integer | No | `60` | Seconds, 0–3,600. A copy of the page fetched at most this long ago may be reused (`cached: true`). `0` always fetches. | | `wait` | integer | No | `60` | Seconds to wait for the page, 1–85. If it is not ready by then, the answer is `202` with `resultsUrl`. | ## Response | Field | Type | Description | |---|---|---| | `url` | string | The URL you sent, after normalising. | | `finalUrl` | string | The page that answered, after redirects. | | `status` | integer | The website's HTTP status. | | `title` | string \| null | The page title. | | `redirects` | array | Each redirect on the way, `{url, status}`. | | `collectedAt` | string | When the page was fetched (UTC). | | `cached` | boolean | `true` when a copy within `maxAge` was reused. | | `counts` | object | `links` (distinct targets), `internal`, `external` and `occurrences`, for the whole page. | | `links[].href` | string | The absolute target URL. | | `links[].anchor` | string | The link text (or the image's alt text). | | `links[].internal` | boolean | Same hostname as `finalUrl`. | | `links[].rel` | string[] | The `rel` values. | | `links[].nofollow`, `sponsored`, `ugc` | boolean | Whether `rel` holds each value. | | `links[].occurrences` | integer | How many times the target appears (with `view=unique`). | | `view` | string | The view used. | | `jobId` | string | The one-page job behind the request. | ## Common questions ### Why are there no links, or fewer than in my browser? The page is read as served, without JavaScript. Links that a script adds after loading are not in the HTML. [Bot View](/docs/bot-view/check-a-page) tells you whether a page needs JavaScript. ### How is internal decided? By hostname: `www.example.com` and `blog.example.com` are different hosts. Links to other subdomains count as external. ## Errors | Status | When | |---|---| | 202 | The page is not ready within `wait`. Read it from `resultsUrl` (see [Get results](/docs/link-extraction/get-results)). | | 400 | `url` or another parameter is missing or invalid. | | 401 | No valid API key. | | 422 | The website answered with an error, timed out, or the page could not be read. The body has the page's `status` and an `error` with `message`, `code` (such as `SOURCE_HTTP_404`) and `retryable`. | | 429 | The service is busy. Wait for `Retry-After`. | | 503 | The service is busy or paused. Retry after `Retry-After`. | ## Limits No per-key limits. A job holds up to 600 URLs (a single-page request is a one-URL job). When the service is busy you get `429` or `503` with `Retry-After`. ## MCP On the [MCP server](/docs/agents/mcp-server), the tool for this is `extract_page_links`. --- # Start a Link Extraction job Source: https://rankfield.dev/docs/link-extraction/start-a-job Run Link Extraction on up to 600 pages in one background job, then read each page's result as soon as it is ready. `POST https://rankfield.dev/v1/html/link-extraction/jobs` ```bash cURL curl -X POST 'https://rankfield.dev/v1/html/link-extraction/jobs' \ -H 'Authorization: Bearer YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -H 'Idempotency-Key: crawl-2026-10-25' \ -d '{"urls":["https://www.iana.org/help/example-domains","https://example.com/"]}' ``` ```python Python import requests response = requests.post( "https://rankfield.dev/v1/html/link-extraction/jobs", headers={"Authorization": "Bearer YOUR_API_KEY", "Idempotency-Key": "crawl-2026-10-25"}, json={"urls": ["https://www.iana.org/help/example-domains", "https://example.com/"]}, ) print(response.json()) ``` ```javascript JavaScript const response = await fetch("https://rankfield.dev/v1/html/link-extraction/jobs", { method: "POST", headers: { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json", "Idempotency-Key": "crawl-2026-10-25", }, body: JSON.stringify({"urls":["https://www.iana.org/help/example-domains","https://example.com/"]}), }); console.log(await response.json()); ``` ```json 202 { "id": "01a1737c-067c-738f-a990-07518fe64d5e", "status": "queued", "total": 2, "view": "unique", "delivery": "standard", "createdAt": "2026-10-25T14:34:30.396Z", "deadlineAt": "2026-10-25T14:40:00.396Z", "expiresAt": "2026-10-26T14:34:30.396Z", "statusUrl": "/v1/html/link-extraction/jobs/01a1737c-067c-738f-a990-07518fe64d5e", "resultsUrl": "/v1/html/link-extraction/jobs/01a1737c-067c-738f-a990-07518fe64d5e/results" } ``` ```json 413 { "error": { "message": "A job can hold at most 600 URLs" } } ``` ## Parameters ### Headers | Name | Type | Required | Default | Description | |---|---|---|---|---| | `Authorization` | string | Yes | – | `Bearer` followed by your API key. See [Authentication](/docs/get-started/authentication). | | `Idempotency-Key` | string | No | – | Up to 100 letters, digits, `.`, `_` or `-`. Sending the same key with the same body again returns the same job instead of a new one. | ### Body | Name | Type | Required | Default | Description | |---|---|---|---|---| | `urls` | string[] | Yes | – | 1–600 page URLs, with the same rules as `url` on the single-page request. | | `view` | string | No | `unique` | `unique` or `occurrences`. Choose `occurrences` to be able to read either view later. | | `delivery` | string | No | `standard` | `standard`: every page within 330 seconds. `priority`: within 90 seconds. See [Delivery](#delivery). | | `maxAge` | integer | No | `60` | Seconds, 0–3,600. A page fetched at most this long ago may be reused. | ## Delivery The deadline is fixed per job, whatever its size: `priority` 90 seconds, `standard` 330 seconds (`deadlineAt` in the answer). By then every page is answered, as a result or as failed. Pages from one website are fetched at up to 5 a second, so about 400 pages of one site fit a `priority` job and about 1,500 a `standard` one; a job spread over many sites fits easily. For many pages of one site, use `standard`. ## Response `202` with the job: | Field | Type | Description | |---|---|---| | `id` | string | The job id. | | `status` | string | `queued` at first. | | `total` | integer | How many URLs the job holds. | | `delivery` | string | The deadline you chose. | | `deadlineAt` | string | When every page should be done. | | `expiresAt` | string | When the job and its results are deleted: 24 hours after it was created. | | `statusUrl` | string | Follow it with [Get a Link Extraction job](/docs/link-extraction/get-a-job). | | `resultsUrl` | string | Read pages with [Get Link Extraction results](/docs/link-extraction/get-results). | | `replayed` | boolean | Present and `true` when this is an earlier job returned again for the same `Idempotency-Key`. | ## Errors | Status | When | |---|---| | 400 | `urls` is missing or holds an invalid URL, another field is invalid, or the `Idempotency-Key` is malformed. | | 401 | No valid API key. | | 413 | More than 600 URLs. | | 429 | The service is busy. Wait for `Retry-After`. | ## Limits No per-key limits. A job holds up to 600 URLs (a single-page request is a one-URL job). When the service is busy you get `429` or `503` with `Retry-After`. ## MCP On the [MCP server](/docs/agents/mcp-server), the tool for this is `start_link_extraction_job`. --- # Get a Link Extraction job Source: https://rankfield.dev/docs/link-extraction/get-a-job Check a Link Extraction job: its status, how many pages are done, succeeded and failed, and its progress from 0 to 1. `GET https://rankfield.dev/v1/html/link-extraction/jobs/{id}` ```bash cURL curl 'https://rankfield.dev/v1/html/link-extraction/jobs/01a1737c-067c-738f-a990-07518fe64d5e' \ -H 'Authorization: Bearer YOUR_API_KEY' ``` ```python Python import requests response = requests.get( "https://rankfield.dev/v1/html/link-extraction/jobs/01a1737c-067c-738f-a990-07518fe64d5e", headers={"Authorization": "Bearer YOUR_API_KEY"}, ) print(response.json()) ``` ```javascript JavaScript const response = await fetch("https://rankfield.dev/v1/html/link-extraction/jobs/01a1737c-067c-738f-a990-07518fe64d5e", { headers: { "Authorization": "Bearer YOUR_API_KEY", }, }); console.log(await response.json()); ``` ```json 200 { "id": "01a1737c-067c-738f-a990-07518fe64d5e", "status": "completed", "total": 2, "completed": 2, "succeeded": 2, "failed": 0, "progress": 1, "createdAt": "2026-10-25T14:34:30.396Z", "deadlineAt": "2026-10-25T14:40:00.396Z", "expiresAt": "2026-10-26T14:34:30.396Z", "statusUrl": "/v1/html/link-extraction/jobs/01a1737c-067c-738f-a990-07518fe64d5e", "resultsUrl": "/v1/html/link-extraction/jobs/01a1737c-067c-738f-a990-07518fe64d5e/results" } ``` ## Parameters ### Headers | Name | Type | Required | Default | Description | |---|---|---|---|---| | `Authorization` | string | Yes | – | `Bearer` followed by your API key. See [Authentication](/docs/get-started/authentication). | ### Path parameters | Name | Type | Required | Default | Description | |---|---|---|---|---| | `id` | string | Yes | – | The job id from [Start a Link Extraction job](/docs/link-extraction/start-a-job). | ## Response | Field | Type | Description | |---|---|---| | `status` | string | `queued`, `running` or `completed`. | | `total` | integer | URLs in the job. | | `completed` | integer | Pages finished, whether they succeeded or failed. | | `succeeded`, `failed` | integer | How the finished pages ended. | | `progress` | number | `completed / total`, from 0 to 1. | | `createdAt`, `deadlineAt`, `expiresAt` | string | When it started, its delivery deadline, and when it is deleted. | You don't need to wait for `completed`: [Get Link Extraction results](/docs/link-extraction/get-results) returns each page as soon as it finishes. ## Errors | Status | When | |---|---| | 401 | No valid API key. | | 404 | No job with this id for this key. | | 410 | The job has expired: jobs are kept for 24 hours after they are created. | --- # Get Link Extraction results Source: https://rankfield.dev/docs/link-extraction/get-results Read a Link Extraction job's pages as each one finishes, page through them with a cursor, or download the finished job as JSONL or CSV. `GET https://rankfield.dev/v1/html/link-extraction/jobs/{id}/results` ```bash cURL curl 'https://rankfield.dev/v1/html/link-extraction/jobs/01a1737c-067c-738f-a990-07518fe64d5e/results?cursor=0&wait=20' \ -H 'Authorization: Bearer YOUR_API_KEY' ``` ```python Python import requests response = requests.get( "https://rankfield.dev/v1/html/link-extraction/jobs/01a1737c-067c-738f-a990-07518fe64d5e/results?cursor=0&wait=20", headers={"Authorization": "Bearer YOUR_API_KEY"}, ) print(response.json()) ``` ```javascript JavaScript const response = await fetch("https://rankfield.dev/v1/html/link-extraction/jobs/01a1737c-067c-738f-a990-07518fe64d5e/results?cursor=0&wait=20", { headers: { "Authorization": "Bearer YOUR_API_KEY", }, }); console.log(await response.json()); ``` ```json 200 { "id": "01a1737c-067c-738f-a990-07518fe64d5e", "status": "running", "view": "unique", "pages": [ { "position": 0, "url": "https://www.iana.org/help/example-domains", "ok": true, "finalUrl": "https://www.iana.org/help/example-domains", "status": 200, "title": "Example Domains", "redirects": [], "collectedAt": "2026-10-25T14:34:22.639Z", "cached": true, "counts": { "links": 26, "internal": 22, "external": 4, "occurrences": 31 }, "links": [ { "href": "https://www.iana.org/", "anchor": "Homepage", "internal": true, "rel": [], "nofollow": false, "sponsored": false, "ugc": false, "occurrences": 1 } ] } ], "nextCursor": 1 } ``` ## Parameters ### Headers | Name | Type | Required | Default | Description | |---|---|---|---|---| | `Authorization` | string | Yes | – | `Bearer` followed by your API key. See [Authentication](/docs/get-started/authentication). | ### Path parameters | Name | Type | Required | Default | Description | |---|---|---|---|---| | `id` | string | Yes | – | The job id. | ### Query parameters | Name | Type | Required | Default | Description | |---|---|---|---|---| | `cursor` | integer | No | `0` | Where to continue: the `nextCursor` of the previous answer. | | `limit` | integer | No | `10` | Pages per answer, 1–100. | | `wait` | integer | No | `0` | Seconds, 0–25, to hold the request until the next page is ready. | | `view` | string | No | `the job's view` | `unique` or `occurrences`. A job started with `occurrences` can be read either way. | | `type` | string | No | `all` | `all`, `internal` or `external`. | | `format` | string | No | `json` | `json` pages through results. Once the job is `completed`, `jsonl` streams one page per line and `csv` streams one row per link: `page_url, page_status, final_url, href, anchor, internal, rel, nofollow, sponsored, ugc, occurrences, error`. | ## Read pages as they finish 1. Start with `cursor=0`. 2. Each answer's `pages` holds the pages finished since your cursor, in the order they finished. `position` is the page's index in your `urls`, from 0. 3. Pass `nextCursor` back as `cursor`. When `nextCursor` is `null`, every page has been delivered and `status` is `completed`. 4. An empty `pages` list means nothing new yet: ask again after `retryAfterMs`, or add `wait=20` so the request waits for the next page. A page that failed has `"ok": false`, its `status` and an `error` with `message`, `code` and `retryable`, as in the single-page `422`. ## Download a finished job `?format=jsonl` streams one page per line; `?format=csv` streams one row per link: `page_url, page_status, final_url, href, anchor, internal, rel, nofollow, sponsored, ugc, occurrences, error`. Both answer `409` until the job is `completed`. A download always starts at the beginning: sending a `cursor` with it answers `400`. ## Errors | Status | When | |---|---| | 400 | `cursor`, `limit`, `wait` or `format` is invalid, or a download was sent with a `cursor`. | | 401 | No valid API key. | | 404 | No job with this id for this key. | | 409 | `format=jsonl` or `csv` before the job is `completed`. | | 410 | The job has expired: jobs are kept for 24 hours after they are created. | ## MCP On the [MCP server](/docs/agents/mcp-server), the tool for this is `get_link_extraction_results`. --- # Scrape Source: https://rankfield.dev/docs/scrape Turn any public web page into clean Markdown for LLMs, with its metadata, links, images, structured data and AI-use signals. Scrape reads one page as plain HTML, the way AI crawlers see it, and returns it as Markdown (the whole page as a person reads it, or only the article with `scope=main`) together with everything else on the page that a model or an SEO check needs. One call for one page, or a job for up to 10,000. ## Pages - [Scrape a page](/docs/scrape/scrape-a-page): Turn a web page into clean Markdown for LLMs, with its metadata, headings, links, images, structured data, language and AI-use signals. - [Start a Scrape job](/docs/scrape/start-a-job): Scrape up to 10,000 URLs in one background job; each item takes the same parameters as a single call. - [Get a Scrape job](/docs/scrape/get-a-job): Check a Scrape job: its status, how many items are done and failed, and when its results expire. - [Get Scrape job results](/docs/scrape/get-results): Read a Scrape job's items as each one finishes, with its full answer or error, and page through them with a cursor. - [Cancel a Scrape job](/docs/scrape/cancel-a-job): Stop a Scrape job. Items already finished stay readable for 24 hours; items not started are dropped. ::: --- # Scrape a page Source: https://rankfield.dev/docs/scrape/scrape-a-page Turn a web page into clean Markdown for LLMs, with its metadata, headings, links, images, structured data, language and AI-use signals. `GET https://rankfield.dev/v1/scrape?url={url}` ```bash cURL curl 'https://rankfield.dev/v1/scrape?url=https://rankfield.dev/blog/what-is-site-strength' \ -H 'Authorization: Bearer YOUR_API_KEY' ``` ```python Python import requests response = requests.get( "https://rankfield.dev/v1/scrape?url=https://rankfield.dev/blog/what-is-site-strength", headers={"Authorization": "Bearer YOUR_API_KEY"}, ) print(response.json()) ``` ```javascript JavaScript const response = await fetch("https://rankfield.dev/v1/scrape?url=https://rankfield.dev/blog/what-is-site-strength", { headers: { "Authorization": "Bearer YOUR_API_KEY", }, }); console.log(await response.json()); ``` ```json 200 { "url": "https://rankfield.dev/blog/what-is-site-strength", "finalUrl": "https://rankfield.dev/blog/what-is-site-strength", "status": 200, "redirects": [], "contentType": "text/html; charset=utf-8", "bytes": 27716, "truncated": false, "fetchedAt": "2026-10-26T18:52:46Z", "timing": { "dnsMs": 0.0, "connectMs": 0.0, "tlsMs": 0.0, "firstByteMs": 42.9, "totalMs": 43.2 }, "language": { "code": "en", "script": "Latn", "confidence": 1, "source": "content", "declared": { "code": "en", "region": null, "script": null, "htmlLang": "en", "contentLanguage": null, "metaContentLanguage": null, "ogLocale": null }, "detected": { "code": "en", "iso639_3": "eng", "name": "English", "script": "Latn", "confidence": 1, "reliable": true }, "agree": true, "textLetters": 837 }, "metadata": { "title": "What is Site Strength? — Rankfield", "description": "Another SEO score? Here is what ours tells you, the evidence behind it, and how 20 real domains compare.", "canonical": null, "robots": null, "xRobotsTag": null, "hreflang": [], "siteName": null, "author": null, "publishedTime": null, "favicon": null, "ogImage": null, "charset": "utf-8" }, "headings": [ { "level": 1, "text": "What is Site Strength?" }, { "level": 2, "text": "How we build Site Strength" }, { "level": 2, "text": "Okay, enough science. How do the numbers compare?" } ], "content": { "mode": "page", "extraction": "document", "words": 854, "truncated": false, "markdown": "[← All articles](https://rankfield.dev/blog) \nTHE SCORE\n\n# What is Site Strength?\n\nAnother SEO score? Here is what ours tells you, the evidence behind it, and how 20 real domains compare.\n\nSite Strength is our 0–100 estimate of a website's link strength. It looks at how many different websites link to a domain and how much weight those links carry. Use it to decide which competitors or link prosp …" }, "links": [ { "url": "https://rankfield.dev/", "text": "Rankfield", "rel": [], "internal": true, "nofollow": false, "sponsored": false, "ugc": false, "occurrences": 3 }, { "url": "https://rankfield.dev/endpoints/", "text": "Endpoints", "rel": [], "internal": true, "nofollow": false, "sponsored": false, "ugc": false, "occurrences": 1 }, { "url": "https://rankfield.dev/free-seo-tools/site-strength", "text": "Free tools", "rel": [], "internal": true, "nofollow": false, "sponsored": false, "ugc": false, "occurrences": 1 } ], "images": [ { "src": "https://rankfield.dev/images/site-strength-links.webp", "alt": "Illustrated websites sending links toward one domain", "width": 1672, "height": 941, "lazy": false, "srcset": [] } ], "structuredData": { "jsonLd": [], "microdata": [], "openGraph": {}, "twitter": {} }, "signals": { "noai": false, "noimageai": false, "tdmReserved": null, "tdmPolicy": null, "tdmSource": null }, "blocked": null, "cached": false, "ageSeconds": 0 } ``` ```json 401 { "error": { "code": "unauthorized", "message": "A valid API key is required" } } ``` We fetch the page once, as plain HTML: what AI crawlers such as GPTBot and ClaudeBot see. When that HTML is an empty JavaScript app shell, we render the page and read it as a browser shows it; `render=never` keeps the raw HTML only, and `render=always` renders every page. Send the parameters as a query string with `GET`, or as a JSON object (up to 1 MB) with `POST` to the same path. For many URLs, [start a job](/docs/scrape/start-a-job). ## Parameters ### Headers | Name | Type | Required | Default | Description | |---|---|---|---|---| | `Authorization` | string | Yes | – | `Bearer` followed by your API key. See [Authentication](/docs/get-started/authentication). | ### Query parameters | Name | Type | Required | Default | Description | |---|---|---|---|---| | `url` | string | Yes | – | A public `http` or `https` URL. Without a scheme, `https://` is added. | | `scope` | string | No | `page` | How much of the page the Markdown covers. `page`: the whole page in reading order, each section once, collapsed content included, without the site's menus and link lists (navigation, header and footer links, sidebars, category and archive lists, language and currency pickers, share and follow buttons), skip links and cookie banners; contact details such as an e-mail address or a phone number stay. `main`: the main article only, or the page's content when the article found is very short next to the rest of the page. `all`: everything, menus and footers included, each section still once. | | `include` | string | No | `all parts` | Only these parts, comma-separated: `content`, `links`, `images`, `headings`, `metadata`, `structuredData`, `language`, `signals`, `highlights`. Parts left out come back as `null`. `highlights` alone returns just the passages, and needs `query`. | | `query` | string | No | – | Words to look for, 1–300 characters. Adds `highlights`: the page's passages that best match them, best first. Close forms count ("price" finds "prices"), synonyms don't; put a phrase in double quotes to match it as a phrase. The same page and query always give the same passages. | | `maxPassages` | integer | No | `5` | 1–20: the most passages `highlights` holds. Only with `query`: without it, `400`. | | `passageChars` | integer | No | `600` | 200–2,000: about how long each passage is, in characters. Only with `query`: without it, `400`. | | `render` | string | No | `auto` | `auto` runs the page's JavaScript only when its raw HTML is an empty app shell; `never` returns the raw HTML only, as AI crawlers see it; `always` runs it every time. See [Rendered pages](#rendered-pages). | | `screenshot` | string | No | – | `viewport` or `fullPage`: also return a WebP image of the page as a browser shows it, the first screen or the whole page up to 12,000 px tall. | | `viewport` | string | No | `mobile` | `mobile` or `desktop`: the screen size for rendering and screenshots. Mobile is 412×915, like Google's smartphone crawler; desktop is 1366×768. | | `maxAge` | integer | No | `600` | How old a stored answer may be, in seconds, up to 2,592,000 (30 days). `0` fetches fresh. | | `timeout` | integer | No | `20` | How long the call may take, in seconds, 1–60. | ## Response | Field | Type | Description | |---|---|---| | `url` | string | The page you asked for. | | `finalUrl` | string | The page that answered, after redirects. | | `status` | integer | The website's HTTP status. A 404 or 500 from the website is reported here, not as an error. | | `redirects` | array | Each redirect, `{url, status}`. | | `contentType`, `bytes` | string, integer | The page's Content-Type and size. | | `truncated` | boolean | `true` when the page was over 10 MB or its Markdown over 1 MB, and the first part was kept. | | `fetchedAt` | string | When the page was fetched (UTC). | | `timing` | object | Milliseconds for DNS, connecting, TLS, the first byte and the whole fetch. | | `language` | object | `code` (such as `en`; `null` when unknown), `script`, `confidence` (0–1), `source` (`content`: read from the text, `declared`: from the markup, or `none`), `declared` (what the markup says: ``, `Content-Language`, the meta tag, `og:locale`) and `detected` (the reading of the text alone). | | `metadata` | object | Title, description, canonical, meta robots, X-Robots-Tag, hreflang, site name, author, published time, favicon, og:image and charset. | | `headings` | array | The H1–H6 outline in page order, `{level, text}`. | | `content` | object | `markdown`, `words`, `mode` (the scope used: `page`, `main` or `all`), `extraction` (`article` when `main` found an article, `document` for the whole page, `text` for a plain-text or Markdown body passed through, or `pdf` for a PDF's text) and `truncated`. A product page whose text doesn't name the product starts with the product's name, description and price from the page's structured data. | | `highlights` | array \| null | Only with `query`: the passages that best match it, best first, each `{text, heading, offset, score, matched}`: the passage as plain text, the headings above it (such as `Acme widgets › Pricing`, or `null`), where it starts in `content.markdown` (in characters), its score, and the query words found in it. `[]` when nothing matches; `null` when the page is blocked or isn't text. | | `links` | array | Every link once, in page order: `url`, `text`, `rel`, `internal`, `nofollow`, `sponsored`, `ugc`, `occurrences`. | | `images` | array | Every image once: `src`, `alt`, `width`, `height`, `srcset`, and `lazy` when the URL only sits in a lazy-loading attribute. | | `structuredData` | object | `jsonLd` (parsed), `microdata`, `openGraph` and `twitter`. | | `signals` | object | AI-use signals: `noai`, `noimageai`, and the TDM reservation (`tdmReserved`, `tdmPolicy`, `tdmSource`). | | `render` | object | `{mode, rendered, reason, tier, ms, timedOut, requests, robotsBlocked}`, or `unavailable` saying why the page wasn't rendered (the answer is then the raw-HTML one). | | `screenshot` | object | With `screenshot`: `{format: "webp", kind, viewport, bytes, data}`, the image in base64. | | `blocked` | object \| null | `{by, kind}` when a firewall answered instead of the page, such as a "verify you're human" challenge. It is reported as it came, never rendered or solved. | | `cached`, `ageSeconds` | boolean, integer | Whether this answer came from our store, and how old it is. | ## Rendered pages When Scrape renders a page (an empty app shell under the default `render=auto`, or every page with `render=always`), it loads the page in Chrome, runs its JavaScript and builds every part of the answer from the page as a browser shows it: content, links, metadata, highlights. `render.rendered` says whether it ran, and `render.reason` says why or why not. When the rendering lane is busy, the page can't be rendered in time, or the site's robots.txt asks for a Crawl-delay of a second or more (`crawlDelay`), you get the raw-HTML answer with `render.unavailable` saying why. Rendering follows the same rules as every Rankfield fetch: - robots.txt for every script and file the page loads (the ones it keeps out are listed in `render.robotsBlocked`); - no cookies and no form posts; - images, media, fonts and trackers are not loaded unless you ask for a screenshot. A page behind a bot challenge is reported as blocked, never worked around. ## PDF documents A PDF (`application/pdf`, or an untyped file that starts as a PDF) is read as a document: - **Content:** the document's text as `content.markdown` (`extraction: "pdf"`), the same for every `scope`, plus `content.pdf`: `pages`, `pagesRead` and `stopped`. - **Length:** up to the first 300 pages and 10 MB are read. When the text ends early, `stopped` says why (`sizeLimit`, `pageLimit`, `timeLimit`, `textLimit`) and `content.truncated` is true. - **Metadata:** the document's title (or its first line), author, subject as `description`, creation date as `publishedTime`. - Links, images, headings and structured data are empty. A PDF we can't read is a `415` `unsupported_content_type` with `details.reason`: `noTextLayer` (scanned pages, no text), `encrypted` (needs a password), `damaged` or `tooComplex`. Any other type (images, JSON, XML…) on a 2xx is a `415` with `details.contentType`. ## How we fetch We fetch as `Rankfield-User` (`Mozilla/5.0 (compatible; Rankfield-User/1.0; +https://rankfield.dev/bot)`). Scrape reads the raw HTML first. When that HTML is an empty JavaScript app shell, it renders the page in Chrome and answers from what a browser shows (`render.rendered` true). Pages with their content in the HTML are answered from the HTML, as before. Ask `render=never` for the raw HTML only. - **robots.txt:** obeyed on every request and redirect, following the rules for `Rankfield-User`, or for `RankfieldBot` when the file has none for it, or `*` when it has neither. A page it disallows is refused and never fetched. - **Firewalls:** a challenge, CAPTCHA or block page is reported in the answer, never worked around and never retried. - **Never:** cookies, logins, proxies or other bots' names. - **Excluded sites:** Reddit, LinkedIn, Facebook, Instagram, X/Twitter and TikTok (subdomains included), Google search result pages, and sites whose owners asked us are never fetched. - **Pace:** each website is fetched gently, shared by all customers, slower when its robots.txt sets a `Crawl-delay` or after a slow answer. Many URLs on one site go at that site's pace. Repeat calls to the same website within about a minute can wait their turn for that site's slot. If the wait runs past the call's time limit (20 seconds by default), the call ends with `504` `timeout` and `details.reason` `sitePace`. It doesn't count as usage: retry after `Retry-After`, or allow a longer `timeout`. More: [Rankfield crawlers](/bot). ## Errors Errors are `{"error": {"code", "message", "details"}}`. Branch on `code`; `message` is for people. | Status | Code | When | |---|---|---| | 400 | `invalid_input` | A parameter is missing, unknown or malformed. | | 401 | `unauthorized` | No valid API key. | | 403 | `robots_disallowed` | robots.txt disallows Rankfield-User for the page or a redirect; `details` has the rule and line. | | 403 | `site_excluded` | The site is on the exclusion list. | | 403 | `blocked` | The website's bot protection refused us. Reported at once and never worked around; `details` says which protection. Not counted as usage. | | 415 | `unsupported_content_type` | A page that answered 2xx isn't HTML, plain text, markdown or a readable PDF (an image, JSON, a scanned or password-protected PDF…). `details.reason` says why for PDFs. A website's error page in another type is an answer, not a 415. | | 422 | `address_not_public` | The host resolves to a private or internal address. | | 429 | `busy` | Our fetch capacity is busy: the call was refused at once, waited 4 seconds without a turn, or its time limit ran out while it waited (then `details.reason` is `capacity`). Not counted as usage. Retry after `Retry-After` seconds. | | 500 | `internal` | Our bug. Retry. | | 502 | `fetch_failed` | The website could not be reached (DNS, connection, TLS, more than 5 redirects); `details.reason` says which. | | 503 | `unavailable` | The service is restarting or briefly unavailable. Retry after `Retry-After`. (A full fetch capacity answers `429` `busy`.) | | 504 | `timeout` | The website didn't answer within the call's time limit. When `details.reason` is `sitePace`, the call waited its turn for this website instead, which we fetch gently, shared by all customers: retry after `Retry-After`, or allow a longer `timeout`. | Only answered pages and the `415`, `502` and website-side `504` errors count as usage; refusals, our own errors (`500`, `503`), `429` `busy` and a `504` with `details.reason` `sitePace` don't. ## Limits No per-key limits. When the server is busy you get `429` `busy` with `Retry-After`. Waiting calls are served in turn across keys, so a key with a few calls is never stuck behind one with thousands. ## MCP On the [MCP server](/docs/agents/mcp-server), the tool for this is `scrape_page`. --- # Start a Scrape job Source: https://rankfield.dev/docs/scrape/start-a-job Scrape up to 10,000 URLs in one background job; each item takes the same parameters as a single call. `POST https://rankfield.dev/v1/scrape/jobs` ```bash cURL curl -X POST 'https://rankfield.dev/v1/scrape/jobs' \ -H 'Authorization: Bearer YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -H 'Idempotency-Key: scrape-2026-10-26' \ -d '{"items":[{"url":"https://rankfield.dev/blog/what-is-site-strength"},{"url":"https://example.com/"}]}' ``` ```python Python import requests response = requests.post( "https://rankfield.dev/v1/scrape/jobs", headers={"Authorization": "Bearer YOUR_API_KEY", "Idempotency-Key": "scrape-2026-10-26"}, json={"items": [{"url": "https://rankfield.dev/blog/what-is-site-strength"}, {"url": "https://example.com/"}]}, ) print(response.json()) ``` ```javascript JavaScript const response = await fetch("https://rankfield.dev/v1/scrape/jobs", { method: "POST", headers: { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json", "Idempotency-Key": "scrape-2026-10-26", }, body: JSON.stringify({"items":[{"url":"https://rankfield.dev/blog/what-is-site-strength"},{"url":"https://example.com/"}]}), }); console.log(await response.json()); ``` ```json 202 { "job": { "id": "8f14e45fceea167a5a36dedd4bea2543", "status": "queued", "total": 2, "done": 0, "failed": 0, "createdAt": "2026-10-26T18:00:00Z", "finishedAt": null, "expiresAt": null } } ``` ```json 409 { "error": { "code": "invalid_input", "message": "This Idempotency-Key was used for different items" } } ``` ## Parameters ### Headers | Name | Type | Required | Default | Description | |---|---|---|---|---| | `Authorization` | string | Yes | – | `Bearer` followed by your API key. See [Authentication](/docs/get-started/authentication). | | `Idempotency-Key` | string | Yes | – | 1–200 characters you choose for this job. Sending the same key with the same body again returns the same job instead of a new one. | ### Body | Name | Type | Required | Default | Description | |---|---|---|---|---| | `items` | object[] | Yes | – | 1–10,000 items. Each takes the same parameters as [Scrape a page](/docs/scrape/scrape-a-page), as a JSON object. | Every item is checked before the job starts. If any is invalid, the whole request is a `400` listing the first 20 (`index` and `message`). ## Response `202` with `job`. A byte-identical body with the same `Idempotency-Key` replays the job (`200`, `Idempotency-Replayed: true`); any other body with that key is `409`. | Field | Type | Description | |---|---|---| | `job.id` | string | The job id. | | `job.status` | string | `queued`, `running`, `done` or `canceled`. | | `job.total` | integer | URLs in the job. | | `job.done` | integer | Items that succeeded. | | `job.failed` | integer | Items that failed. | | `job.createdAt`, `job.finishedAt` | string \| null | When it was created and when it finished (UTC). | | `job.expiresAt` | string \| null | When its results are deleted: 24 hours after it finishes. | A job runs 16 items at a time. An item counts as usage once the job accepts it, whatever its outcome. Items use the endpoint's default `timeout` rather than their own, and retry `busy` by themselves (up to 10 times). Follow it with [Get a Scrape job](/docs/scrape/get-a-job) and read items with [Get Scrape job results](/docs/scrape/get-results). ## Errors | Status | Code | When | |---|---|---| | 400 | `invalid_input` | No `Idempotency-Key`, no items, too many items, or an invalid item. | | 401 | `unauthorized` | No valid API key. | | 409 | `invalid_input` | The `Idempotency-Key` was used before with a different body. | | 429 | `busy` | The batch queue is full: 200,000 items from all keys are already waiting. Retry after `Retry-After` (60 seconds). | | 503 | `unavailable` | Batch jobs are paused (storage). Retry after `Retry-After` (300 seconds). | ## Limits Send up to 10,000 URLs in one job, and as many jobs as you like. No per-key limits. Jobs from all keys share one queue: when 200,000 items are already waiting, a new job answers `429` `busy` with `Retry-After` (60 seconds). ## MCP On the [MCP server](/docs/agents/mcp-server), the tool for this is `start_scrape_job`. --- # Get a Scrape job Source: https://rankfield.dev/docs/scrape/get-a-job Check a Scrape job: its status, how many items are done and failed, and when its results expire. `GET https://rankfield.dev/v1/scrape/jobs/{id}` ```bash cURL curl 'https://rankfield.dev/v1/scrape/jobs/8f14e45fceea167a5a36dedd4bea2543' \ -H 'Authorization: Bearer YOUR_API_KEY' ``` ```python Python import requests response = requests.get( "https://rankfield.dev/v1/scrape/jobs/8f14e45fceea167a5a36dedd4bea2543", headers={"Authorization": "Bearer YOUR_API_KEY"}, ) print(response.json()) ``` ```javascript JavaScript const response = await fetch("https://rankfield.dev/v1/scrape/jobs/8f14e45fceea167a5a36dedd4bea2543", { headers: { "Authorization": "Bearer YOUR_API_KEY", }, }); console.log(await response.json()); ``` ```json 200 { "job": { "id": "8f14e45fceea167a5a36dedd4bea2543", "status": "running", "total": 2, "done": 1, "failed": 0, "createdAt": "2026-10-26T18:00:00Z", "finishedAt": null, "expiresAt": null } } ``` ## Parameters ### Headers | Name | Type | Required | Default | Description | |---|---|---|---|---| | `Authorization` | string | Yes | – | `Bearer` followed by your API key. See [Authentication](/docs/get-started/authentication). | ### Path parameters | Name | Type | Required | Default | Description | |---|---|---|---|---| | `id` | string | Yes | – | The job id from [Start a Scrape job](/docs/scrape/start-a-job). | ## Response | Field | Type | Description | |---|---|---| | `job.id` | string | The job id. | | `job.status` | string | `queued`, `running`, `done` or `canceled`. | | `job.total` | integer | URLs in the job. | | `job.done` | integer | Items that succeeded. | | `job.failed` | integer | Items that failed. | | `job.createdAt`, `job.finishedAt` | string \| null | When it was created and when it finished (UTC). | | `job.expiresAt` | string \| null | When its results are deleted: 24 hours after it finishes. | Only the key that started a job can read it; any other key gets `404`. ## Errors | Status | Code | When | |---|---|---| | 401 | `unauthorized` | No valid API key. | | 404 | | No job with this id for this key, or it has expired. This answer has no `code`: `{"error":{"message":"Not found"}}`. | --- # Get Scrape job results Source: https://rankfield.dev/docs/scrape/get-results Read a Scrape job's items as each one finishes, with its full answer or error, and page through them with a cursor. `GET https://rankfield.dev/v1/scrape/jobs/{id}/results` ```bash cURL curl 'https://rankfield.dev/v1/scrape/jobs/8f14e45fceea167a5a36dedd4bea2543/results?cursor=0&limit=100' \ -H 'Authorization: Bearer YOUR_API_KEY' ``` ```python Python import requests response = requests.get( "https://rankfield.dev/v1/scrape/jobs/8f14e45fceea167a5a36dedd4bea2543/results?cursor=0&limit=100", headers={"Authorization": "Bearer YOUR_API_KEY"}, ) print(response.json()) ``` ```javascript JavaScript const response = await fetch("https://rankfield.dev/v1/scrape/jobs/8f14e45fceea167a5a36dedd4bea2543/results?cursor=0&limit=100", { headers: { "Authorization": "Bearer YOUR_API_KEY", }, }); console.log(await response.json()); ``` ```json 200 { "job": { "id": "8f14e45fceea167a5a36dedd4bea2543", "status": "done", "total": 2, "done": 1, "failed": 1, "createdAt": "2026-10-26T18:00:00Z", "finishedAt": "2026-10-26T18:00:04Z", "expiresAt": "2026-10-27T18:00:04Z" }, "cursor": "2", "hasMore": false, "items": [ { "index": 0, "ok": true, "result": { "…": "the same answer as a single call" } }, { "index": 1, "ok": false, "error": { "code": "fetch_failed", "message": "…", "details": { "reason": "dns" } } } ] } ``` ## Parameters ### Headers | Name | Type | Required | Default | Description | |---|---|---|---|---| | `Authorization` | string | Yes | – | `Bearer` followed by your API key. See [Authentication](/docs/get-started/authentication). | ### Path parameters | Name | Type | Required | Default | Description | |---|---|---|---|---| | `id` | string | Yes | – | The job id from [Start a Scrape job](/docs/scrape/start-a-job). | ### Query parameters | Name | Type | Required | Default | Description | |---|---|---|---|---| | `cursor` | integer | No | `0` | Where to continue: the `cursor` of the previous answer (returned as a string; send it back as it is). | | `limit` | integer | No | `100` | Items per answer, 1–500. | ## Read results Start with `cursor=0`. Read `items`, then ask again with the returned `cursor` while `hasMore` is `true`. Items arrive in the order they finish; `index` is the item's position in your `items`, from 0. | Field | Type | Description | |---|---|---| | `job` | object | The job, as in [Get a Scrape job](/docs/scrape/get-a-job). | | `items[].index` | integer | Position in your `items`. | | `items[].ok` | boolean | Whether the item succeeded. | | `items[].result` | object | The same answer as [Scrape a page](/docs/scrape/scrape-a-page). | | `items[].error` | object | `{code, message, details}`, as a single call's error. | | `cursor` | string | Pass it back to continue. | | `hasMore` | boolean | Whether more items exist now or will. | ## Errors | Status | Code | When | |---|---|---| | 400 | `invalid_input` | `cursor` or `limit` is invalid. | | 401 | `unauthorized` | No valid API key. | | 404 | | No job with this id for this key, or it has expired. This answer has no `code`: `{"error":{"message":"Not found"}}`. | ## MCP On the [MCP server](/docs/agents/mcp-server), the tool for this is `get_scrape_results`. --- # Cancel a Scrape job Source: https://rankfield.dev/docs/scrape/cancel-a-job Stop a Scrape job. Items already finished stay readable for 24 hours; items not started are dropped. `POST https://rankfield.dev/v1/scrape/jobs/{id}/cancel` ```bash cURL curl -X POST 'https://rankfield.dev/v1/scrape/jobs/8f14e45fceea167a5a36dedd4bea2543/cancel' \ -H 'Authorization: Bearer YOUR_API_KEY' ``` ```python Python import requests response = requests.post( "https://rankfield.dev/v1/scrape/jobs/8f14e45fceea167a5a36dedd4bea2543/cancel", headers={"Authorization": "Bearer YOUR_API_KEY"}, ) print(response.json()) ``` ```javascript JavaScript const response = await fetch("https://rankfield.dev/v1/scrape/jobs/8f14e45fceea167a5a36dedd4bea2543/cancel", { method: "POST", headers: { "Authorization": "Bearer YOUR_API_KEY", }, }); console.log(await response.json()); ``` ```json 200 { "job": { "id": "8f14e45fceea167a5a36dedd4bea2543", "status": "canceled", "total": 2, "done": 1, "failed": 0, "createdAt": "2026-10-26T18:00:00Z", "finishedAt": "2026-10-26T18:00:04Z", "expiresAt": "2026-10-27T18:00:04Z" } } ``` ## Parameters ### Headers | Name | Type | Required | Default | Description | |---|---|---|---|---| | `Authorization` | string | Yes | – | `Bearer` followed by your API key. See [Authentication](/docs/get-started/authentication). | ### Path parameters | Name | Type | Required | Default | Description | |---|---|---|---|---| | `id` | string | Yes | – | The job id from [Start a Scrape job](/docs/scrape/start-a-job). | ## Response The job, now `canceled`, as in [Get a Scrape job](/docs/scrape/get-a-job). Items already running finish; nothing new starts. A job that has already finished is returned unchanged (its status stays `done`). ## Errors | Status | Code | When | |---|---|---| | 401 | `unauthorized` | No valid API key. | | 404 | | No job with this id for this key, or it has expired. This answer has no `code`: `{"error":{"message":"Not found"}}`. | --- # Bot View Source: https://rankfield.dev/docs/bot-view See how 274 search engines, AI crawlers and SEO tools are treated by a page's robots.txt, llms.txt, meta robots and TDM signals. Bot View answers "which bots can read this page?" for every known crawler at once, with the robots.txt rule and line behind each verdict, and checks the other signals sites use for crawlers and AI: llms.txt, meta robots, X-Robots-Tag, Content-Signal and TDMRep. ## Pages - [Check how crawlers see a page](/docs/bot-view/check-a-page): See which of 274 search, AI and SEO crawlers a page's robots.txt allows, with the deciding rule, plus llms.txt, meta robots and TDM signals. - [Start a Bot View job](/docs/bot-view/start-a-job): Check up to 10,000 URLs with Bot View in one background job; each item takes the same parameters as a single call. - [Get a Bot View job](/docs/bot-view/get-a-job): Check a Bot View job: its status, how many items are done and failed, and when its results expire. - [Get Bot View job results](/docs/bot-view/get-results): Read a Bot View job's items as each one finishes, with its full answer or error, and page through them with a cursor. - [Cancel a Bot View job](/docs/bot-view/cancel-a-job): Stop a Bot View job. Items already finished stay readable for 24 hours; items not started are dropped. ::: --- # Check how crawlers see a page Source: https://rankfield.dev/docs/bot-view/check-a-page See which of 274 search, AI and SEO crawlers a page's robots.txt allows, with the deciding rule, plus llms.txt, meta robots and TDM signals. `GET https://rankfield.dev/v1/bot-view?url={url}` ```bash cURL curl 'https://rankfield.dev/v1/bot-view?url=https://www.airbnb.com/' \ -H 'Authorization: Bearer YOUR_API_KEY' ``` ```python Python import requests response = requests.get( "https://rankfield.dev/v1/bot-view?url=https://www.airbnb.com/", headers={"Authorization": "Bearer YOUR_API_KEY"}, ) print(response.json()) ``` ```javascript JavaScript const response = await fetch("https://rankfield.dev/v1/bot-view?url=https://www.airbnb.com/", { headers: { "Authorization": "Bearer YOUR_API_KEY", }, }); console.log(await response.json()); ``` ```json 200 { "url": "https://www.airbnb.com/", "agents": [ "all" ], "finalUrl": "https://www.airbnb.com/", "status": 200, "redirects": [], "summary": { "agents": 274, "allowed": 265, "blocked": 9, "blockedSite": 9, "purposes": { "aiTraining": { "agents": 44, "allowed": 38, "blocked": 6, "blockedSite": 6 }, "aiSearch": { "agents": 30, "allowed": 30, "blocked": 0, "blockedSite": 0 }, "search": { "agents": 29, "allowed": 29, "blocked": 0, "blockedSite": 0 } }, "robotsTxt": "found", "pageChecked": true }, "companies": [ { "company": "OpenAI", "agents": [ { "token": "GPTBot", "matchedAs": "GPTBot", "purpose": "aiTraining", "controlToken": false, "respectsRobots": "yes", "known": true, "allowed": false, "page": { "allowed": false, "group": "GPTBot", "groupKind": "named", "rule": "Disallow: /", "line": 691, "note": null }, "site": { "allowed": false, "group": "GPTBot", "groupKind": "named", "rule": "Disallow: /", "line": 691, "note": null }, "finalUrl": null, "crawlDelay": null, "directives": [], "docs": "https://developers.openai.com/api/docs/bots", "note": null }, { "token": "OAI-SearchBot", "matchedAs": "OAI-SearchBot", "purpose": "aiSearch", "controlToken": false, "respectsRobots": "yes", "known": true, "allowed": true, "page": { "allowed": true, "group": "OAI-SearchBot", "groupKind": "named", "rule": null, "line": null, "note": null }, "site": { "allowed": true, "group": "OAI-SearchBot", "groupKind": "named", "rule": null, "line": null, "note": null }, "finalUrl": null, "crawlDelay": null, "directives": [], "docs": "https://developers.openai.com/api/docs/bots", "note": null } ] } ], "robotsTxt": { "url": "https://www.airbnb.com/robots.txt", "status": 200, "outcome": "found", "reason": null, "fetchedAt": "2026-10-24T17:15:30Z", "bytes": 20930, "truncated": false, "groups": 18, "rules": 711, "sitemaps": [ "https://www.airbnb.com/sitemap-master-index.xml.gz" ], "contentSignals": [], "problems": [], "final": null }, "llmsTxt": { "url": "https://www.airbnb.com/llms.txt", "finalUrl": "https://www.airbnb.com/404", "status": 404, "present": false, "valid": null, "servedHtml": true, "contentType": "text/html;charset=utf-8", "bytes": 2947, "truncated": false, "title": null, "summary": null, "sections": [], "links": 0, "problems": [], "note": null }, "llmsFullTxt": { "url": "https://www.airbnb.com/llms-full.txt", "finalUrl": "https://www.airbnb.com/404", "status": 404, "present": false, "valid": null, "servedHtml": true, "contentType": "text/html;charset=utf-8", "bytes": 2947, "truncated": false, "title": null, "words": null, "problems": [], "note": null }, "pageDirectives": { "checked": true, "note": null, "metaRobots": [], "botMetas": [ { "name": "google", "content": "notranslate", "directives": [ "notranslate" ], "agents": [] } ], "xRobotsTag": [], "all": { "noindex": false, "nofollow": false, "noarchive": false, "nosnippet": false, "noimageindex": false, "notranslate": false, "noai": false, "noimageai": false, "maxSnippet": null, "maxImagePreview": null, "maxVideoPreview": null, "unavailableAfter": null } }, "tdm": { "reserved": null, "source": null, "policy": null, "policySource": null, "problems": [], "headers": { "checked": true, "reservation": null, "policy": null }, "meta": { "checked": true, "reservation": null, "policy": null }, "tdmrepJson": { "url": "https://www.airbnb.com/.well-known/tdmrep.json", "status": 404, "present": false, "valid": null, "rules": null, "matchedRule": null, "problems": [], "note": null }, "note": null }, "firewall": { "vendor": null, "challenged": false, "kind": null, "signals": [] }, "rawHtml": { "checked": true, "note": null, "bytes": 664934, "truncated": false, "visibleWords": 511, "mainTextWords": 426, "mainTextMode": "article", "noscriptWords": 14, "scripts": { "inline": 13, "external": 40, "inlineBytes": 321685 }, "jsShell": false, "jsShellSignals": [ "a