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

# Link Extractor

> Extract every link from a page or an entire site, grouped by type, with anchor text and rel.

Three endpoints, sharing the Broken Link Checker's crawl engine but never checking HTTP status — this tool only reports each link's metadata (anchor text, `rel`, type) and counts by type. Use it to audit outbound links, find every `mailto:`/`tel:` on a site, or feed a link graph.

<Info>
  **1 credit** per call (single-page extract or crawl start) · Typical response time \~200ms for a single page
</Info>

## Extract from a single page

`GET /v1/link-extract?url=...` extracts every link on one page and returns immediately.

### Query parameters

<ParamField query="url" type="string" required>
  The page to extract links from. `https://` is assumed if the scheme is omitted.
</ParamField>

### Response

<ResponseField name="data" type="object" required>
  <Expandable title="properties" defaultOpen>
    <ResponseField name="url" type="string" required>
      Echoes the requested URL.
    </ResponseField>

    <ResponseField name="scannedAt" type="string" required>
      ISO 8601 timestamp.
    </ResponseField>

    <ResponseField name="totalLinks" type="integer" required>
      Same as `counts.total`.
    </ResponseField>

    <ResponseField name="truncated" type="boolean" required>
      `true` if the page had more than 5,000 links.
    </ResponseField>

    <ResponseField name="counts" type="object" required>
      <Expandable title="properties">
        <ResponseField name="total" type="integer" required>
          Links reported below — excludes in-page anchors and non-HTTP schemes.
        </ResponseField>

        <ResponseField name="internal" type="integer" required>
          Links to the same site.
        </ResponseField>

        <ResponseField name="external" type="integer" required>
          Links to a different site.
        </ResponseField>

        <ResponseField name="mailto" type="integer" required>
          `mailto:` links.
        </ResponseField>

        <ResponseField name="tel" type="integer" required>
          `tel:` links.
        </ResponseField>
      </Expandable>

      In-page anchors (`#...`) and non-HTTP schemes (`javascript:`, `data:`, ...) are discovered but excluded from these counts.
    </ResponseField>

    <ResponseField name="links" type="array" required>
      <Expandable title="item shape">
        <ResponseField name="url" type="string" required>
          Resolved absolute URL (or the raw href for non-HTTP schemes).
        </ResponseField>

        <ResponseField name="originalHref" type="string" required>
          href exactly as authored in the HTML.
        </ResponseField>

        <ResponseField name="text" type="string" required>
          Anchor text.
        </ResponseField>

        <ResponseField name="rel" type="string">
          The `rel` attribute, e.g. `"nofollow noopener"`.
        </ResponseField>

        <ResponseField name="type" type="string" required>
          `"internal"`, `"external"`, `"mailto"`, `"tel"`, `"anchor"`, or `"other"`.
        </ResponseField>

        <ResponseField name="scheme" type="string" required>
          URL scheme without the colon, e.g. `"https"`, `"mailto"`.
        </ResponseField>
      </Expandable>
    </ResponseField>
  </Expandable>
</ResponseField>

<RequestExample>
  ```bash cURL theme={null}
  curl "https://api.pocketutils.com/v1/link-extract?url=https://example.com" \
    -H "Authorization: Bearer YOUR_API_KEY"
  ```

  ```javascript JavaScript theme={null}
  const res = await fetch(
    "https://api.pocketutils.com/v1/link-extract?url=https://example.com",
    { headers: { Authorization: "Bearer YOUR_API_KEY" } },
  );
  const { data } = await res.json();
  ```
</RequestExample>

<ResponseExample>
  ```json Response (abridged) theme={null}
  {
    "success": true,
    "data": {
      "url": "https://example.com",
      "scannedAt": "2026-07-21T09:14:02.000Z",
      "totalLinks": 64,
      "truncated": false,
      "counts": { "total": 64, "internal": 48, "external": 12, "mailto": 3, "tel": 1 },
      "links": [
        {
          "url": "https://example.com/pricing",
          "originalHref": "/pricing",
          "text": "Pricing",
          "rel": null,
          "type": "internal",
          "scheme": "https"
        },
        {
          "url": "mailto:hello@example.com",
          "originalHref": "mailto:hello@example.com",
          "text": "hello@example.com",
          "rel": null,
          "type": "mailto",
          "scheme": "mailto"
        }
      ]
    }
  }
  ```
</ResponseExample>

***

## Extract from an entire site

For more than one page, start an async crawl and poll it — see [Async Jobs & Polling](/async-jobs) for the full pattern, phase list, and crawl limits (250 pages, 6-minute budget).

### Start the crawl

`POST /v1/link-extract/crawl`, body `{ "url": "..." }`. Returns `202` immediately:

```json theme={null}
{ "success": true, "data": { "jobId": "a1b2c3d4-5678-4e9f-9a0b-1c2d3e4f5a6b" } }
```

### Poll the status

`GET /v1/link-extract/crawl/:jobId` — free and uncached. Returns the [standard job envelope](/async-jobs#response-shape), with `progress` shaped as `phase`, `message`, `sitemapFound`, `sitemapUrlCount`, `pagesScanned`, `pagesDiscovered`, `pagesCap`, `linksFound`. Once `status` is `"done"`, `result` is a `LinkExtractResult` (same shape as the single-page response, capped at 3,000 links instead of 5,000), and each link additionally carries:

<ResponseField name="result.links[].sources" type="array">
  Sample of source pages the link was found on, capped at 25.
</ResponseField>

<ResponseField name="result.links[].sourceCount" type="integer">
  Total number of pages the link was found on across the whole crawl.
</ResponseField>

<RequestExample>
  ```bash Start crawl theme={null}
  curl -X POST https://api.pocketutils.com/v1/link-extract/crawl \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"url": "https://example.com"}'
  ```

  ```bash Poll status theme={null}
  curl https://api.pocketutils.com/v1/link-extract/crawl/a1b2c3d4-5678-4e9f-9a0b-1c2d3e4f5a6b \
    -H "Authorization: Bearer YOUR_API_KEY"
  ```
</RequestExample>

## Errors

| Status | Cause                                                                             |
| ------ | --------------------------------------------------------------------------------- |
| `400`  | `url` missing (query parameter for the single-page extract, body field for crawl) |
| `404`  | `jobId` doesn't exist or its 30-minute window has expired                         |

See the full [error reference](/errors) for the platform-level codes (`401`, `402`, `429`) that apply to every endpoint.


## Related topics

- [Async Jobs & Polling](/async-jobs.md)
- [Introduction](/index.md)
- [Broken Link Checker](/api-reference/broken-links.md)
- [Rate Limits & Credits](/rate-limits-and-credits.md)
- [Errors](/errors.md)
