# GoodFirms Scraper — Agencies, Rates & Reviews (`tortuga/goodfirms-scraper`) Actor

Scrape GoodFirms.co agency and software company directories and profiles: services, hourly rate, team size, founding year, location, rating, reviews and website. Built for B2B lead lists.

- **URL**: https://apify.com/tortuga/goodfirms-scraper.md
- **Developed by:** [Trevor Ortega](https://apify.com/tortuga) (community)
- **Categories:** Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 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

Scrape GoodFirms.co directory listings and company profiles: services, hourly rate, team size, founding year, location, rating, review count, focus areas and website. Ideal for B2B agency and software vendor lead lists.

Give it any GoodFirms.co directory page (for example `https://www.goodfirms.co/directory/languages/top-software-development-companies`, `https://www.goodfirms.co/companies/web-development-agency`, a country or industry directory, or a filtered URL copied from the site) or a company profile URL. The Actor walks the directory page by page (48 companies each) and saves one clean record per company. Optionally it opens every profile for the full breakdown (service focus with percentages, industry and client focus, key clients, portfolio, typical project cost, all offices, business phone and email, social links, verification) and nests the client reviews.

No login, no browser. Export as JSON, CSV or Excel, or pull the dataset through the API into Google Sheets, a CRM or an LLM pipeline. You pay only for the companies you get.

See also: [Clutch Scraper](https://apify.com/tortuga/clutch-scraper) - the same fields and pricing for Clutch.co, so you can merge both directories into one lead list.

### What data does GoodFirms Scraper extract?

Every company record (directory pages, charged as `result`):

| Field | Description |
|---|---|
| `name`, `slug`, `id` | Company name, GoodFirms slug and numeric GoodFirms company id |
| `profileUrl` | GoodFirms profile URL |
| `websiteUrl` | The company's own website as linked by GoodFirms (utm tags removed, never visited) |
| `logoUrl` | Logo image |
| `rating`, `reviewCount` | GoodFirms rating (1-5) and number of reviews |
| `hourlyRate`, `hourlyRateMin`, `hourlyRateMax` | Published hourly rate band ("$25 - $49/hr") plus parsed numbers |
| `employees`, `employeesMin`, `employeesMax` | Team size band ("50 - 249") plus parsed numbers |
| `foundedYear` | Founding year |
| `location` | `{text, city, region, country, countryCode, street, postalCode}` of the headquarters |
| `phone` | Business phone number GoodFirms publishes for the company |
| `isVerified`, `verificationLevel`, `badges` | GoodFirms verification (e.g. `pro` for "Pro Verified") and badges |
| `summary` | Short company description from the directory card |
| `aiTools`, `whyChooseUs` | AI tools the company says it uses, and its "Why choose us" bullets |
| `topReview` | Highlighted review on the card: `{text, reviewerName, reviewerRole, rating, date}` (`reviewerName` comes from the card's public structured data) |
| `isSponsored`, `listingType` | `sponsored` / `organic` (`profile` when started from a profile URL) |
| `rank`, `page`, `positionOnPage` | Position in the directory |
| `serviceLines` | Service focus `[{name, percent}]` (filled when the profile is opened; directory cards do not show it) |
| `sourceUrl`, `scrapedAt` | Where and when the record was scraped |

With **Open each company profile** (charged as `detail`, one extra request per company):

| Field | Description |
|---|---|
| `description`, `tagline` | Full company description (and tagline when it differs from the name) |
| `serviceLines`, `industries`, `clients` | Service focus, industry focus and client focus (small / medium / large business), each `[{name, percent}]` |
| `keyClients`, `clientsCount` | Named key clients |
| `portfolioCount`, `portfolio` | Number of portfolio projects and `[{title, industry, budget, timeline, imageUrl}]` |
| `projectIndustries`, `projectCosts`, `projectTimelines` | Distributions of past projects by industry, budget and duration |
| `mostCommonProjectSize`, `typicalProjectTimeline` | Most common project budget and duration |
| `locations` | Every office: `{isHeadquarters, street, city, region, country, countryCode, postalCode, phone}` |
| `email`, `emails` | Company mailbox(es) published on the profile (generic business addresses such as info@ / sales@ only) |
| `socialLinks` | `{linkedin, facebook, twitter, instagram, ...}` |
| `ratingBreakdown`, `reviewSummary`, `topMentions` | Quality / schedule / communication / overall averages, GoodFirms' review summary, most mentioned themes |
| `aiTools` | `[{name, purpose}]` |
| `partnerPrograms`, `certifications` | e.g. "AWS Partner", "Great Place To Work" |
| `verifiedAt`, `legalEntity` | Verification date and registered entity (name, type, incorporation, location, country) |

With **Include reviews** each company additionally carries `reviews` (up to **Max reviews per company**):

| Field | Description |
|---|---|
| `id`, `title`, `text` | Review id, headline and full review text |
| `rating`, `ratings` | Overall score and `{quality, communication, schedule}` |
| `projectName`, `summary` | Project name and project summary |
| `liked`, `improve` | What the client liked and what could be improved |
| `services` | Services delivered in the project |
| `projectSize`, `projectCostMin`, `projectCostMax`, `projectStatus` | Budget band ("$10,001 to $50,000") and status ("Completed") |
| `reviewerName` | Reviewer's display name as GoodFirms shows it (often first name + last name or initial; "Anonymous" when the reviewer chose that); null if missing |
| `reviewerRole`, `reviewerCompany`, `reviewerIndustry` | Who hired the company (role, client company, industry) |
| `isVerified`, `date` | Verified flag and ISO date |

GoodFirms has no reviewer profile pages, reviewer ids or reviewer LinkedIn links, so only the shown name is available. The executive-interview person and personal mailboxes are deliberately not collected.

### How to use GoodFirms Scraper

1. Paste one or more GoodFirms.co URLs into **Start URLs**: a service directory (`https://www.goodfirms.co/companies/web-development-agency`), a country directory (`https://www.goodfirms.co/directory/country/top-software-development-companies/us`), an industry directory (`https://www.goodfirms.co/top-software-development-companies/healthcare`), a filtered URL copied from your browser, or a company profile (`https://www.goodfirms.co/company/empat`).
2. Set **Max companies** to cap the run and the cost.
3. Turn on **Open each company profile** and/or **Include reviews** if you need the deep data.
4. Click **Start**. Results appear in the **Dataset** tab; export from there or use the API.

### Input example

```json
{
  "startUrls": [
    { "url": "https://www.goodfirms.co/directory/languages/top-software-development-companies" },
    { "url": "https://www.goodfirms.co/companies/web-development-agency?employees=50+-+249" }
  ],
  "maxItems": 200,
  "includeProfileDetails": true,
  "includeReviews": true,
  "maxReviewsPerCompany": 20
}
```

### Output example

```json
{
  "type": "company",
  "id": "105165",
  "slug": "empat",
  "name": "Empat",
  "profileUrl": "https://www.goodfirms.co/company/empat",
  "websiteUrl": "https://empat.tech/",
  "rating": 5.0,
  "reviewCount": 79,
  "hourlyRate": "$25 - $49/hr",
  "hourlyRateMin": 25,
  "hourlyRateMax": 49,
  "employees": "50 - 249",
  "foundedYear": 2013,
  "location": { "text": "Miami, United States", "city": "Miami", "region": "Florida", "country": "United States", "countryCode": "US", "street": "360 Northwest 27th Street", "postalCode": "33127" },
  "phone": "+16282010783",
  "email": "hello@empat.tech",
  "serviceLines": [
    { "name": "Mobile App Development", "percent": 30 },
    { "name": "Web Development", "percent": 30 },
    { "name": "Software Development", "percent": 20 },
    { "name": "Artificial Intelligence", "percent": 20 }
  ],
  "industries": [{ "name": "Healthcare", "percent": 30 }, { "name": "Social", "percent": 30 }],
  "clients": [{ "name": "Small Business", "percent": 40 }, { "name": "Medium Business", "percent": 50 }, { "name": "Large Business", "percent": 10 }],
  "keyClients": ["Porsche", "Panasonic", "Transparency International"],
  "portfolioCount": 12,
  "mostCommonProjectSize": "$50001 to $100000",
  "isVerified": true,
  "verificationLevel": "pro",
  "badges": ["Pro Verified"],
  "listingType": "organic",
  "rank": 3,
  "sourceUrl": "https://www.goodfirms.co/directory/languages/top-software-development-companies",
  "scrapedAt": "2026-10-05T12:00:00Z",
  "reviews": [
    {
      "id": "92867",
      "title": "Strong technical execution with thoughtful UX",
      "rating": 5.0,
      "ratings": { "quality": 5.0, "communication": 5.0, "schedule": 5.0 },
      "projectName": "Web Redesign, Development & E-Commerce Development for Mountain Resort",
      "services": ["Web Development", "App Designing (UI/UX)", "E-commerce Development"],
      "projectSize": "$10,001 to $50,000",
      "projectCostMin": 10001,
      "projectCostMax": 50000,
      "projectStatus": "Completed",
      "reviewerName": "Butsiak Romana",
      "reviewerRole": "CMO",
      "reviewerCompany": "Mountain Resort",
      "reviewerIndustry": "Other Industries",
      "isVerified": true,
      "date": "2026-08-25"
    }
  ]
}
```

### How to scrape a GoodFirms category, country or filtered list

Any directory URL works: service categories (`/companies/web-development-agency`, `/directory/marketing-services/top-digital-marketing-companies`), language and platform directories (`/directory/languages/top-software-development-companies`), country directories (`/directory/country/top-software-development-companies/us`) and industry directories (`/top-software-development-companies/healthcare`). Apply the filters on goodfirms.co (team size, hourly rate, services, industry, client type) and paste the resulting URL; the Actor keeps the query string and follows the site's own pagination, so one run can export a full category. Set **Max companies** to stop early.

### How to get a company's website, phone and email from GoodFirms

`websiteUrl` comes straight from the directory card (GoodFirms links the vendor site directly; only `utm_*` tags are removed) and `phone` is the business number GoodFirms publishes. With **Open each company profile**, you also get every office with its phone and the company mailbox from the profile. Only generic business mailboxes (info@, sales@, hello@ and similar) are kept; addresses that look like a person's own mailbox are dropped.

### How to scrape GoodFirms reviews

Turn on **Include reviews** and set **Max reviews per company**. The profile page carries the first 15 reviews; each further 15 reviews cost one more page request. Reviews come nested inside the company record with sub-ratings, review text, project name and summary, what the client liked and what to improve, project cost and status, and the reviewer's display name, role, company and industry.

### Does it work without login or proxies?

Yes. GoodFirms pages are public and server-rendered, so no login and no browser are needed. Apify Proxy is on by default for reliability on long runs; you can turn it off for small runs. Profile pages are large, and datacenter proxies sometimes time out on them and get retried, which slows the run. If you see 403 errors or want faster profile runs, switch the proxy to the RESIDENTIAL group (it costs more proxy traffic).

### Pricing

Pay per company: a `result` event for every company saved ($0.004), plus a `detail` event ($0.014) for every company whose profile page was opened (nested reviews included, no separate review charge). So 1,000 companies from listings cost $4, and 1,000 full profiles with reviews cost $18. No start fee and no subscription; Apify's free plan is enough to try it.

### Integrations and API

Use the run in Zapier, Make, n8n, Google Sheets, or call it from Python/Node with the Apify client. See the **API** tab for ready-made snippets.

### Is it legal to scrape GoodFirms.co?

This Actor collects only data that GoodFirms.co shows publicly without a login (company profiles, rates, ratings, business contact details and published reviews). Reviews include the reviewer's display name as GoodFirms publishes it, which is personal data under GDPR and similar laws. The Actor does not collect the names of company executives or personal email addresses. You are responsible for how you use the data and for complying with GoodFirms' terms and applicable law, including GDPR/CCPA where relevant (for example, have a lawful basis before storing or contacting reviewers).

### Support

Found a bug or need a field added? Open an issue in the **Issues** tab; it is usually answered within a day.

# Actor input Schema

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

GoodFirms.co pages to scrape: directory pages (https://www.goodfirms.co/directory/languages/top-software-development-companies, https://www.goodfirms.co/companies/web-development-agency, https://www.goodfirms.co/top-software-development-companies/healthcare), filtered directory URLs copied from the site (?employees=…, ?rate=…, ?services=…, ?client_type=…), or company profile pages (https://www.goodfirms.co/company/empat). Directory pages are paginated automatically (48 companies per page).

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

Stop after this many companies (across all start URLs). Keeps cost predictable.

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

Fetch every company's profile page for the full picture: description, service focus with percentages, industry focus, client focus (small / medium / large business), key clients, portfolio count and projects, typical project cost and timeline, all office locations, company phone and email (business mailboxes only), social links, AI tools, partner programs, verification data and rating breakdown. One extra request per company (charged as a `detail` event).

## `includeReviews` (type: `boolean`):

Nest client reviews inside each company (rating and sub-ratings, title, review text, project name and summary, what they liked / what to improve, project cost, project status, services, reviewer display name as shown, reviewer role, client company and industry, date). Implies opening the profile; reviews are included in the `detail` price.

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

Upper limit of nested reviews per company when Include reviews is on. The profile page carries the first 15 reviews; every further 15 reviews cost one more page request.

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

Apify Proxy is recommended for reliability on long runs. If you see 403 errors, switch to the RESIDENTIAL group. Proxy usage is billed to your account.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.goodfirms.co/directory/languages/top-software-development-companies"
    }
  ],
  "maxItems": 100,
  "includeProfileDetails": false,
  "includeReviews": false,
  "maxReviewsPerCompany": 20,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `results` (type: `string`):

No description

# 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://www.goodfirms.co/directory/languages/top-software-development-companies"
        }
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

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

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

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,tortuga/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/rvU8nqyKAEK1xTozh/builds/X0gto4VaaxY6oIEsD/openapi.json
