# Martindale-Hubbell Attorney Scraper — AV Preeminent Ratings (`scrapersdelight/martindale-lawyer-scraper`) Actor

Attorney records from Martindale-Hubbell: name, firm, job title, practice areas, office address, phone and website where published, year of first admission, bar admissions, law school, ISLN — and the AV Preeminent peer review rating with its five sub-scores. Filter by state, city or rating.

- **URL**: https://apify.com/scrapersdelight/martindale-lawyer-scraper.md
- **Developed by:** [Scrapers Delight](https://apify.com/scrapersdelight) (community)
- **Categories:** Lead generation, Business, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$3.00 / 1,000 per attorney returneds

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/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

## Martindale-Hubbell Attorney Scraper — AV Preeminent Ratings

One row per attorney from [Martindale-Hubbell](https://www.martindale.com), with **fullName**,
**jobTitle**, **firmName**, **practiceAreas**, **street/city/state/postalCode**, **phone** and
**website** where published, **yearOfFirstAdmission**, **barAdmissions** (every bar and court),
**lawSchool**, **university**, **isln** — and the credential Martindale has been publishing since
1868: the **AV Preeminent® Peer Review Rating**, as `peerRatingTier`, `peerRating`,
`peerReviewCount` and the **five sub-scores** in `peerReviewBreakdown`.
Filter by country, state/province, city, practice area, rating tier and years since admission — or
paste attorney profile URLs and scrape those directly.
**No login. No cookies. No CAPTCHA solving.**

Martindale is the **credential file, not the contact file**. It returns the AV peer ballot **broken
out into its five named sub-scores**, plus the ISLN and the full bar-admission list — the credential
side of Martindale rather than a single rating label. Phone is **1.8%** here, so read
[Honest limits](#honest-limits) before you buy.

### Scope

Measured on Martindale's live sitemap index on **2026-08-12**: **94 attorney sitemap chunks** —
**76 across 52 US regions**, 13 Canadian provinces and territories, 5 US territories, at up to
50,000 attorney URLs per chunk. **Texas alone publishes 142,408 attorney URLs**; Yukon publishes
107\. Those are counts read off the files, not quoted from a marketing page.

### 🚀 Quick start

The Actor ships prefilled with a working demo. **Click Try for free, press Start, change nothing.**

```json
{
  "country": "usa",
  "states": ["texas"],
  "maxItems": 50,
  "proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] }
}
```

Measured on that exact input over three runs, 2026-08-12: **50 Texas attorneys in 35–50 seconds**,
52–53 requests, **96–100% clean on the first attempt**, **0 duplicates**, costing **$0.15**. Every
field in the sample row below came out of a run like that one.

### The wedge: the AV Preeminent peer rating

Martindale-Hubbell has been rating lawyers **since 1868**. Its rating is not a star average from
clients — it is a **confidential ballot of other attorneys and judges**, scored on Legal Knowledge,
Analytical Capability, Judgment, Communication and Legal Experience, and it comes in three tiers:

| Tier | What it means |
|---|---|
| **AV Preeminent®** | The top tier. A large number of the lawyer's peers rank them at the highest level of professional excellence. |
| **Distinguished** | An excellent rating for a lawyer with some experience; widely respected by peers. |
| **Notable** | Recognised by a large number of peers for strong ethical standards. |

This field is **not on Avvo, not on Justia, and not on any state bar registry.** It is the oldest
and most-cited attorney credential in the United States, and it is what a legal-tech, legal-
recruiting, expert-witness, litigation-finance or law-firm-M\&A buyer actually wants to sort on.
Every row carries the tier, the numeric peer score and the peer review count; peer-reviewed
attorneys also carry the five sub-scores broken out.

**Measured on 490 unique Texas attorneys:** 25.5% carry a peer rating. Of 490 rows,
**77 AV Preeminent, 34 Distinguished, 8 Notable.**

### Read this before you buy rows

1. **The sitemaps slice by STATE ONLY.** Martindale's browse and listing pages are behind a
   Cloudflare interactive challenge, so city, practice-area and rating filters are applied *after* a
   profile has been fetched. That is cheap for a state pull and expensive for a narrow one —
   measured, Texas unfiltered returned 250 attorneys from 250 fetches (**1.0 fetches per row**),
   while Texas + `cities: ["Austin"]` + `avPreeminentOnly` returned 12 attorneys from 657 fetches
   (**55 fetches per row**). You are billed per attorney **returned**, so that costs you time and
   this Actor its proxy budget — not your money.
2. **Most Martindale records were never claimed by the attorney.** Credential fields are
   near-universal (ISLN 100%, bar admissions 99.6%); contact fields are not (phone 1.8%, website
   5.1%). The fill table below is the whole story and it is not flattering in one column on purpose.
3. **There is no email address in this dataset, anywhere.** Martindale publishes none, for any
   attorney. See the FAQ.
4. **Law firms are a different record.** `/organization/…` URLs are rejected with an error rather
   than silently producing a half-empty attorney row.
5. **A missing rating is a fact about Martindale, not a scraping failure.** `peerReviewCount: 0`
   means "rated by nobody"; `null` means Martindale did not render that rating for this attorney.

### What you get — one row per attorney

| Group | Fields |
|---|---|
| **Identity** | `profileUrl`, `profileId`, `isln`, `fullName`, `photoUrl`, `scrapedAt` |
| **Work** | `jobTitle`, `firmName`, `firmProfileUrl`, `practiceAreas` |
| **Address** | `street`, `city`, `state`, `postalCode`, `country`, `addressFull`, `latitude`, `longitude`, `officeCount`, `offices[]` |
| **Contact** | `phone`, `phones[]`, `fax`, `website`, `websites[]` |
| **Peer rating** | `peerRatingTier`, `isAvPreeminent`, `peerRating`, `peerReviewCount`, `peerReviewBreakdown{5}`, `awards[]` |
| **Client / other ratings** | `clientRecommendPercent`, `clientReviewCount`, `avvoRating`, `avvoReviewCount`, `overallRating`, `overallReviewCount` |
| **Credentials** | `yearOfFirstAdmission`, `yearsSinceAdmission`, `barAdmissions[]`, `lawSchool`, `lawSchoolDegree`, `lawSchoolYear`, `university`, `universityDegree`, `universityYear` |

Conventions, stated once: `scrapedAt` is ISO-8601 UTC. `peerRating`, `avvoRating` and
`overallRating` are on a **0–5 scale**; `clientRecommendPercent` is a **percentage (0–100)**.
`yearsSinceAdmission` is computed from `yearOfFirstAdmission` against the current UTC year.
The dataset ships four table views — **Attorneys**, **Peer ratings**, **Contact details** and
**Credentials** — so you can look at the slice you bought without writing a transformation.
The run also writes `NEXT_SKIP_FIRST` to the key-value store.

### Field fill — measured on 490 unique Texas attorneys

Sorted by fill, highest first. Martindale's sitemaps list *every* attorney it has a record for, and
most of those records were never claimed.

| Field | Fill | |
|---|---|---|
| `fullName`, `city`, `state`, `addressFull`, `isln`, `profileUrl` | **100%** | every row |
| `barAdmissions` | **99.6%** | the bars and courts, split into a list |
| `yearOfFirstAdmission` / `yearsSinceAdmission` | **98.2%** | |
| `postalCode` | **96.7%** | |
| `street` | **91.0%** | the rest publish a city-only address |
| `lawSchool` | **80.0%** | `lawSchoolDegree` and `lawSchoolYear` alongside |
| `practiceAreas` | **79.2%** | 4.7 areas per attorney on average |
| `university` | **58.0%** | undergraduate |
| `jobTitle` | **52.0%** | Partner, Member, Associate, Of Counsel, judges' titles |
| `firmName` / `firmProfileUrl` | **36.9%** | many listings are solo or unaffiliated |
| **`peerRating` (numeric)** | **25.5%** | **the AV field — the reason this Actor exists** |
| **`peerRatingTier`** | **24.3%** | AV Preeminent / Distinguished / Notable |
| `awards` | 24.3% | includes Client Champion tiers |
| `peerReviewBreakdown` | 9.0% | the five sub-scores, when peer reviews exist |
| **`website`** | **5.1%** | claimed profiles only |
| `overallRating` | 4.3% | blended peer + client |
| `clientRecommendPercent` / `clientReviewCount` | 3.7% | |
| **`phone`** | **1.8%** | **claimed, subscribing profiles only** |
| `latitude` / `longitude` | 1.8% | |
| `photoUrl` | 1.4% | |
| `avvoRating` | 0.4% | only on profiles linked across Martindale and Avvo |

**The headline that could mislead you: this is not a phone-number product.** Martindale publishes a
phone on roughly **1 profile in 50** on a random state walk, and **no email address for any
attorney, ever**.

Fill varies by state and by slice — a walk through a state's older, more established attorneys
returns more ratings than one through recent admissions. **Every run prints its own measured fill in
the log.**

### How to run it

#### A. A plain state pull (the cheapest mode)

Every profile fetched is a row emitted: **1.0 fetches per row**, no waste.

```json
{ "country": "usa", "states": ["new-york", "new-jersey"], "maxItems": 2000 }
```

#### B. The AV Preeminent slice

The filter runs after the fetch, so budget roughly 6 fetches per row (15.7% of 490 Texas attorneys
qualified). The log prints the running match rate every 200 fetches.

```json
{
  "country": "usa",
  "states": ["texas"],
  "avPreeminentOnly": true,
  "minYearsSinceAdmission": 25,
  "maxItems": 500
}
```

#### C. A practice-area cut

`practiceAreas` is a dropdown of Martindale's root area names, matched as a case-insensitive
substring — so **Criminal** also keeps "Criminal Defense" and "Criminal Law". Martindale's taxonomy
is long-tail (50 Wyoming attorneys produced **202 distinct area strings**), so anything the dropdown
does not list goes in `practiceAreaKeywords` as free text. The two are OR-ed together.

```json
{
  "country": "usa",
  "states": ["florida"],
  "practiceAreas": ["Personal Injury", "Medical Malpractice"],
  "practiceAreaKeywords": ["Insurance Coverage"],
  "maxItems": 300
}
```

#### D. Known profiles — paste the URLs

Sitemap enumeration is skipped entirely. Only `/attorney/…` URLs are accepted; Martindale's
directory pages are Cloudflare-walled and `/organization/…` firm pages are a different record.

```json
{
  "profileUrls": [
    "https://www.martindale.com/attorney/mr-william-mather-mckellar-1808340/"
  ]
}
```

#### E. Resuming a big state across runs

Run 1 stops and logs `Resume: set skipFirst to N`, and writes the same number to the key-value store
as `NEXT_SKIP_FIRST`. Feed it to run 2 and schedule the pair on Apify Schedules if you want it
standing.

```json
{ "country": "usa", "states": ["texas"], "skipFirst": 1000, "maxItems": 1000 }
```

Measured: two contiguous 120-attorney slices joined this way shared **0 rows** — 240 rows,
240 distinct profile ids.

### Sample row

One real row, from a real run:

```jsonc
{
  "profileUrl": "https://www.martindale.com/attorney/mr-william-mather-mckellar-1808340/",
  "profileId": "1808340",
  "isln": "904947272",

  "fullName": "William Mather McKellar",
  "jobTitle": "Member",
  "firmName": "McKellar, Tiedeken & Scoggin LLC",
  "firmProfileUrl": "https://www.martindale.com/organization/mckellar-tiedeken-scoggin-llc-1445765/…",
  "practiceAreas": ["Insurance Defense Law", "Insurance Coverage Law", "Personal Injury Litigation",
                    "Products Liability Litigation", "Environmental Litigation", "Toxic Tort Litigation"],

  "street": "702 Randall Ave.",
  "city": "Cheyenne",
  "state": "WY",
  "postalCode": "82003-0748",
  "country": "US",
  "addressFull": "702 Randall Ave., Cheyenne, WY 82003-0748",
  "latitude": null,
  "longitude": null,

  "phone": null,
  "phones": [],
  "fax": null,
  "website": null,
  "websites": [],

  "peerRatingTier": "AV Preeminent",
  "isAvPreeminent": true,
  "peerRating": 4.9,
  "peerReviewCount": 3,
  "clientRecommendPercent": null,
  "clientReviewCount": null,
  "avvoRating": null,
  "avvoReviewCount": null,
  "overallRating": null,
  "overallReviewCount": 0,
  "awards": ["AV Preeminent"],
  "peerReviewBreakdown": {
    "legalKnowledge": 4.9, "analyticalCapability": 4.9, "judgment": 4.9,
    "communication": 4.9, "legalExperience": 4.9
  },

  "yearOfFirstAdmission": 1982,
  "yearsSinceAdmission": 44,
  "barAdmissions": ["1982, Wyoming", "1982, Colorado",
                    "1982, U.S. District Court, District of Wyoming, U.S. Court of Appeals, Tenth Circuit and U.S. Supreme Court"],
  "lawSchool": "University of Wyoming",
  "lawSchoolDegree": "J.D.",
  "lawSchoolYear": 1982,
  "university": "University of Northern Colorado",
  "universityDegree": "B.A.",
  "universityYear": 1979,

  "photoUrl": null,
  "officeCount": 1,
  "offices": [{ "label": "Cheyenne, WY", "street": "702 Randall Ave.", "city": "Cheyenne",
                "state": "WY", "postalCode": "82003-0748", "phone": null, "fax": null }],
  "scrapedAt": "2026-08-12T22:35:19.477Z"
}
```

Three fields people misread, so they are named to prevent it:

- **`clientRecommendPercent` is a percentage, not a star rating.** Martindale renders the client
  tile as "100%" — the share of reviewing clients who recommend the attorney — while `peerRating`,
  `avvoRating` and `overallRating` are on a 0–5 scale. Mixing them would put a 100 in a 5-point
  column.
- **`peerReviewCount: 0` means "rated by nobody"; `peerReviewCount: null` means "Martindale did not
  render that rating for this attorney".** They are different facts and are kept different.
- **`peerRating` is a peer ballot, `clientRecommendPercent` is client feedback.** An attorney can
  have one, both or neither.

### Input

Grouped in the Console as **Step 1 | Choose the market** · **Step 1 | Or paste profile URLs** ·
**Step 2 | Filter the rows** · **Step 3 | Choose how deep to go** · **Step 4 | Set limits and
resume** · **Connection settings**. Nothing is required; everything that matters is prefilled.

| Field | Type | Default | What it does |
|---|---|---|---|
| `country` | select | `usa` | `usa` (76 sitemap chunks, 52 regions), `canada` (13) or `territories` (5). |
| `states` | multi-select | `["texas"]` | State/province picked from a dropdown of all 70 regions Martindale publishes. Empty walks the whole country alphabetically. **The only pre-fetch filter.** |
| `profileUrls` | list | — | Paste `/attorney/…` URLs to skip enumeration. Directory URLs are walled and firm URLs are rejected. |
| `cities` | list | — | Keep only these office cities. Exact, case-insensitive. Applied after fetching. |
| `practiceAreas` | multi-select | — | 61 root area names, matched as case-insensitive substring. Applied after fetching. |
| `practiceAreaKeywords` | list | — | Free-text escape hatch for the long tail; OR-ed with the dropdown. |
| `avPreeminentOnly` | bool | `false` | Keep only AV Preeminent attorneys. ~6 fetches per row. |
| `ratedOnly` | bool | `false` | Keep any peer or client rating. ~4 fetches per row. |
| `withPhoneOnly` | bool | `false` | Keep only attorneys with a published phone. **~50 fetches per row** — read the fill table first. |
| `withWebsiteOnly` | bool | `false` | Keep only attorneys with a firm website. ~20 fetches per row. |
| `minYearsSinceAdmission` | int | `0` | Admitted at least this many years ago. 98.2% fill, so this filter is cheap. |
| `includePeerBreakdown` | bool | `true` | Include the five peer sub-scores. No extra request. |
| `includeOffices` | bool | `true` | Include the full office array. The primary office is always flattened into the top-level address fields. |
| `maxItems` | int | `50` | Cap on attorneys returned. **Also your budget control.** |
| `skipFirst` | int | `0` | Resume where the previous run stopped. |
| `maxConcurrency` | int | `4` | Workers in flight (1–8). The 600 ms pacer sets the real rate. |
| `proxyConfiguration` | proxy | Apify RESIDENTIAL | Leave it. See the transport ladder. |

### Pricing

**$0.003 per attorney returned** — **$3 per 1,000** — charged on the `attorney-scraped` event.
No monthly platform fee from this Actor.

| Run | Attorneys | Cost |
|---|---|---|
| The prefilled demo, no edits | 50 | **$0.15** |
| A city sample | 250 | **$0.75** |
| A practice-area list | 1,000 | **$3.00** |
| A serious Texas pass | 5,000 | **$15.00** |
| Every attorney Texas publishes | 142,408 | **$427.22** |

You are charged for rows **delivered**, never for a profile that was fetched and then filtered out,
and never twice for the same attorney — duplicate profile ids are dropped at enumeration, before
anything is fetched. Rows are charged **as they are pushed**, so if you hit a budget cap you get
whole rows and stop, not a half-billed dataset. `maxItems` is your hard cost cap.

### Honest limits

- **No email addresses.** Martindale publishes none, for any attorney. Anyone selling you
  "Martindale emails" generated them somewhere else.
- **Phone is 1.8% and website is 5.1%** on a random state walk of 490 Texas attorneys. Those fields
  live only on claimed, subscribing profiles. This is a credentials-and-ratings product, not a
  dialler list. If you need contactable rows, pair it with `justia-lawyer-scraper` (claimed-profile
  subset, phone near-universal) or a state bar registry, and use this Actor for the credential and
  rating fields.
- **City and practice-area filtering happens after the fetch**, because Martindale's city listings
  are Cloudflare-walled. Narrow asks are slow — 55 fetches per row on the Austin + AV Preeminent
  measurement. There is no workaround that does not involve attacking the challenge, which this
  Actor does not do. **The cheap way to slice a city:** pull the state unfiltered into a dataset
  (1.0 fetches per row) and filter it yourself — you get every other city for free.
- **Law firms are a different record.** `/organization/…` URLs are rejected rather than silently
  producing a half-empty attorney row.
- **Ratings are Martindale's, not ours.** A missing rating means Martindale never published one; it
  is not a scraping failure. Peer reviews submitted before 2008 are not displayed by Martindale, so
  some long-standing attorneys show a tier with no visible reviews behind it — which is why
  `peerRatingTier` is 24.3% while `peerReviewBreakdown` is 9.0%.
- **`avvoRating` is sparse (0.4%)** — Martindale and Avvo are sister sites and the tile only appears
  on profiles that are linked across both.
- **Canada and the territories are much smaller files.** Yukon publishes 107 profile URLs. Expect
  fewer ratings and far fewer claimed profiles outside the US.
- **Nationwide and multi-state runs deduplicate within a run.** Across runs, use `skipFirst`.

### How it works (technical)

1. Read `https://www.martindale.com/sitemap_profiles.xml`, the index Martindale's own `robots.txt`
   publishes. `/search/`, `/assets/html/profiles/` and `/marketyourfirm/…` are `Disallow`ed and this
   Actor never touches them.
2. Pick the `atty_<country>_<region>_NNNN_profiles.xml` chunks for your country and states (up to
   50,000 attorney URLs each) and stream the `<loc>` values out.
3. Deduplicate on the numeric profile id **before anything is fetched**, then skip `skipFirst`.
4. Fetch each `/attorney/{name}-{id}/` page over plain HTTP through one pinned Apify RESIDENTIAL
   session, paced at 600 ms.
5. Merge two sources on the page: the JSON-LD `LegalService` block (name, firm, address, geo, phones,
   website, photo, awards, alumni) plus the rendered HTML for what Martindale keeps out of JSON-LD —
   the peer score and its five sub-scores, the client score, "Year of First Admission", the
   bar-admission list, the ISLN, the university and the full office list. Ratings are always read
   from the labelled blocks, because the JSON-LD `aggregateRating` collapses peer and client into one
   number without saying which.
6. Apply your filters, push in batches, charge on push.

**Why the sitemaps and not the listing pages.** Measured 2026-08-12 through a working Apify
RESIDENTIAL session — the same session that was fetching profile pages at 100% seconds earlier:

| Page | Result |
|---|---|
| `/all-lawyers/austin/texas/` | **HTTP 403**, "Just a moment…", 0 attorney links |
| `/all-lawyers/austin/texas/?page=2` | **HTTP 403** |
| `/by-location/austin-lawyers/texas/` | **HTTP 403** |
| `/by-location/texas-lawyers/` | **HTTP 403** |
| `/family-law-lawyer/texas/austin/` | **HTTP 403** |
| `/attorney/{name}-{id}/` | **HTTP 200**, full profile |
| `sitemap_profiles.xml` and the per-state chunks | **HTTP 200** |

**The transport ladder.** Everything below was measured through **Apify Proxy**, not from a laptop.
A home broadband line reaches martindale.com without a proxy; an Apify container does not, so the
home result is reported and then ignored.

| Rung | Result |
|---|---|
| Direct, no proxy, residential home line | 200 — **but not a shippable rung**, no Apify container has a residential IP |
| Apify **datacenter**, new exit IP every request | **0/5** — HTTP 403 + Cloudflare challenge |
| Apify **datacenter**, pinned session | 100/130 requests clean on the first attempt = **76.9%** |
| Apify **RESIDENTIAL**, new exit IP every request | 24/25 = **96.0%** |
| Apify **RESIDENTIAL**, pinned session, 250 ms pacing | 143/161 = **88.8%** first attempt |
| **Apify RESIDENTIAL, pinned session, 600 ms pacing** | **122/122 = 100%**, then **247/257 = 96.1%** over a 250-attorney run, **0 failures** |

Two findings worth keeping:

1. **Pinning matters more than the pool.** A residential exit Cloudflare has already let through
   keeps being let through; a fresh IP on every request pays the challenge lottery each time.
2. **Pacing beat concurrency.** At 250 ms the failures were HTTP **429**, not challenges —
   Martindale rate-limits the exit IP, and a pinned session cannot rotate away from it for free.
   Backing off to 600 ms removed them *and made the run faster end to end* (79 s vs 86 s for 120
   attorneys), because every retry costs a request plus its backoff. The Actor is therefore paced,
   not parallelised; `maxConcurrency` only controls how many workers wait on the pacer.

Anything still challenged or rate-limited burns its proxy session and retries on a fresh one. Across
every validation run, **0 attorneys were lost to the transport**.

### Uniqueness — measured, not asserted

- Texas publishes **142,408** attorney URLs across 3 **contiguous** sitemap chunks: **0.00%
  duplicate profile ids** across all three.
- Every validation run reported **0.00% duplicates** in its own output, including the 50-row
  prefilled demo (50 rows, 50 distinct profile ids).
- Two contiguous slices joined with `skipFirst` (120 + 120 attorneys) shared **0 rows**:
  240 rows, 240 distinct profile ids.

Profile ids are deduplicated at enumeration, **before anything is fetched**, and re-asserted on the
way out — so a duplicate can reach neither your dataset nor your bill. Every run ends with a
`Resume: set skipFirst to N` line and writes the same number to the key-value store as
`NEXT_SKIP_FIRST`.

### When a run fails

This Actor fails loudly rather than handing you a green run with an empty dataset.

- **0 rows throws**, with the fetch count, how many were blocked, dead, unparseable and how many your
  filters removed — plus which of those is the likely cause.
- **A state or country slug that does not exist throws**, listing the slugs Martindale actually
  publishes right now.
- **A sitemap index with no attorney sitemaps throws** rather than reporting a successful empty run.
- **More than 20% of HTTP-200 profile pages failing to parse throws** — that is a template change,
  and shipping degraded rows would be worse than stopping.
- **`skipFirst` past the end of the enumerated list throws** instead of returning nothing.
- **A firm `/organization/…` URL throws** instead of emitting a half-empty attorney row.
- Running out of candidates before `maxItems` is a **warning**, not a failure — the rows you got are
  real, there just were not more that matched.

### Who buys this

| You are… | You use it to… |
|---|---|
| **Legal-tech / practice-management SaaS** (Clio, MyCase, Filevine) | Build an ICP list from `firmName` + `officeCount` + `practiceAreas`, and prioritise the AV Preeminent partners who set software budgets. |
| **Legal recruiters and lateral-hire desks** | Rank candidates on a peer credential rather than a self-written bio: `peerRatingTier` + `yearsSinceAdmission` + `lawSchool` + `barAdmissions`. |
| **Expert-witness and litigation-support brokers** | Find AV-rated specialists by `practiceAreas` in the venue's state, with `barAdmissions` proving they can appear there. |
| **Litigation funders and law-firm M\&A advisors** | Screen firms by how many AV Preeminent attorneys they carry, and who is 25+ years in (`minYearsSinceAdmission`) and near succession. |
| **Legal-directory and rating aggregators** | Join on `isln`, the stable Martindale identifier present on 100% of rows, and carry the five peer sub-scores broken out rather than a single rating label. |
| **Bar associations and CLE providers** | Segment by `yearOfFirstAdmission` cohort and `barAdmissions` jurisdiction. |

### How this differs from the sibling legal Actors

| Actor | What it is | Why you would use it instead |
|---|---|---|
| **This Actor** | Martindale-Hubbell, 94 attorney sitemap chunks across 70 regions | You want the **AV Preeminent peer rating** with its five sub-scores, the ISLN, the full bar-admission list and law school — the credential fields. |
| [`avvo-scraper`](https://apify.com/scrapersdelight/avvo-scraper) | Avvo | You want the **client**-review side and the Avvo 10-point score. |
| [`ca-attorney-scraper`](https://apify.com/scrapersdelight/ca-attorney-scraper) | The California State Bar's own file | You want **every** licensed attorney in California, with licence status and discipline history, from the official record. |
| `justia-lawyer-scraper` | The Justia directory, claimed-profile subset | You want **contactable leads** — phone and website are near-universal there because it only covers claimed profiles. |
| `state-bar-attorney-scraper` | A state's own registration file | You want registration status, admission date and discipline history for a whole state. |
| `superlawyers-attorney-scraper` | Super Lawyers selections | You want the **peer-nominated annual list** rather than a standing rating. |

The honest summary: **Justia is where you go for phone numbers, Martindale is where you go for the
peer rating.** The usual play is to pull the AV Preeminent slice here and enrich the contact details
elsewhere.

### FAQ

**Can I get attorney emails?**
No — and nobody honestly can, from this source. Martindale publishes **no email address for any
attorney**, so there is no `email` field and nothing in this Actor can invent one. What you do get is
`phone` (1.8%), `website` (5.1%) and `firmProfileUrl` (36.9%), plus the credential fields at
98–100%. If email is the point of your project, start from a source that publishes contact data and
use this Actor for the rating.

**Does this need an account or login?**
No. It reads public directory pages listed in Martindale's own `robots.txt` sitemaps. No login, no
cookies, no CAPTCHA solving.

**Can I get every attorney in a state?**
Yes. Set the state, leave the filters off, set `maxItems`. Texas is 142,408 attorney URLs, so a full
pull is a multi-run job — use the `Resume: set skipFirst to N` line the run prints.

**Can I filter to one city?**
Yes, but the filter runs after each profile is fetched, because Martindale's city listings are
walled. A big city inside its own state costs a handful of fetches per row; **a small town can cost
hundreds**, and Austin + AV Preeminent measured 55 fetches per row. Cheaper: pull the state and
filter the dataset yourself.

**How do I get only AV Preeminent lawyers?**
Set `avPreeminentOnly: true`. Measured on 490 Texas attorneys, 77 qualified (15.7%), so raise
`maxItems` and give the run time; the log prints the running match rate.

**Do I get charged for rows that get filtered out?**
No. You are charged per attorney **returned**, on push. A narrow filter costs time and proxy budget,
not money.

**Two runs, will I get duplicates?**
Not if you use `skipFirst` with the number the previous run logged. Measured: two contiguous 120-row
slices shared 0 rows out of 240.

**Does it need a proxy?**
Yes — Apify Proxy with the RESIDENTIAL group, which is the default. A datacenter IP that rotates on
every request measured 0/5; a pinned datacenter session 76.9%; a pinned residential session at
600 ms 100% then 96.1%.

**Why do so many rows have no firm?**
Because 36.9% is the real number. Martindale lists solo practitioners, judges, in-house counsel and
unaffiliated attorneys with no firm record attached.

**What is ISLN?**
Martindale's own International Standard Lawyer Number — a stable per-attorney identifier, present on
100% of rows. It is the right key for de-duplicating against another Martindale pull.

**Does it work outside the US?**
Canada (13 provinces and territories) and the US territories (5), yes — set `country`. The files are
much smaller and much sparser; Yukon publishes 107 profile URLs.

**Will a run ever succeed with zero rows?**
No. A run that emits nothing throws, and the error says how many pages were fetched, how many were
blocked or dead, and how many your filters removed.

**Something looks wrong — how do I debug it?**
Read the run log. Every run prints its own field fill, its own duplicate rate, and the transport
statuses it saw, so you can compare your slice against the numbers on this page instead of guessing.

**Can I schedule it?**
Yes — Apify Schedules. Pair it with `skipFirst` / `NEXT_SKIP_FIRST` to walk a large state a slice at
a time.

### ⚠️ Legal & fair use

This Actor reads **public directory pages** that Martindale-Hubbell publishes and lists in its own
`robots.txt` sitemaps, and it does not touch the `Disallow`ed paths (`/search/`,
`/assets/html/profiles/`, `/marketyourfirm/…`). It does not log in, and it collects no data behind
any authentication or CAPTCHA.

Rows describe named individuals, so they are personal data. **You are responsible for complying with
Martindale's terms and with how you use the data.** Attorney advertising, solicitation and anti-spam
rules may apply to **your** outreach — including GDPR, CCPA, CAN-SPAM and state bar rules on
soliciting lawyers and their clients.

Martindale-Hubbell®, AV®, AV Preeminent® and Martindale® are trademarks of their owner; this Actor is
not affiliated with, endorsed by or sponsored by them.

### Feedback

Found a missing field or want a new filter? Open an issue on the **Issues** tab.

### SEO keywords

martindale scraper, martindale-hubbell scraper, martindale lawyer scraper, martindale attorney
scraper, av preeminent rating scraper, av rated lawyers list, attorney peer review rating data,
lawyer directory scraper, attorney data export, legal directory api, law firm lead generation,
attorney lead list, lawyer database download, bar admissions data, isln lookup, attorney credentials
dataset, legal recruiting data, expert witness sourcing, law firm m\&a research, attorney practice
area list, us lawyer directory scraper, canada lawyer directory scraper, legal tech icp list,
attorney law school data, peer review rating api, martindale csv export

# Actor input Schema

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

Which Martindale directory to walk. Measured on the live sitemap index 2026-08-12: United States publishes 76 sitemap chunks across 52 regions, Canada 13, US territories 5. The states/provinces list below must belong to the country you pick here — mixing them stops the run with an error listing the valid slugs rather than returning nothing.

## `states` (type: `array`):

Pick one or more. Leave empty to walk the whole country in alphabetical order. Sizes differ by orders of magnitude: Texas alone publishes 142,408 attorney URLs across 3 sitemap chunks, while Yukon publishes 107. Only pick options belonging to the country selected above.

## `profileUrls` (type: `array`):

One attorney profile URL per line, e.g. https://www.martindale.com/attorney/mr-william-mather-mckellar-1808340/ — the trailing numeric id is what identifies the attorney. Martindale's DIRECTORY pages (/all-lawyers/…, /by-location/…, practice-area listings) are behind a Cloudflare challenge and returned HTTP 403 on every attempt through a working residential session, so they are not accepted here; only /attorney/ profile URLs are. Law-firm /organization/ URLs are a different record and are rejected with an error rather than producing a half-empty attorney row.

## `cities` (type: `array`):

Optional. Keep only attorneys whose office city matches, e.g. Austin, Houston, San Antonio. Exact and case-insensitive against the city Martindale prints in the address. A big city inside its own state costs a few fetches per row; a small town can cost hundreds. The run logs the running match rate every 200 fetches so you are never guessing.

## `practiceAreas` (type: `array`):

Optional. Keep only attorneys who list one of these areas. Matching is case-insensitive SUBSTRING against Martindale's own area names, so picking Criminal also keeps "Criminal Defense" and "Criminal Law", and picking Litigation keeps "Commercial Litigation", "Civil Litigation" and "Toxic Tort Litigation". Measured fill: 79.2% of attorneys list at least one area, 4.7 areas each on average — but Martindale's taxonomy is long-tail (50 Wyoming attorneys produced 202 distinct area names), so this list covers the roots. For anything not here, use the free-text box below.

## `practiceAreaKeywords` (type: `array`):

Optional escape hatch for the long tail the dropdown above cannot list. Same case-insensitive substring rule, one phrase per line, e.g. "Insurance Coverage", "Oil and Gas", "Complex and Multi-District". Combined with the dropdown as OR — an attorney is kept if they match ANY selected area or ANY keyword. Leave empty if the dropdown covers you.

## `avPreeminentOnly` (type: `boolean`):

Keep only attorneys carrying the AV Preeminent Peer Review Rating — Martindale's top peer tier, voted by other lawyers and judges, and the field no other attorney directory publishes. Measured on 490 unique Texas attorneys: 77 qualify (15.7%), so expect roughly 6 fetches per row on a plain state walk and far more if you combine it with a city.

## `ratedOnly` (type: `boolean`):

Looser than the AV filter: keeps any attorney with a peer rating (AV Preeminent, Distinguished or Notable) or a client rating. Measured 25.5% carry a peer rating on 490 Texas attorneys, so roughly 4 fetches per row.

## `withPhoneOnly` (type: `boolean`):

Keep only attorneys who publish a phone number. Off by default, and read this first: Martindale publishes a phone only on CLAIMED, subscribing profiles — measured at 1.8% of 490 Texas attorneys, roughly 1 profile in 50 — so this filter costs about 50 fetches per row returned. Martindale publishes NO email address for any attorney, so there is no email filter and no email field.

## `withWebsiteOnly` (type: `boolean`):

Keep only attorneys who link a firm website. Measured 5.1% on 490 Texas attorneys — same claimed-profile subset as the phone, so budget roughly 20 fetches per row.

## `minYearsSinceAdmission` (type: `integer`):

Keep only attorneys admitted to a bar at least this many years ago, computed from the profile's own 'Year of First Admission'. Set 25+ for succession, retirement and practice-acquisition outreach; leave at 0 for everyone. Measured 98.2% fill, so this is the one cheap filter on the page.

## `includePeerBreakdown` (type: `boolean`):

Include the five Martindale peer sub-scores — Legal Knowledge, Analytical Capability, Judgment, Communication and Legal Experience — as an object on each row. Measured present on 9.0% of 490 Texas attorneys, because Martindale only renders them where visible peer reviews exist (reviews submitted before 2008 are not displayed).

## `includeOffices` (type: `boolean`):

Include the full array of office locations Martindale lists for the attorney, each with its own street, city, state, ZIP, phone and fax. The primary office is always flattened into the top-level address fields either way, so turning this off loses nothing for a single-office attorney.

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

Cap on attorneys returned. You are charged $0.003 per attorney returned, so this is also your budget control: 50 rows = $0.15, 1,000 = $3.00, 5,000 = $15.00. Rows that your Step 2 filters remove are never charged.

## `skipFirst` (type: `integer`):

Resume where a previous run stopped. Martindale's sitemaps are stable and served in a fixed order, so run 1 with maxItems 1000 and run 2 with skipFirst 1000 walks the next slice of the same state without repeating anyone — measured: two contiguous 120-attorney slices joined this way shared 0 rows out of 240. Every run logs the number to use and writes it to the key-value store as NEXT\_SKIP\_FIRST.

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

How many profile pages may be in flight at once (1-8). The run is paced to roughly 1.7 requests a second no matter what you set, because Martindale rate-limits the proxy exit IP and not the worker — at 4 requests a second the run measured 88.8% clean and spent the difference on retries, at 1.7 it measured 96-100% and finished FASTER (79 s vs 86 s for 120 attorneys). Raising this above 6 mostly changes how many workers wait on the pacer.

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

Apify Proxy with the RESIDENTIAL group. Leave it alone unless you are supplying your own residential proxies. Measured through Apify against martindale.com: a datacenter exit that changes on every request cleared 0 of 5 requests, a pinned datacenter session 76.9%, a pinned residential session at 600 ms pacing 122/122 = 100% and then 96.1% over a 250-attorney run with 0 attorneys lost. Datacenter will eventually get there on retries; it just pays for the same rows several times over.

## Actor input object example

```json
{
  "country": "usa",
  "states": [
    "texas"
  ],
  "avPreeminentOnly": false,
  "ratedOnly": false,
  "withPhoneOnly": false,
  "withWebsiteOnly": false,
  "minYearsSinceAdmission": 0,
  "includePeerBreakdown": true,
  "includeOffices": true,
  "maxItems": 50,
  "skipFirst": 0,
  "maxConcurrency": 4,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `items` (type: `string`):

One row per attorney: name, job title, firm, practice areas, office address and geo, phone, website, AV Preeminent / Distinguished peer rating with sub-scores, client-recommendation percentage, year of first admission, bar admissions, law school and ISLN.

# 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 = {
    "country": "usa",
    "states": [
        "texas"
    ],
    "maxItems": 50,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapersdelight/martindale-lawyer-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 = {
    "country": "usa",
    "states": ["texas"],
    "maxItems": 50,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("scrapersdelight/martindale-lawyer-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 '{
  "country": "usa",
  "states": [
    "texas"
  ],
  "maxItems": 50,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call scrapersdelight/martindale-lawyer-scraper --silent --output-dataset

```

## MCP server setup

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