> ## Documentation index
> Fetch the complete documentation index at: https://rankfield.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Extract links from a page

> 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`.
