# Clutch.co Scraper - Agency Directory & B2B Company Data (`marknetic/clutch-scraper`) Actor

Scrape B2B agencies from Clutch.co by category and location. Returns rating, reviews, hourly rate, min project size, employees and services. Pay only for rows returned.

- **URL**: https://apify.com/marknetic/clutch-scraper.md
- **Developed by:** [Marknetic](https://apify.com/marknetic) (community)
- **Categories:** Automation, Lead generation, SEO tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.00 / 1,000 returned rows

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Clutch.co Scraper — Agency Directory & B2B Company Data

Extract B2B agencies from [Clutch.co](https://clutch.co) by category and location — ratings, review counts, hourly rates, minimum project sizes, headcount, and service mix — as structured JSON, CSV, or Excel.

Built for lead lists, competitor research, and market mapping.

***

### Three guarantees

**1. You pay per row returned. Nothing else.**

No charge to start a run. No charge for a failed request, a blocked page, or a URL that turns out not to exist. If a run returns nothing, it costs you nothing — and the run summary says so in plain words.

**2. Every field matches the page, or it is `null`.**

A value is never inferred, never estimated, and never borrowed from a different field that happened to look similar. If Clutch shows an agency's minimum project size as *Undisclosed*, you get `null` and a reason — not a number invented from somewhere else on the page.

This is enforced in code, not by good intentions: every field declares which source keys may legally populate it, and each row ships with a `_provenance` block recording where each value came from.

**3. Nothing is dropped silently.**

Every requested URL is accounted for. If one cannot be fetched, it appears in the run summary with a reason (`not_found`, `blocked`, `transient`, `permanent`) instead of vanishing from your results. A short record and a missing record are different things, and you can tell them apart.

***

### What you get

| Field | Example |
|---|---|
| `name` | `Ignite Visibility` |
| `profileUrl` | `https://clutch.co/profile/ignite-visibility` |
| `rating` | `4.8` |
| `reviewCount` | `173` |
| `minProjectSize` | `$1,000+` |
| `hourlyRate` | `$100 - $149 / hr` |
| `employees` | `250 - 999` |
| `location` | `San Diego, CA` |
| `services` | `[{ "service": "Search Engine Optimization", "percent": 40 }, …]` |
| `sourceUrl` | the page the agency was found on |
| `_provenance` | where each field came from, or why it is `null` |

#### Real sample output

```json
{
  "name": "Ignite Visibility",
  "profileUrl": "https://clutch.co/profile/ignite-visibility",
  "rating": 4.8,
  "reviewCount": 173,
  "minProjectSize": "$1,000+",
  "hourlyRate": "$100 - $149 / hr",
  "employees": "250 - 999",
  "location": "San Diego, CA",
  "services": [
    { "service": "Search Engine Optimization", "percent": 40 },
    { "service": "Advertising", "percent": 10 },
    { "service": "Email Marketing", "percent": 10 },
    { "service": "Pay Per Click", "percent": 10 },
    { "service": "Social Media Marketing", "percent": 10 },
    { "service": "Web Design", "percent": 10 },
    { "service": "Web Development", "percent": 10 }
  ],
  "sourceUrl": "https://clutch.co/us/agencies/digital-marketing",
  "_provenance": {
    "name":             { "source": "dom", "key": "h3 a",                     "reason": null, "conflict": null },
    "profile_url":      { "source": "dom", "key": "a[href*=\"/profile/\"]",   "reason": null, "conflict": null },
    "rating":           { "source": "dom", "key": ".sg-rating__number",       "reason": null, "conflict": null },
    "review_count":     { "source": "dom", "key": ".sg-rating__reviews",      "reason": null, "conflict": null },
    "min_project_size": { "source": "dom", "key": ".min-project-size",        "reason": null, "conflict": null },
    "hourly_rate":      { "source": "dom", "key": ".hourly-rate",             "reason": null, "conflict": null },
    "employees":        { "source": "dom", "key": ".employees-count",         "reason": null, "conflict": null },
    "location":         { "source": "dom", "key": ".location",                "reason": null, "conflict": null },
    "services":         { "source": "dom", "key": ".provider__services-list-item", "reason": null, "conflict": null }
  }
}
```

Every field is accounted for — that is the full block, not an excerpt.

#### An agency that withholds its numbers

The row keeps **every key**, and each `null` says why:

```json
{
  "name": "MAXIMUS",
  "profileUrl": "https://clutch.co/profile/maximus",
  "rating": null,
  "reviewCount": null,
  "minProjectSize": null,
  "hourlyRate": "$100 - $149 / hr",
  "employees": "1,000 - 9,999",
  "location": "Reston, VA",
  "services": [],
  "sourceUrl": "https://clutch.co/profile/maximus",
  "_provenance": {
    "name":             { "source": "dom",  "key": "h1",                   "reason": null,          "conflict": null },
    "min_project_size": { "source": null,   "key": null,                   "reason": "undisclosed", "conflict": null },
    "hourly_rate":      { "source": "dom",  "key": "label \"Hourly rate\"", "reason": null,          "conflict": null },
    "rating":           { "source": null,   "key": null,                   "reason": "absent",      "conflict": null },
    "review_count":     { "source": null,   "key": null,                   "reason": "absent",      "conflict": null },
    "services":         { "source": null,   "key": null,                   "reason": "absent",      "conflict": null },
    "employees":        { "source": "dom",  "key": "label \"Employees\"",   "reason": null,          "conflict": null },
    "location":         { "source": "dom",  "key": "label \"Locations\"",   "reason": null,          "conflict": null }
  }
}
```

Read those two lines together — they are the guarantee in miniature:

- `min_project_size` is `null` with reason **`undisclosed`**, because the page shows *Undisclosed*. This agency's page also carries a machine-readable `priceRange` of `$100 - $149`. That number is the **hourly rate**, so it is not eligible to fill the project minimum, and it does not. You get `null`, not a plausible-looking `$100`.
- `hourly_rate` is populated from **`label "Hourly rate"`** — the value a human reads on the page — and the provenance names that source, so you can check it.

`absent` means Clutch publishes no value (this agency has no reviews yet). `undisclosed` means Clutch explicitly shows *Undisclosed*. Both are `null` — and you can tell which is which.

***

### Reliability

Measured over **300 consecutive requests** (26,361 agencies) through the same path this
actor uses in production:

| | |
|---|---|
| Requests succeeded | **300 of 300** |
| Retries needed | **none** — 1.00 attempts per request |
| Trend across the run | **flat** — no drop-off from the first 30 requests to the last 30 |

Stated conservatively: **at least a 99% success rate, with 95% confidence.** The
observed rate was 100%, but 300 requests cannot prove 100% — so the published number is
the bound the evidence actually supports, not the number that reads best.

This is a snapshot from one run, not a promise about every future run.

***

### Speed

Measured on the Apify platform, not estimated:

| | |
|---|---|
| **500 agencies** | **25–61 seconds** (two runs: 25.2 s and 60.9 s) |
| Per directory page | 74–95 agencies, ~3.6 s median |
| Memory used | 80–103 MB peak, at a 512 MB setting |

Speed varies with how quickly Clutch responds, so the range above is both runs as measured rather than the better one.

Directory pages are read directly, so one request yields dozens of agencies rather than one. No browser is launched, which is why the runs are fast and the memory footprint is small.

***

### Input

| Field | Description |
|---|---|
| **Clutch.co URLs** | Directory pages or agency profile URLs. Filter on Clutch.co, then paste the address bar. |
| **Maximum agencies** | Stop after N agencies. Defaults to **500 (about $5.00)**. Pagination is followed automatically. `0` = no limit, but a full category can exceed 35,000 agencies. |

```json
{
  "startUrls": [
    { "url": "https://clutch.co/us/agencies/digital-marketing" },
    { "url": "https://clutch.co/uk/agencies/social-media-marketing" }
  ],
  "maxResults": 500
}
```

Both directory and profile URLs work:

- `https://clutch.co/us/agencies/digital-marketing` — a whole category
- `https://clutch.co/us/agencies/digital-marketing/chicago` — narrowed by city
- `https://clutch.co/profile/ignite-visibility` — one specific agency

#### Category URLs are validated before the run starts

Clutch's category and location paths are not uniformly shaped — some city and industry combinations exist, and many do not. Rather than discovering that mid-run, directory URLs are checked against Clutch's own sitemap **before any billable work**.

A URL Clutch does not publish is reported in the run summary with a list of close matches, and costs you nothing. **One bad URL does not sink the run** — the valid ones are still scraped. If none of your URLs are valid there is nothing to scrape, so the run stops immediately and still charges nothing.

***

### Typical uses

- **Agency lead lists** — pull every digital marketing agency in a city with their rate band and headcount
- **Competitor tracking** — monitor ratings and review counts across a category over time
- **Market research** — service-mix percentages across hundreds of agencies in one export
- **Vendor shortlists** — filter by minimum project size and hourly rate to match a budget

***

### Notes

Only publicly visible directory information is collected — the same pages any visitor can browse. Query-parameter filter URLs that Clutch disallows in its `robots.txt` are not requested; filter by choosing the right category or location page instead.

A single category can hold 35,000+ agencies across 700+ pages. **Maximum agencies** defaults to 500 (about $5.00) precisely so a full category cannot run away with your budget by accident. It doubles as your cost ceiling, since you are charged per agency returned.

***

### FAQ

**What happens if the run finds nothing?**
It costs you nothing, and the run summary states that explicitly.

**What if I mistype one of my URLs?**
The other URLs are still scraped. The bad one is listed in the run summary with suggested corrections, and you are not charged for it.

**Why is a field `null`?**
Because Clutch does not show a value for it. Check `_provenance` for the reason: `absent` (not published), `undisclosed` (explicitly withheld), or `unparseable` (present but unreadable). The scraper will not guess.

**Do I get the full service breakdown?**
Yes. Clutch visually collapses long lists behind a "+4 services" link; all of them are extracted, with percentages.

**Are sponsored listings duplicated?**
No. Promoted placements repeat agencies that also appear organically; results are deduplicated by profile URL.

**Can I set a spending cap?**
Two ways, and they work together. **Maximum agencies** in the input is your row ceiling — 500 rows is about $5.00. Apify's **maximum cost per run** is a hard currency cap, and the run reads it *before* it starts: it scrapes only as many agencies as your cap can cover, tells you that number in the log, and stops immediately without scraping anything if the cap will not cover even one row. You are never cut off mid-write, and never charged beyond the cap.

**How fast is it?**
500 agencies in 25–61 seconds across two measured runs. Directory pages carry 74–95 agencies each and take roughly 3.6 seconds apiece; the spread reflects Clutch's own response time.

# Actor input Schema

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

Directory pages (a category, optionally narrowed by country or city) or individual agency profile URLs. Open Clutch.co, filter to the agencies you want, and paste the address bar here. Directory URLs are checked against Clutch's sitemap before the run starts, so a mistyped category fails immediately with suggestions instead of running up a bill.

## `maxResults` (type: `integer`):

Stop after this many agencies. You are charged per agency returned, so this is your cost ceiling: 100 agencies is about $1.00, 500 about $5.00. Set 0 for no limit, but note a full category can exceed 35,000 agencies. Pagination is followed automatically until the limit is reached.

## `concurrency` (type: `integer`):

How many pages to fetch at once. 3 is the default and was measured over 300 consecutive requests with no failures and no slowdown. Lower it to 1 to be gentler on Clutch; raising it beyond 3 is untested and may reduce reliability.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://clutch.co/us/agencies/digital-marketing"
    }
  ],
  "maxResults": 100,
  "concurrency": 3
}
```

# Actor output Schema

## `agencies` (type: `string`):

One row per agency, with the Agencies view selected by default. Switch to All fields to see the \_provenance block.

## `runSummary` (type: `string`):

Items requested and returned, failures by class, and what the run was charged.

# 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://clutch.co/us/agencies/digital-marketing"
        }
    ],
    "maxResults": 100,
    "concurrency": 3
};

// Run the Actor and wait for it to finish
const run = await client.actor("marknetic/clutch-scraper").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://clutch.co/us/agencies/digital-marketing" }],
    "maxResults": 100,
    "concurrency": 3,
}

# Run the Actor and wait for it to finish
run = client.actor("marknetic/clutch-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).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://clutch.co/us/agencies/digital-marketing"
    }
  ],
  "maxResults": 100,
  "concurrency": 3
}' |
apify call marknetic/clutch-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=marknetic/clutch-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/OrP8wdDDWCPlvJqoi/builds/UsKLfTudpYsdKRRCN/openapi.json
