# HubSpot Solutions Partner Directory and RevOps Agency Scraper (`groundplanes/hubspot-solutions-partner-agency-scraper`) Actor

Extract accredited HubSpot Solutions Partner agencies from the official HubSpot Ecosystem Marketplace Solutions Directory, with partner tier, rating and review count, certification count, catalog services, industries, budget range, office locations (city, state, country) and agency website URL.

- **URL**: https://apify.com/groundplanes/hubspot-solutions-partner-agency-scraper.md
- **Developed by:** [Groundplane Labs](https://apify.com/groundplanes) (community)
- **Categories:** Business
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.10 / 1,000 agencies

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

## HubSpot Solutions Partner Directory & RevOps Agency Scraper

Extract accredited HubSpot Solutions Partner agencies from the official [HubSpot Ecosystem Marketplace Solutions Directory](https://ecosystem.hubspot.com/marketplace/solutions), including partner tier (`Elite`, `Diamond`, `Platinum`, `Gold`), verified client rating, review count, HubSpot Academy certification count, catalog services provided, industries served, client budget range, office locations, and agency website URL.

### What it does

- Queries the official HubSpot Ecosystem Marketplace directory (`PersonalizationPublicRpc/search`) with server-side filters for **Partner Tier**, **Catalog Services**, and **Country**, reconciling delivered results against the directory's reported total.
- Filters candidate partner agencies by **Minimum Verified Reviews Count** and **Minimum Average Rating**.
- Enriches each qualifying agency via `MarketplaceListingDetailsRpc/getListingDetailsV3` at a polite rate limit ($\le 3\text{ requests/second}$) to extract full HubSpot Academy credentials, catalog services, industry specializations, minimum budget ranges, physical and remote office locations, and external website URLs.
- Emits `agencyPhone` and `agencyContactEmail` only when published directly on the HubSpot directory profile itself; never crawls external websites.

### Who it's for

- **B2B SaaS Channel & Ecosystem Teams** recruiting certified HubSpot implementation and RevOps agencies for co-selling and referral partnerships.
- **Enterprise Procurement & RevOps Buyers** benchmarking HubSpot Elite and Diamond implementation partners by service capability, verified review volume, and budget tier.
- **Market Researchers** analyzing regional distribution, certification density, and industry focus across the HubSpot Solutions Partner ecosystem.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `partnerTiers` | `string[]` | `["Elite", "Diamond", "Platinum", "Gold"]` | Partner tiers to include. Empty means all tiers. |
| `servicesOffered` | `string[]` | `["CRM Implementation", "Inbound Marketing", "Custom API Integrations", "RevOps"]` | HubSpot catalog service names. Empty means all services. |
| `countries` | `string[]` | `["United States", "United Kingdom", "Canada", "Australia"]` | Full country names or ISO-2 codes. Empty means all countries. |
| `minReviewsCount` | `integer` | `5` | Keep only agencies with at least this many verified client reviews. |
| `minRating` | `number` | `4.5` | Keep only agencies with at least this average client rating (0.0 to 5.0). |
| `maxItems` | `integer` | `1500` | Maximum agency profiles to return. |

Note on `RevOps`: HubSpot's live service catalog has no RevOps service. In this actor, `RevOps` maps to Revenue Hub Implementation OR Sales and Marketing Alignment.

Example input (with default spending protection `"maxTotalChargeUsd": 50` in run options). Note: the `maxItems` value of 150 in this example is non-default; the schema default is 1500:

```json
{
  "partnerTiers": ["Elite", "Diamond", "Platinum", "Gold"],
  "servicesOffered": [
    "CRM Implementation",
    "Inbound Marketing",
    "Custom API Integrations",
    "RevOps"
  ],
  "countries": [
    "United States",
    "United Kingdom",
    "Canada",
    "Australia"
  ],
  "minReviewsCount": 5,
  "minRating": 4.5,
  "maxItems": 150
}
```

Recommended Apify run options set a default maximum charge of `$50` (`"maxTotalChargeUsd": 50`).

### Output

| Field | Type | Description |
|---|---|---|
| `partnerId` | `string` | Unique HubSpot Solutions Partner profile ID |
| `agencyName` | `string` | Agency display name on the HubSpot Solutions Directory |
| `partnerTier` | `string\|null` | Partner tier (`Elite`, `Diamond`, `Platinum`, `Gold`) |
| `averageRating` | `number\|null` | Average client rating (`0.0` to `5.0`) |
| `reviewsCount` | `integer` | Count of verified client reviews |
| `certificationsCount` | `integer` | Count of HubSpot Academy certifications and accreditations |
| `servicesProvided` | `string[]` | Catalog services offered by the agency |
| `industriesServed` | `string[]` | Target industries served by the agency |
| `budgetMinimumRange` | `string\|null` | Supported client budget range (`$0 - 2,500`, `$2,500 - 5,000`, `$5,000+`, `Any budget`) |
| `officeLocations` | `string[]` | Office locations as city, state, country only. Street addresses and ZIP codes from the source payload are never emitted. |
| `agencyWebsiteUrl` | `string\|null` | External website URL of the agency |
| `agencyPhone` | `string\|null` | Published agency phone number on the HubSpot profile, if present |
| `agencyContactEmail` | `string\|null` | Published agency inquiry email on the HubSpot profile, if present |
| `hubspotDirectoryUrl` | `string` | Direct URL to the HubSpot Solutions Partner profile |

#### Sample output row (synthetic example)

```json
{
  "partnerId": "284910",
  "agencyName": "RevOps Growth Partners",
  "partnerTier": "Diamond",
  "averageRating": 4.9,
  "reviewsCount": 84,
  "certificationsCount": 42,
  "servicesProvided": [
    "CRM Implementation",
    "Custom API Integrations",
    "Programmable Automation"
  ],
  "industriesServed": [
    "Technology - software",
    "Banking/financial services",
    "Manufacturing"
  ],
  "budgetMinimumRange": "$5,000+",
  "officeLocations": [
    "Austin, TX, USA",
    "London, United Kingdom"
  ],
  "agencyWebsiteUrl": "https://www.revopsgrowth.example.com",
  "agencyPhone": null,
  "agencyContactEmail": null,
  "hubspotDirectoryUrl": "https://ecosystem.hubspot.com/marketplace/solutions/revops-growth"
}
```

### How pricing works

Pay per result. You only pay for rows returned.

### Data source and terms

- **Official Source:** [HubSpot Ecosystem Marketplace - Solutions Directory](https://ecosystem.hubspot.com/marketplace/solutions)
- **Terms of Use:** Subject to the [HubSpot Legal & Website Terms of Use](https://legal.hubspot.com/website-terms-of-use).
- **Update Frequency:** Partner tiers, verified client reviews, and Academy credentials update continuously on HubSpot's directory.
- This Actor is an independent tool and is not affiliated with or endorsed by HubSpot, Inc.

### Compliance

All business directory attributes are published on the public HubSpot Solutions Directory. Users are solely responsible for complying with the Telephone Consumer Protection Act (TCPA), the National Do-Not-Call Registry, the CAN-SPAM Act, GDPR, CCPA, and all applicable federal, state, and international privacy and outreach regulations before initiating any business communication. Not for consumer or individual targeting.

### Limits and FAQ

- **Rate limiting:** Requests are throttled to $\le 3\text{ requests/second}$ with automatic exponential-backoff retries to respect HubSpot's storefront gateway.
- **Phone and email availability:** Most HubSpot Solutions Partner profiles route inquiries through an embedded HubSpot form rather than displaying a raw phone number or email address; `agencyPhone` and `agencyContactEmail` are populated only when explicitly published on the directory profile itself and are `null` otherwise.
- Found a problem or need a field? Open an issue on the Issues tab; we respond quickly.

### Canary input (labeled)

```json
{
  "partnerTiers": ["Elite"],
  "servicesOffered": ["CRM Implementation"],
  "countries": ["United States"],
  "minReviewsCount": 50,
  "minRating": 4.8,
  "maxItems": 5
}
```

Expected: 5 rows, Complete status, in under 30 seconds at review time.

### Troubleshooting

- **Zero matches:** a healthy source with no agencies passing your filters ends as `Complete: 0 ... matched your filters`. Lower `minReviewsCount` or `minRating`, or widen tiers, services, or countries.
- **PARTIAL status:** the run hit a source problem (search, detail, schema, or value-format drift). The status states the counts and the reason. If this keeps happening, please open an issue on this actor's Issues tab and we'll fix it fast.
- **Caps:** hitting `maxItems` or the charge cap stops the run early and the status says so; raise the limit to get more rows.
- **Phone and email are usually null:** the HubSpot V3 detail payload reviewed on 2026-11-01 carries no phone or email keys at all, so both fields are null unless HubSpot starts publishing them on the directory profile itself. External websites are never crawled to fill them.

### Data path note

This actor uses the HubSpot Chirp gateway (`PersonalizationPublicRpc/search`). The Chirp gateway is an undocumented internal storefront RPC, not a published HubSpot API: it is publicly reachable without a key, but HubSpot can change or remove it silently and without notice, and that silent-change risk stands for as long as the actor relies on it. It was confirmed live at build and review time. It does not use a server-rendered initial-state payload: the directory HTML served on 2026-11-01 contains no `__NEXT_DATA__` payload (that name in the original draft was wrong) and no equivalent SSR partner payload, so the sentinel is pinned to the JSON gateway instead: the filter-config keys, the integer search `total`, the first-card shape (name, `reviewSummary.reviewCount`, `products[].partnerTier`, `slug`), and every V3 detail payload's core fields (`tier`, `companyName`, `services`, `credentials`, `urlSlug`), checked per row for presence and substance (non-empty text for tier, companyName and urlSlug; a list with substantive entries for services and credentials).

### Changelog

- **v0.2.5** - The credential entry substance test is now anchored to identifying content: a credential entry in object form counts only when one of the credential schema's identifying keys (name, title, internalName, id) holds a non-empty name or a positive identifier. An entry carrying only ancillary values (an icon URL, a description, a topic, a type, a grantee entity, or any unrelated key) is hollow: it no longer produces a certificationsCount under a Complete status; an all-hollow credentials list skips the row as before, and a hollow entry in a mixed list is dropped and stated as unparsed drift. Bare numeric credential entries now count only when positive, matching the identifier rule used for profile ids.
- **v0.2.4** - The per-row core detail check now tests the substance of the entries inside the services and credentials lists, not only the container type: a services entry counts only when it yields a non-empty service name, and a credential entry counts only when it carries a name or an identifier. A list whose entries are all hollow now skips the row, counts toward the 20 percent skip rule, and ends the run PARTIAL with the reason stated and the Issues CTA, instead of shipping fabricated enrichment (including a positive certificationsCount counted from empty credential objects) under a Complete status. In a mixed list the row is kept with only the contributing entries counted, and the hollow entries are stated as unparsed fields in the status line. A non-positive profileId now falls back to the search card listingId instead of being emitted as a negative partnerId.
- **v0.2.3** - The per-row core detail check now tests substance, not only key presence: tier, companyName and urlSlug must be non-empty text (whitespace alone does not count), services must be a list, and credentials must be a list. A detail payload whose core fields are present but empty or null now skips that row, counts toward the 20 percent skip rule, and ends the run PARTIAL with the reason stated and the Issues CTA, instead of shipping a row built from the search card alone under a Complete status. The search card fallbacks for tier, companyName and urlSlug now apply only when the detail value is absent, so an empty or null detail value can no longer be masked by the card.
- **v0.2.2** - H1 closed in full: a detail response whose listing is an empty object now counts as a detail failure instead of a successful enrichment; a per-row core sentinel now checks every hydrated profile (tier, companyName, services, credentials, urlSlug) with no truthiness guard, so an empty or truncated detail payload skips that row, counts toward the 20 percent skip rule, and ends the run PARTIAL with the reason stated and the Issues CTA; an absent credentials key is now treated the same as an unparseable credentials value and the row is skipped instead of shipping certificationsCount 0. The terminal over-20-percent failure message now names all three skip categories: detail fetch, schema validation, and enrichment skips.
- **v0.2.1** - Rows whose detail enrichment failed or whose credentials value was unparseable are now skipped and counted toward the 20 percent skip rule instead of being emitted with fabricated 0/empty enrichment fields; an unrecognized partner tier value is now emitted as null instead of being capitalized through; the dataset-count reconciliation now logs a warning and ends the run PARTIAL on a mismatch instead of never firing; README now discloses the RevOps service mapping, lists input defaults (the example `maxItems` 150 is non-default; the schema default is 1500), shows `agencyPhone` and `agencyContactEmail` as null in the sample row, and states that the Chirp gateway is an undocumented internal storefront RPC; fixture suite extended to the CODING_RULES section 7 minimums.
- **v0.2** - Reconstructed the empty .actor files (actor, input, dataset, output schemas) from the spec; retry standard unified to 6 tries with jittered backoff (computed backoff 2 to 32 s; the actual sleep is half to the full computed value, so the first retry can sleep 1 s), 120 s cap, and Retry-After honored; zero matches now end Complete: 0 instead of failing; value-format drift is counted and stated as unparsed fields, never silent; skipped rows over 20 percent fail loudly; office locations emit city, state, country only (street addresses scrubbed); sentinel expanded to first search card and first V3 detail payload.
- **v0.1** - Initial release with HubSpot Chirp storefront search, total reconciliation, V3 profile enrichment, and schema validation.

# Actor input Schema

## `countries` (type: `array`):

Full country names or ISO-2 codes, for example United States, United Kingdom, Canada, Australia. Empty means all countries.

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

Maximum agency profiles to return.

## `minRating` (type: `number`):

Keep only agencies with at least this average client rating (0.0 to 5.0).

## `minReviewsCount` (type: `integer`):

Keep only agencies with at least this many verified client reviews.

## `partnerTiers` (type: `array`):

Partner tiers to include: Elite, Diamond, Platinum, Gold. Empty means all tiers.

## `servicesOffered` (type: `array`):

HubSpot catalog service names, for example CRM Implementation, Inbound Marketing, Custom API Integrations, RevOps. Empty means all services.

## Actor input object example

```json
{
  "countries": [
    "United States",
    "United Kingdom",
    "Canada",
    "Australia"
  ],
  "maxItems": 1500,
  "minRating": 4.5,
  "minReviewsCount": 5,
  "partnerTiers": [
    "Elite",
    "Diamond",
    "Platinum",
    "Gold"
  ],
  "servicesOffered": [
    "CRM Implementation",
    "Inbound Marketing",
    "Custom API Integrations",
    "RevOps"
  ]
}
```

# 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 = {
    "countries": [
        "United States",
        "United Kingdom",
        "Canada",
        "Australia"
    ],
    "maxItems": 1500,
    "minRating": 4.5,
    "minReviewsCount": 5,
    "partnerTiers": [
        "Elite",
        "Diamond",
        "Platinum",
        "Gold"
    ],
    "servicesOffered": [
        "CRM Implementation",
        "Inbound Marketing",
        "Custom API Integrations",
        "RevOps"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("groundplanes/hubspot-solutions-partner-agency-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 = {
    "countries": [
        "United States",
        "United Kingdom",
        "Canada",
        "Australia",
    ],
    "maxItems": 1500,
    "minRating": 4.5,
    "minReviewsCount": 5,
    "partnerTiers": [
        "Elite",
        "Diamond",
        "Platinum",
        "Gold",
    ],
    "servicesOffered": [
        "CRM Implementation",
        "Inbound Marketing",
        "Custom API Integrations",
        "RevOps",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("groundplanes/hubspot-solutions-partner-agency-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 '{
  "countries": [
    "United States",
    "United Kingdom",
    "Canada",
    "Australia"
  ],
  "maxItems": 1500,
  "minRating": 4.5,
  "minReviewsCount": 5,
  "partnerTiers": [
    "Elite",
    "Diamond",
    "Platinum",
    "Gold"
  ],
  "servicesOffered": [
    "CRM Implementation",
    "Inbound Marketing",
    "Custom API Integrations",
    "RevOps"
  ]
}' |
apify call groundplanes/hubspot-solutions-partner-agency-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,groundplanes/hubspot-solutions-partner-agency-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/eRzUyIAUlhV04whUa/builds/fe5ogVkPgI7COQ9BH/openapi.json
