# Martindale Scraper \[$0.95/1K💰] | Attorney Leads & Ratings (`ahmed_jasarevic/martindale-scraper`) Actor

Scrape martindale.com attorney profiles in bulk and export name, firm, practice areas, address, phone, website, peer and client ratings as clean JSON. ~30 attorneys per request, no browser. Built for legal lead-gen and CRM enrichment.

- **URL**: https://apify.com/ahmed_jasarevic/martindale-scraper.md
- **Developed by:** [Ahmed Jasarevic](https://apify.com/ahmed_jasarevic) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.93 / 1,000 results

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

## Martindale Scraper — Attorney Directory & Legal Leads API

Extract attorney leads from **martindale.com** — name, firm, practice areas, address, phone, website and **peer/client ratings** — scraped from directory listing pages in bulk at roughly **30 attorneys per HTTP request**. Built for legal lead generation, B2B data vendors and CRM/intake enrichment teams.

### Main use cases

- **Legal lead generation** — build attorney/law-firm lead lists by city, state or practice area.
- **CRM and data enrichment** — append firm, phone, website, geo-coordinates and ratings to existing records.
- **Law firm data research** — map firms and their attorneys across a market for competitive or recruiting analysis.
- **Legal directory database** — assemble a structured attorney directory for a SaaS, marketplace or data product.
- **Compliance and credential checks** — collect peer ratings, client ratings and awards (e.g. AV Preeminent) at scale.

### How it works

Give the actor one or more **start URLs**:

- **Directory listings** — city pages (`/all-lawyers/{city}/{state}/`), practice×state pages (`/areas-of-law/{practice}-lawyers/{state}/`) and state indexes (`/by-location/{state}-lawyers/`). Each listing page embeds a JSON-LD `@graph` with ~30 `LegalService` items, so the actor reads the attorneys straight from it and follows pagination (`?page=N` / `rel="next"`, capped by *Max listing pages*) — roughly **50 attorneys in about 10 seconds**.
- **Profile pages** directly (`/attorney/{name-slug-id}/`) — scraped immediately, no listing crawl needed. These yield the extra fields listings do not carry: practice areas, job title, fax, awards, education, the peer/client rating split and geo-coordinates.

It is a **CheerioCrawler** (plain HTTP, **256 MB**) — no browser is needed because every target field is in the raw served HTML.

### Extract Attorney Leads For Legal Lead Generation

Attorneys coming from **listings** carry the fields present in the embedded JSON-LD: name, firm, firm URL, city/state, phone and card-level peer rating. Attorneys scraped from **profile pages** additionally fill `practiceAreas`, `jobTitle`, `fax`, `awards`, `alumni`, the peer/client rating split and `latitude`/`longitude`. If you need those extra fields, list the profile URLs directly as start URLs.

### Build A Legal Directory Database Without The Martindale API

Recon order was: embedded JSON → third-party search backend → internal JSON API → static HTML → browser.

- **Embedded JSON-LD blob (USED — primary).** Every profile page ships an `application/ld+json` block with `@type: LegalService` containing name, `legalName` (firm), full postal address, geo, `telephone`/`faxNumber`, `knowsAbout` (practice areas), `aggregateRating` (`ratingValue` + `reviewCount`), `award` (AV Preeminent), job title and education. Listing pages embed the same `@graph` (~30 items/page), with the fields inverted vs profile pages: `sameAs` carries the `/attorney/` profile URL while `url` carries the firm's website.
- **Third-party search backend (NONE FOUND).** The bundles were grepped for Algolia, Meilisearch, TypeSense, Elasticsearch, Coveo, Solr and AppSearch markers — none present.
- **Internal/XHR JSON API (NONE FOUND).** No `/api/`, `/graphql`, `/_next/data/` or mobile-app JSON endpoints appear in the served HTML.
- **Static HTML via CheerioCrawler (USED — supplementary).** Listing cards (`.card__content`) carry name, firm, location, `tel:` phone and card-level peer rating; the profile DOM (`.rating-item` / `.review-title` / `.review-line`) splits the peer vs client rating that the JSON blob only aggregates.
- **Playwright (NOT NEEDED).** Rejected — all target fields exist in raw served HTML, keeping compute at the 256 MB tier.

**Anti-bot (Cloudflare).** Verified access matrix (Oct 2026 smoke tests):

- Direct connection (Apify datacenter IP): **HTTP 403, blocked**.
- Apify Proxy, RESIDENTIAL group, non-US exit: **HTTP 403, blocked**.
- Apify Proxy, **RESIDENTIAL group + country US: works** — full profile + listing pages return HTTP 200.

**Required production setting: enable "Use Apify Proxy", group `RESIDENTIAL`, country `US`** (this is the default prefill). The actor detects challenge/block pages and fails fast with a message pointing at this setting. It crawls politely: concurrency capped at 2 with ~600 ms between same-domain requests; failed requests back off exponentially (1 s, 2 s, 4 s) and rotate the pinned proxy session so the retry rides a fresh US residential exit IP.

### Input Parameters

| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
| `startUrls` | array (requestListSources) | **Yes** | city + profile prefills | Directory listing pages and/or individual `/attorney/{slug}/` profile URLs. |
| `maxAttorneys` | integer | No | `50` | Max profiles to save (max 5000). Free users capped at 10 regardless. |
| `maxListingPages` | integer | No | `10` | Max pagination pages to follow per listing (`?page=N`). |
| `maxRequestsPerCrawl` | integer | No | `200` | Safety cap on total HTTP requests (listings + profiles). |
| `proxy` | object (proxy) | No | `useApifyProxy: true`, RESIDENTIAL, US | Cloudflare blocks datacenter IPs — keep the US residential default. |

Free users get a **10-attorney preview**; paid runs go up to the configured caps.

### Output Fields

One dataset item per attorney (24 fields):

| Field | Type | Description |
|---|---|---|
| `profileUrl` | string | Canonical attorney profile URL. |
| `name` | string / null | Attorney full name. |
| `firm` | string / null | Law firm / employer name. |
| `firmUrl` | string / null | Link to the firm page on martindale.com. |
| `jobTitle` | string / null | Title at the firm (e.g. Associate Principal). |
| `practiceAreas` | array | Areas of practice (from structured data + page section). |
| `city` | string / null | Office city. |
| `state` | string / null | Office state code. |
| `streetAddress` | string / null | Office street address. |
| `postalCode` | string / null | Office ZIP code. |
| `phone` | string / null | Office phone number. |
| `fax` | string / null | Office fax number. |
| `website` | string / null | Attorney/firm external website. |
| `photoUrl` | string / null | Profile photo URL. |
| `peerRating` | string / null | Martindale-Hubbell peer review score (out of 5). |
| `peerReviewCount` | number / null | Number of peer reviews. |
| `clientRating` | string / null | Client review score (out of 5), when present. |
| `clientReviewCount` | number / null | Number of client reviews. |
| `overallRating` | string / null | Aggregate rating value. |
| `overallReviewCount` | number / null | Aggregate review count. |
| `awards` | array | Ratings/awards (e.g. AV Preeminent). |
| `alumni` | array | Law schools / education. |
| `latitude` | string / null | Office latitude. |
| `longitude` | string / null | Office longitude. |

### Example Input And Output

**Input**

```json
{
  "startUrls": [
    { "url": "https://www.martindale.com/all-lawyers/abilene/texas/" },
    { "url": "https://www.martindale.com/attorney/a-gabe-sybesma-3146937/" }
  ],
  "maxAttorneys": 50,
  "maxListingPages": 10,
  "proxy": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"], "apifyProxyCountry": "US" }
}
```

**Output (one dataset row)**

```json
{
  "profileUrl": "https://www.martindale.com/attorney/a-gabe-sybesma-3146937/",
  "name": "A. Gabe Sybesma",
  "firm": "Sybesma Legal, PLLC",
  "firmUrl": "https://www.martindale.com/organization/sybesma-legal-pllc-..../",
  "jobTitle": "Member",
  "practiceAreas": ["Business Law", "Real Estate"],
  "city": "Abilene",
  "state": "TX",
  "streetAddress": "123 Main St",
  "postalCode": "79601",
  "phone": "(325) 555-0100",
  "fax": "(325) 555-0101",
  "website": "https://example-firm.com",
  "photoUrl": "https://www.martindale.com/....jpg",
  "peerRating": "4.8",
  "peerReviewCount": 12,
  "clientRating": "5.0",
  "clientReviewCount": 4,
  "overallRating": "4.8",
  "overallReviewCount": 16,
  "awards": ["AV Preeminent"],
  "alumni": ["Texas Tech University School of Law"],
  "latitude": "32.4487",
  "longitude": "-99.7331"
}
```

### Integrations & Automation

Every Apify actor is reachable through the **Apify API**, and this one plugs into **webhooks**, **Zapier** and **Make** so new attorneys can flow into a CRM, Google Sheet or Airtable enrichments queue. For market coverage, run it on a **schedule** — a weekly pass over your target cities or practice areas keeps a legal-directory database fresh, and recurring runs also improve the actor's Store recommendation signals.

### Related Actors

- [Avvo-Scraper](https://apify.com/shahidirfan/Avvo-Scraper) — attorney listings from Avvo, the other major US legal directory.
- [superlawyers-scraper](https://apify.com/jungle_synthesizer/superlawyers-scraper) — Super Lawyers directory listings.
- [superlawyers-scraper](https://apify.com/ahmed_jasarevic/superlawyers-scraper) — own actor, Super Lawyers directory listings.
- [avvo-lawyers-scraper](https://apify.com/scrapers_lat/avvo-lawyers-scraper) — additional Avvo attorney coverage.
- [superlawyers-scraper](https://apify.com/chimerical_quicklime/superlawyers-scraper) — additional Super Lawyers coverage.

### FAQ

#### Does Martindale have an API?

Martindale does not publish an open API for its public directory. This actor reads the embedded JSON-LD that Martindale itself publishes on listing and profile pages, so you get structured attorney data without an API key.

#### What are the alternatives to this actor / this data source?

For US legal-directory data you can combine this with Avvo and Super Lawyers scrapers. Martindale-Hubbell is the oldest US legal directory and its peer-rating data is distinctive, so it complements the other directories rather than duplicating them.

#### How do I build a legal leads list by city or practice area?

Pass a city page (`/all-lawyers/{city}/{state}/`) or a practice×state page (`/areas-of-law/{practice}-lawyers/{state}/`) as a start URL and set `maxAttorneys`. The actor paginates the listing automatically and returns each attorney's name, firm, phone and rating.

#### How do I get practice areas, job title, fax and awards?

Those fields live on individual profile pages. List the `/attorney/{slug}/` URLs directly as start URLs and the actor fills `practiceAreas`, `jobTitle`, `fax`, `awards`, `alumni` and the peer/client rating split.

#### Why does it need a US residential proxy?

Martindale is Cloudflare-protected and blocks datacenter IPs. Testing showed direct datacenter IPs and non-US residential exits both return HTTP 403, while **residential + country US** returns HTTP 200. That setting is the default prefill and is required for a successful run.

#### How fast is a run?

Listing pages yield about **30 attorneys per request** — roughly 50 attorneys in about 10 seconds — and profile pages add the extra fields. Concurrency is capped at 2 with ~600 ms between same-domain requests to stay polite.

### SEO Keywords

martindale, martindale hubbell, martindale scraper, attorney directory, lawyer directory, legal leads, lawyer leads, attorney data api, legal lead generation, law firm data enrichment, attorney peer rating, avvo scraper, super lawyers scraper, us attorney data, legal directory database

### For AI Agents & LLM Apps

Purpose: return one structured attorney row per martindale.com profile, from a listing or profile start URL.

Minimal working input:

```json
{ "startUrls": [{ "url": "https://www.martindale.com/all-lawyers/abilene/texas/" }], "maxAttorneys": 50 }
```

Variant (profile URLs for the full field set):

```json
{ "startUrls": [{ "url": "https://www.martindale.com/attorney/a-gabe-sybesma-3146937/" }], "maxAttorneys": 50 }
```

Output fields: `profileUrl, name, firm, firmUrl, jobTitle, practiceAreas, city, state, streetAddress, postalCode, phone, fax, website, photoUrl, peerRating, peerReviewCount, clientRating, clientReviewCount, overallRating, overallReviewCount, awards, alumni, latitude, longitude`.

Behaviors an agent should know:

- **`startUrls` is required** (no default keyword mode).
- If `maxAttorneys` is left unset it defaults to **50**; free-plan runs are capped at **10** regardless.
- A proxy is **required in practice**: keep the default US residential proxy — datacenter and non-US residential exits return HTTP 403.
- Attorneys found via **listing** pages have profile-only fields (`practiceAreas`, `jobTitle`, `fax`, `awards`, `alumni`, rating split, geo) as `null`; pass their profile URLs directly to fill them.
- Profiles without reviews return `null` rating/count fields — not an error.

Billing: pay-per-event — **$0.00095 per result** ($0.95 / 1,000 results) plus a $0.00005 actor-start event.

### Legal & Compliance Disclaimer

This actor is an independent tool and is **not affiliated with, endorsed by, or sponsored by Martindale or Martindale-Hubbell**. It accesses only publicly available martindale.com pages; it does not log in, bypass authentication, or solve CAPTCHAs on anyone's behalf. Users are responsible for ensuring their use complies with Martindale's Terms of Service and with applicable data-protection law. Output contains professional contact data (names, firm phone numbers, addresses); where personal contact data is present it must not be used for unsolicited commercial outreach in violation of applicable law (e.g. CAN-SPAM, TCPA).

# Actor input Schema

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

Directory listing pages (e.g. /all-lawyers/{city}/{state}/, /areas-of-law/{practice}-lawyers/{state}/, /by-location/...) and/or individual attorney profile pages (/attorney/{slug}/) to scrape.

## `maxAttorneys` (type: `integer`):

Maximum number of attorney profiles to save. Free users are capped at 10 regardless of this setting.

## `maxListingPages` (type: `integer`):

Maximum pagination pages to follow per directory listing (?page=N).

## `maxRequestsPerCrawl` (type: `integer`):

Safety cap on total HTTP requests (listings + profiles).

## `proxy` (type: `object`):

martindale.com is Cloudflare-protected and blocks datacenter IPs. Keep the default US residential proxy, or supply your own residential proxy URLs.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.martindale.com/all-lawyers/abilene/texas/"
    },
    {
      "url": "https://www.martindale.com/attorney/a-gabe-sybesma-3146937/"
    }
  ],
  "maxAttorneys": 50,
  "maxListingPages": 10,
  "maxRequestsPerCrawl": 200,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}
```

# 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.martindale.com/all-lawyers/abilene/texas/"
        },
        {
            "url": "https://www.martindale.com/attorney/a-gabe-sybesma-3146937/"
        }
    ],
    "proxy": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "US"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("ahmed_jasarevic/martindale-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.martindale.com/all-lawyers/abilene/texas/" },
        { "url": "https://www.martindale.com/attorney/a-gabe-sybesma-3146937/" },
    ],
    "proxy": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "US",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("ahmed_jasarevic/martindale-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.martindale.com/all-lawyers/abilene/texas/"
    },
    {
      "url": "https://www.martindale.com/attorney/a-gabe-sybesma-3146937/"
    }
  ],
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}' |
apify call ahmed_jasarevic/martindale-scraper --silent --output-dataset

```

## MCP server setup

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