# GoodFirms Scraper — B2B Agencies, Ratings & Reviews (`memo23/goodfirms-scraper`) Actor

Scrape GoodFirms B2B company directories into JSON for lead lists: ratings, review counts, hourly rates, team size, every office, website, emails, phones, services, and reviews. Paste a directory or company URL, or pick a category shortcut. One dataset row per company saved.

- **URL**: https://apify.com/memo23/goodfirms-scraper.md
- **Developed by:** [Muhamed Didovic](https://apify.com/memo23) (community)
- **Categories:** Lead generation, Automation
- **Stats:** 3 total users, 2 monthly users, 83.3% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.50 / 1,000 companies

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## GoodFirms Scraper — B2B agencies, ratings, and reviews

Pull a GoodFirms directory into a dataset you can filter: company name, rating, review count, hourly rate, team size, website, email, phone, every office, services, and the reviews on the profile.

Paste a directory URL, a `/company/{slug}` profile, or leave the URL list empty and pick a category. Each saved row is one company.

![How the GoodFirms Scraper works](https://raw.githubusercontent.com/muhamed-didovic/muhamed-didovic.github.io/main/assets/how-it-works-goodfirms.png)

### Why use this scraper?

- Directory cards and full profiles in one run: rating, rate, employees, website, email, phone, offices, services, social links, and reviews.
- Real GoodFirms paths. Category shortcuts were checked against the live sitemap, including web development at `/companies/web-development-agency` rather than a guessed URL that 404s.
- Any directory URL works, not only the eight shortcuts: country, city, language, and framework listings included.
- One dataset row per company. Reviews stay on that row, so you are not billed twice for the same firm.
- Direct request first, proxy only if that request fails.

### Overview

GoodFirms (goodfirms.co, published by Alphazero Technologies Inc.) is a B2B ratings directory of IT service firms, software companies, and agencies. This actor turns public directory pages and company profiles into JSON for lead lists, vendor shortlists, and rate benchmarking.

A row is one company. Turning profiles off keeps the directory card only (name, rating, hourly rate, one office, one featured review) and skips the extra page fetch.

### Supported inputs

**Directory URLs** — paginated with `?page=2`, `?page=3`, and so on:

- `https://www.goodfirms.co/directory/languages/top-software-development-companies`
- `https://www.goodfirms.co/directory/country/top-software-development-companies/us`
- `https://www.goodfirms.co/companies/web-development-agency`
- `https://www.goodfirms.co/directory/platforms/top-web-design-companies`
- `https://www.goodfirms.co/seo-agencies`
- `https://www.goodfirms.co/artificial-intelligence`

**Company profiles** — scraped directly, no directory crawl:

- `https://www.goodfirms.co/company/instinctools`

**Category shortcut** — used only when Start URLs is empty: `software-development`, `web-development`, `web-design`, `digital-marketing`, `mobile-app-development`, `seo`, `artificial-intelligence`, `devops`.

**Not supported:** GoodFirms login, GoodFirms Pro, or any page that is not a public directory or `/company/{slug}` profile. A run that finds no companies fails instead of inventing rows.

### Use cases

| Who | What they get |
|---|---|
| Outbound teams | Agency lists with website, email, phone, and team size |
| Procurement | Shortlists with rating, review count, hourly rate, and services |
| Research | Multi-office footprints and category rankings |
| Agencies | Competitor rate and headcount bands by country |
| RevOps | A flat company table to join onto a CRM |

### How it works

1. You pass directory URLs, company URLs, or a category shortcut.
2. Directory pages are fetched and paginated until `maxPages`, `maxItems`, or a page with no new companies.
3. Each card is read from the page's public JSON-LD.
4. With profiles on, each `/company/{slug}` page adds website, emails, phones, employee band, founding year, every office, services, social links, and reviews.
5. One company row is written to the dataset.

### Input configuration

| Field | Default | What it does |
|---|---|---|
| `startUrls` | software-development directory | Directory or company URLs. When any URL is set, category and country are ignored. |
| `category` | `software-development` | Shortcut used only when Start URLs is empty. |
| `country` | empty | Location slug for categories that have one. `software-development` uses `us`, `gb`, `de`. `web-development`, `artificial-intelligence`, and `devops` append the slug GoodFirms uses on that path. Other shortcuts ignore it. |
| `maxItems` | `100` | Stop after this many companies. Free plans are capped at 100. |
| `maxPages` | `5` | Directory pages per start URL. |
| `includeProfileDetails` | `true` | Open each profile. Off = directory card only. |
| `maxReviewsPerCompany` | `5` | Reviews kept on the row. Rating and review count still reflect the full GoodFirms total. |
| `maxConcurrency` | `8` | Profiles fetched at once. |
| `proxyConfiguration` | Apify Proxy on | Used only after a direct request fails. |

**US software companies, 50 rows:**

```json
{
  "startUrls": [
    "https://www.goodfirms.co/directory/country/top-software-development-companies/us"
  ],
  "maxItems": 50,
  "maxPages": 2,
  "includeProfileDetails": true
}
```

**One profile:**

```json
{
  "startUrls": ["https://www.goodfirms.co/company/instinctools"],
  "includeProfileDetails": true
}
```

**Category shortcut, no URL list** — clear Start URLs in the form, or pass an empty array:

```json
{
  "startUrls": [],
  "category": "web-development",
  "country": "gb",
  "maxItems": 25
}
```

### Output overview

Each item is one company. Contact fields, offices, services, and reviews come from the profile page. Rank (`position`) and the directory URL come from the listing that found the company. A direct profile URL leaves `position` and `directoryUrl` empty.

### Output samples

Directory run, profiles on, `maxReviewsPerCompany` 2. Values from a live profile fetched on 2026-09-25. Review text is shortened here; the dataset keeps the full text up to the review cap.

```json
{
  "companyId": "instinctools",
  "name": "Instinctools",
  "profileUrl": "https://www.goodfirms.co/company/instinctools",
  "website": "https://www.instinctools.com/",
  "domain": "instinctools.com",
  "rating": 4.9,
  "reviewCount": 27,
  "hourlyRate": "$25 - $49",
  "employees": "250 - 999",
  "foundedYear": 2000,
  "phone": "+12028214280",
  "email": "contact@instinctools.com",
  "city": "Potomac",
  "region": "Maryland",
  "country": "us",
  "offices": [
    { "city": "Potomac", "region": "Maryland", "country": "us" },
    { "city": "Stuttgart", "country": "de" },
    { "city": "Warsaw", "country": "pl" },
    { "city": "Mississauga", "country": "ca" }
  ],
  "services": ["Software Development", "Web Development", "Artificial Intelligence"],
  "socialLinks": ["https://www.linkedin.com/company/instinctools"],
  "reviews": [
    {
      "author": "Sally Maddah",
      "rating": 5,
      "publishedAt": "2026-06-03T13:18:35Z",
      "title": "Instinctools was a highly collaborative partner that successfully delivered a scalable health platform while adapting seamlessly to evolving project needs."
    }
  ],
  "position": 1,
  "directoryUrl": "https://www.goodfirms.co/directory/languages/top-software-development-companies",
  "source": "goodfirms"
}
```

### Key output fields

**Identity** — `companyId`, `name`, `profileUrl`, `logoUrl`, `description`, `position`, `directoryUrl`

**Commercial** — `rating`, `reviewCount`, `hourlyRate`, `employees`, `foundedYear`, `services`

**Contact** — `website`, `domain`, `email`, `emails`, `phone`, `phones`, `socialLinks`

**Location** — `streetAddress`, `city`, `region`, `postalCode`, `country`, plus `offices` for every address on the profile

**Reviews** — `reviews` (`author`, `rating`, `publishedAt`, `title`, `text`), capped by `maxReviewsPerCompany`

### FAQ

**Does a URL list override the category?**
Yes. If `startUrls` has any URL, `category` and `country` are ignored.

**Why did country do nothing?**
`web-design`, `digital-marketing`, `mobile-app-development`, and `seo` have no single country pattern on GoodFirms. Paste the location URL in Start URLs. For `artificial-intelligence` and `devops`, use the slug from the live path (`usa`, `uk`, `india`), not only a two-letter code.

**Are empty emails a bug?**
No. The email is whatever the public profile lists. Some companies publish a phone and website and no email.

**Does this scrape every review ever written?**
It keeps up to `maxReviewsPerCompany` reviews from the profile JSON-LD. `reviewCount` is still the full total GoodFirms publishes.

**What is not included?**
No login-only fields, no GoodFirms Pro analytics, and no separate software-product catalog unless that page is itself a company directory with company cards.

### Support

Open an issue on this actor's Issues tab for a bug or a missing field. For a custom field list or a scheduled export, email **muhamed.didovic@gmail.com**.

### Additional services

Need a private actor, extra fields, or a feed into your warehouse? Custom builds are available at **muhamed.didovic@gmail.com**.

### Explore more scrapers

- [**Clutch Scraper**](https://apify.com/memo23/apify-clutch-cheerio) — B2B agency profiles and reviews from Clutch
- [**G2 Scraper**](https://apify.com/memo23/g2-scraper) — software product reviews from G2
- [**Capterra Scraper**](https://apify.com/memo23/capterra-scraper) — software listings and reviews from Capterra

Full list at [apify.com/memo23](https://apify.com/memo23).

### 🤖 For AI Agents & LLM Apps

Compact reference for AI agents calling this actor via the [Apify MCP server](https://mcp.apify.com) or the Apify API (actor: `memo23/goodfirms-scraper`).

**Purpose:** one dataset row per GoodFirms company, with profile contacts and reviews, from a directory URL or a `/company/{slug}` URL.

**Minimal input:**

```json
{
  "startUrls": ["https://www.goodfirms.co/directory/languages/top-software-development-companies"],
  "maxItems": 20,
  "maxPages": 1,
  "includeProfileDetails": true
}
```

**Output:** one row per company — `companyId`, `name`, `profileUrl`, `website`, `domain`, `logoUrl`, `description`, `rating`, `reviewCount`, `hourlyRate`, `employees`, `foundedYear`, `phone`, `phones`, `email`, `emails`, `streetAddress`, `city`, `region`, `postalCode`, `country`, `offices` `{streetAddress, city, region, postalCode, country}`, `services`, `socialLinks`, `reviews` `{author, rating, publishedAt, title, text}`, `position`, `directoryUrl`, `source`, `scrapedAt`.

**Behaviors an agent should know:**

- Always set `maxItems`. An empty cap still defaults to 100, and a large directory has hundreds of pages.
- Non-empty `startUrls` overrides `category` and `country`.
- `includeProfileDetails: false` skips website, email, employee band, extra offices, services, and full reviews.
- Billing is one `apify-default-dataset-item` per company saved, plus `apify-actor-start` per GB of memory. Reviews are not a separate charge.
- A run with zero companies fails. Do not retry the same bad URL in a loop.

### ⚠️ Disclaimer

This Actor is an independent tool and is not affiliated with, endorsed by, or sponsored by Alphazero Technologies Inc. (GoodFirms) or any of its subsidiaries. All trademarks mentioned are the property of their respective owners.

The scraper accesses only publicly available GoodFirms directory and company profile pages — no authenticated endpoints, paid features, or content behind the goodfirms.co login wall. Users are responsible for ensuring their use complies with goodfirms.co's Terms of Service, applicable data-protection law (GDPR, CCPA, etc.), and any contractual obligations of their own organization.

### SEO Keywords

GoodFirms scraper, GoodFirms API, GoodFirms companies, GoodFirms agency directory, B2B agency lead list, IT services companies scraper, software development companies list, web development agencies, digital marketing agencies data, GoodFirms ratings export, GoodFirms hourly rates, GoodFirms reviews export, agency email list, vendor shortlist data, GoodFirms country directory

# Actor input Schema

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

GoodFirms URLs to crawl. Directory pages (for example https://www.goodfirms.co/directory/languages/top-software-development-companies or https://www.goodfirms.co/companies/web-development-agency) are paginated. Company profiles (https://www.goodfirms.co/company/instinctools) are scraped directly. Leave empty to use Category.

## `category` (type: `string`):

Used only when Start URLs is empty. Paths were checked against the live GoodFirms sitemap: software-development, web-development, web-design, digital-marketing, mobile-app-development, seo, artificial-intelligence, devops. Example: web-development.

## `country` (type: `string`):

Used only when Start URLs is empty. software-development takes a short slug such as us, gb, de, in (builds /directory/country/top-software-development-companies/us). web-development, artificial-intelligence, and devops append the slug GoodFirms uses on that path (us or usa, uk, india). web-design, digital-marketing, mobile-app-development, and seo ignore this field — paste a location URL in Start URLs instead. Leave empty for the global list.

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

Stop after this many companies are saved. Default 100. Free Apify plans are capped at 100. Example: 50.

## `maxPages` (type: `integer`):

How many paginated directory pages to open per Start URL (?page=1, 2, …). Default 5. A page with no new companies ends that directory early. Example: 3.

## `includeProfileDetails` (type: `boolean`):

When on, each company profile is fetched for website, domain, founding year, employee band, emails, phones, every office, services, social links, and reviews. When off, only the directory card is saved (rating, hourly rate, one office, one featured review) and the run is faster.

## `maxReviewsPerCompany` (type: `integer`):

How many review objects to keep on each company row. Default 5. The review count and rating always come from the full GoodFirms total, not from this cap. Example: 10.

## `maxConcurrency` (type: `integer`):

How many company profiles to fetch at once. Default 8. Lower it if GoodFirms starts returning errors.

## `proxyConfiguration` (type: `object`):

Each URL is tried directly first. If that fails, the proxy selected here is used for the retry. Datacenter is enough for most directory pages. Switch on Residential if a run comes back empty.

## Actor input object example

```json
{
  "startUrls": [
    "https://www.goodfirms.co/directory/languages/top-software-development-companies"
  ],
  "category": "software-development",
  "maxItems": 100,
  "maxPages": 5,
  "includeProfileDetails": true,
  "maxReviewsPerCompany": 5,
  "maxConcurrency": 8,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `companies` (type: `string`):

GoodFirms companies with ratings, rates, offices, contacts, and reviews.

# 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": [
        "https://www.goodfirms.co/directory/languages/top-software-development-companies"
    ],
    "country": ""
};

// Run the Actor and wait for it to finish
const run = await client.actor("memo23/goodfirms-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": ["https://www.goodfirms.co/directory/languages/top-software-development-companies"],
    "country": "",
}

# Run the Actor and wait for it to finish
run = client.actor("memo23/goodfirms-scraper").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": [
    "https://www.goodfirms.co/directory/languages/top-software-development-companies"
  ],
  "country": ""
}' |
apify call memo23/goodfirms-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,memo23/goodfirms-scraper"
        }
    }
}
```

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/GWkohdtpnMNev2rEe/builds/znLSuFT6Gz15pddrc/openapi.json
