# Website Favicon Discovery (`automation-lab/website-favicon-discovery`) Actor

Extract favicon URLs, rel, type, sizes and source metadata from public website URLs for website catalogue and brand enrichment.

- **URL**: https://apify.com/automation-lab/website-favicon-discovery.md
- **Developed by:** [Automation Lab](https://apify.com/automation-lab) (community)
- **Categories:** Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.22 / 1,000 website inspecteds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Website Favicon Discovery

Website favicon extraction for catalogue enrichment: supply public website URLs and receive one structured row per website with favicon URLs, declared sizes, MIME types, rel values and source metadata. Use the results to add source-linked icons to website directories, CRM company records or brand inventories.

### What does it do?

The Actor fetches each supplied HTML page and reads supported icon declarations. It resolves relative URLs against the page's first valid base declaration or final redirected URL. It does not download, resize, convert or validate images.

Supported declarations are `icon` (including `shortcut icon`), `apple-touch-icon`, `apple-touch-icon-precomposed` and `mask-icon`. Optional root favicon inference is clearly marked as a fallback. Web manifests, dynamically inserted DOM elements and generic page images are not collected.

### Who is it for?

- Directory operators enriching recurring website catalogues.
- Developers mapping company domains to declared icon assets.
- Brand researchers comparing published icon declarations across websites.

This is not an icon generator or a brand-logo detector.

### Why use Website Favicon Discovery?

Keep the page URL, final hostname and icon attributes together rather than losing provenance in a list of images. A single row includes every retained declaration so downstream joins remain one website to one record. Exact duplicate declarations are removed, while the same URL with different rel, type or sizes is preserved.

### Getting started

1. Enter public website URLs in **Website URLs**.
2. Choose a maximum number of websites to attempt.
3. Leave inferred fallback disabled when you only want declared icons.
4. Run the Actor and open the default dataset.
5. Export JSON to retain nested icons, or select flattened fields for a spreadsheet.

Example input:

```json
{
  "startUrls": [{"url":"https://apify.com"},{"url":"https://github.com"}],
  "maxItems": 10,
  "includeFallback": false
}
```

### Inputs

| Field | Default | Meaning |
| --- | --- | --- |
| `startUrls` | Required | 1–1000 fully qualified public HTTP(S) URLs. Each entry is an object with a `url` field. |
| `maxItems` | 10 | Global cap on distinct normalized URLs attempted, in input order; range 1–1000. Failures count. Zero/unlimited is unsupported. |
| `includeFallback` | false | Only when no supported declaration exists, return final-origin `/favicon.ico` as an unverified inference. |

Only GET requests are supported. URL entries accept `url` only; `method`, `headers`, `payload` and `userData` controls are rejected even if a client editor offers them.

Duplicate normalized URLs are processed once, ignoring fragments. Distinct paths are distinct inputs even on the same domain. Only standard HTTP(S) ports are supported. Credentials and private/reserved network destinations are rejected, including redirect destinations.

### Extracted data

| Field | Meaning |
| --- | --- |
| `inputUrl` | Normalized input URL |
| `finalUrl` | URL after redirects |
| `hostname` | Final page hostname |
| `httpStatus` | Successful HTML response status |
| `pageTitle` | First title, up to 1000 characters, or null |
| `icons` | Array of icon URL, rel, type, sizes, source and verified flag |
| `iconCount` | Number of retained icons |
| `primaryIconUrl` | First declaration whose rel includes `icon`, otherwise first retained declaration; null when none |
| `status` | `found` (including inferred fallback) or `no_icons` |
| `truncated` | True when more than 200 unique valid declarations were found |
| `scrapedAt` | UTC extraction timestamp |

`icons[].type` and `icons[].sizes` are author-declared strings, not measured properties. They are null when absent. `rel` is lowercased and whitespace-normalized. `source` is `html` or `fallback`. `verified` is always false: a declaration does not prove that an asset exists or is usable. Non-HTTP(S) icon URLs, including embedded data URLs, are excluded.

### Output example

A representative GitHub page result has this structure; timestamps vary:

```json
{
  "inputUrl": "https://github.com/",
  "finalUrl": "https://github.com/",
  "hostname": "github.com",
  "httpStatus": 200,
  "pageTitle": "GitHub · Change is constant. GitHub keeps you ahead. · GitHub · GitHub",
  "icons": [
    {"url":"https://github.githubassets.com/assets/pinned-octocat-093da3e6fa40.svg","rel":"mask-icon","type":null,"sizes":null,"source":"html","verified":false},
    {"url":"https://github.githubassets.com/favicons/favicon.png","rel":"alternate icon","type":"image/png","sizes":null,"source":"html","verified":false},
    {"url":"https://github.githubassets.com/favicons/favicon.svg","rel":"icon","type":"image/svg+xml","sizes":null,"source":"html","verified":false}
  ],
  "iconCount": 3,
  "primaryIconUrl": "https://github.githubassets.com/favicons/favicon.png",
  "status": "found",
  "truncated": false,
  "scrapedAt": "2026-10-26T07:00:00.000Z"
}
```

See the dataset for the actual current declarations. The Actor does not rank icons by image quality, pixel size or browser appearance.

### How much does it cost to extract website favicons?

Pricing is pay-per-event: a one-time `start` fee plus one `item` event per successfully inspected website row. This includes inspections finding no icons: the delivered result records that the inspected HTML has no supported icon declaration. The number of icons inside a row does not multiply the charge. Failed websites have no item charge. The start fee still applies after valid input is accepted, even if sources fail.

The one-time start fee is **$0.0015** on every tier. Per inspected website:

| Apify Store spend tier | Item price |
| --- | --- |
| FREE | $0.000414 |
| BRONZE | $0.00036 |
| SILVER | $0.0002808 |
| GOLD | $0.000216 |
| PLATINUM | $0.000216 |
| DIAMOND | $0.000216 |

At BRONZE, 1 inspected website costs about $0.00186, 10 cost $0.0051, and 100 cost $0.0375, including one start fee. These Actor-charge estimates exclude taxes and any separately applicable Apify account charges. Higher qualifying aggregate monthly Apify Store spend tiers receive the displayed discounts; these are not private per-Actor volume discounts.

### Limits and failure behavior

Each page allows at most five redirects and 10 seconds per request attempt. A transient network error, 429 or selected 5xx response can be retried once. The HTML download is capped at 2 MiB; larger pages fail instead of silently returning incomplete declarations. Pages with more than 1024 nested elements also fail rather than exhaust parser resources. Streaming parsing retains no full DOM. Up to four websites are fetched concurrently; input attempts are selected in order, but result rows can arrive in completion order.

The Actor uses direct HTTP, no browser and no paid proxy fallback. Protected, login-only, JavaScript-only or geographically restricted pages may fail. HTML responses without icons are valid `no_icons` results. Non-HTML responses and HTTP errors fail the source. If any website fails, the run is FAILED after the remaining selected inputs are attempted; available dataset rows are partial results, not a complete successful batch.

There are at most 200 icon declarations per website; `truncated=true` explicitly identifies overflow. Source changes, geo responses and dynamically generated markup can affect which declarations appear. The optional fallback may point to a missing file.

### Integrations

Join results to your catalogue by `inputUrl` or `hostname`. Keep `finalUrl` to audit redirected domains. Store the nested icon array in your database, or use `primaryIconUrl` in a review queue before displaying third-party images.

Apify dataset exports and integrations can deliver results to spreadsheets, automation platforms or your own data pipeline. To compare changes, schedule runs with Apify and compare saved datasets downstream; this Actor does not keep historical state, issue alerts or perform change detection itself.

### API usage

Start a run once, retain its run ID, and wait for that same run to finish before reading its default dataset. A start response is metadata, not extracted rows.

#### cURL

```bash
curl -X POST 'https://api.apify.com/v2/acts/automation-lab~website-favicon-discovery/runs' \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"startUrls":[{"url":"https://apify.com"}],"maxItems":1}'
```

#### JavaScript

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({token: process.env.APIFY_TOKEN});
const run = await client.actor('automation-lab/website-favicon-discovery').call({
  startUrls: [{url: 'https://apify.com'}], maxItems: 1
});
if (run.status !== 'SUCCEEDED') throw new Error(`Run ${run.id}: ${run.status}`);
const {items} = await client.dataset(run.defaultDatasetId).listItems({limit: 20});
console.log(items);
```

#### Python

```python
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('automation-lab/website-favicon-discovery').call(
    run_input={'startUrls': [{'url': 'https://apify.com'}], 'maxItems': 1})
if run['status'] != 'SUCCEEDED':
    raise RuntimeError(f"Run {run['id']}: {run['status']}")
print(client.dataset(run['defaultDatasetId']).list_items(limit=20).items)
```

### MCP setup

#### Claude Code

```bash
claude mcp add --transport http apify \
  "https://mcp.apify.com?tools=automation-lab/website-favicon-discovery"
```

#### Claude Desktop, Cursor, and VS Code

Use the equivalent HTTP MCP configuration supported by your client:

```json
{
  "mcpServers": {
    "apify": {
      "type": "http",
      "url": "https://mcp.apify.com?tools=automation-lab/website-favicon-discovery"
    }
  }
}
```

Authenticate using the client's supported Apify sign-in or token configuration. Discover actual names and schemas with scoped `tools/list`; Actor selection also includes run and storage tools, not exactly one tool.

Example prompt: “Extract declared favicon URLs from https://apify.com and https://github.com. Show the final domains, primary icon URLs and unverified status.”

For an authorized invocation, start once and retain the returned run/storage IDs. If still running after the server wait (at most 45 seconds), poll the same run with 2, 4, 8 then at most 10 seconds backoff, stopping after a 120-second total deadline. On a client timeout, check the known run rather than starting again. At deadline, report pending with the run ID. A failed run's rows are partial.

After success, read untransformed dataset pages of 20 rows, starting at offset 0, selecting only needed fields. Stop at exhaustion, 100 source rows or 64 KiB of serialized UTF-8 results admitted to model context, whichever occurs first. Enforce the byte limit host-side; if your client cannot intercept oversized responses, do not claim a hard byte guarantee. Disclose omissions, truncation and the continuation offset; keep full exports outside model context. Tool discovery and context caching depend on the client; no token-savings guarantee is made.

### Legality and data handling

Only submit public pages you are permitted to access and process. Respect site terms, intellectual-property rights and applicable law. Discovering an asset URL does not grant permission to reuse that asset. This independent utility is not affiliated with target websites.

No AI model is used during Actor execution. No paid external API or proxy is required. HTML is processed transiently; it is not saved to the dataset. Dataset rows include supplied/final URLs, page titles and public icon declarations, which may contain query values: do not supply secrets or private URLs. Apify stores run input, datasets and logs under its account retention controls; delete them through Apify when no longer needed. The Actor keeps no cross-run cache.

Failed operations send sanitized diagnostic input, exceptions and actor/build/run IDs to our private GlitchTip service for repair; secret fields and URL queries are removed and reports are retained for 30 days. Apify and the target websites receive ordinary requests. No icon binary is downloaded.

### FAQ and troubleshooting

#### Why is verified always false?

This product discovers declarations, not image availability or correctness. Validate and approve an asset downstream before using it.

#### Why did a batch fail when rows exist?

At least one selected website failed. Use rows only as partial output, inspect logs and retry the failed source separately after correcting the input or access issue.

#### Why are there no icons?

The page may have no supported HTML declaration, require JavaScript, or declare icons only in a manifest. With fallback disabled, an empty icon array truthfully records this outcome. A successful website inspection is billed even when it finds no supported declarations. Enable fallback only if an unverified inferred URL is useful.

#### Can it download, convert or monitor icons?

No. Results contain URL metadata. Schedule and compare datasets externally if recurring enrichment is needed.

### Related workflows and support

This is a standalone metadata utility; no additional Actor is required. Use Apify dataset exports or your existing ETL tools for catalogue enrichment. Report bugs and how-to questions through the Actor's Apify Issues tab with public sample input and a run link; never post credentials.

# Changelog

This Actor's version history is a separate document: https://apify.com/automation-lab/website-favicon-discovery/changelog.md

# Actor input Schema

## `startUrls` (type: `array`):

Supply 1–1000 fully qualified public HTTP(S) website URLs using url only. Requests are GET; method, headers, payload and userData entry controls are unsupported and rejected. Duplicate normalized URLs (ignoring fragments) are processed once; different paths remain separate. Credentials, non-HTTP(S) schemes, nonstandard ports and private/reserved network destinations are rejected, including redirects.

## `maxItems` (type: `integer`):

Global maximum of distinct normalized input URLs attempted, in input order. Default 10, range 1–1000; zero/unlimited is unsupported. Failed attempts count toward this cap. Each website yields at most 200 icon declarations, with truncation flagged.

## `includeFallback` (type: `boolean`):

When true, only if a page has no supported icon declarations, return its final origin /favicon.ico as source=fallback, verified=false. This URL is not requested and may not exist. False by default; no-icons pages then return status=no_icons. Each successfully inspected website row has one item charge, including no_icons; failed websites have no item charge.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://apify.com"
    },
    {
      "url": "https://github.com"
    }
  ],
  "maxItems": 10,
  "includeFallback": false
}
```

# Actor output Schema

## `dataset` (type: `string`):

One row per website, including nested icon metadata.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "startUrls": [
        {
            "url": "https://apify.com"
        },
        {
            "url": "https://github.com"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/website-favicon-discovery").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "startUrls": [
        { "url": "https://apify.com" },
        { "url": "https://github.com" },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/website-favicon-discovery").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "startUrls": [
    {
      "url": "https://apify.com"
    },
    {
      "url": "https://github.com"
    }
  ]
}' |
apify call automation-lab/website-favicon-discovery --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automation-lab/website-favicon-discovery"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/HwLyc3oe0LS49p6he/builds/HBcHLihI3JmW8eKPf/openapi.json
