# Martindale.com Lawyer Directory Scraper (`memo23/martindale-scraper`) Actor

Scrape US attorneys from Martindale.com — name, phone, full address with city/state/zip, firm website, headshot, and Martindale profile URL. 30 attorneys per listing page (no detail-page fetches needed). JSON/CSV out, billed per result.

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

## Pricing

from $2.25 / 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.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are a software tools running on the Apify platform, for all kinds of web data extraction and automation use cases.
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.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp.md).

If your project is in a different language, use 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.com Lawyer Directory Scraper

**Turn Martindale.com into a structured US-attorney dataset.** Scrape every lawyer card from any Martindale listing URL — name (split into first/last), telephone (formatted + digits-only), full street address with city/state/zip, headshot image, firm website, and direct Martindale profile URL — all from a single fetch. ~30 attorneys per listing page; pagination auto-walked. JSON or CSV out, no compute charge per run, just per result.

#### How it works

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

#### 🚀 Quick start

The actor is **prefilled with a working demo** — paste any martindale.com URL, or pick a practice area + state + city from the dropdowns. Click **Start** and you'll get the first 30+ attorneys for `personal-injury-lawyers/dallas/texas/`.

| Mode | Example input |
|---|---|
| **A. Paste a URL** | `startUrls: ["https://www.martindale.com/personal-injury-lawyers/dallas/texas/"]` |
| **B. Dropdowns (cartesian)** | `searchByCategory: ["personal-injury-lawyers"]`, `searchByLocation: ["texas"]`, `searchByCity: ["dallas","austin","houston"]` → 3 listing URLs |
| **C. State-wide** | `searchByCategory: ["family-lawyers"]`, `searchByLocation: ["california"]`, `searchByCity: []` → `/areas-of-law/family-lawyers/california` (single huge fetch) |

> 💡 **Pagination is automatic.** Pasting `?page=4` will continue walking from page 5 until the source runs out or `maxItems` is reached.

#### ✨ Why use this scraper?

Building a US legal lead list? Mapping attorney density by city + practice area? Need a clean dataset of personal-injury / family / criminal-defense attorneys without paying for a CRM-vertical seat?

- 🎯 **Three ways to start.** Paste a Martindale URL, pick from `practice area × state × city` dropdowns (cartesian product), or combine. URLs always win.
- ⚡ **Lite-only architecture, no detail fetches.** Listing pages embed a `@graph` JSON-LD with full row data for ~30 attorneys per page. No per-attorney detail-page fetches needed — keeps the bill low and skips Martindale's flaky detail-page Cloudflare gate.
- 🛡 **Cloudflare-aware.** Martindale serves Cloudflare on every page; the 3-stack TLS race (impit + got-scraping + curl-impersonate Chrome 131) gets through. No browser automation.
- 🔁 **Pagination auto-walked.** Paste a base URL or a `?page=N` URL — the actor walks subsequent pages until `maxItems` is reached or the source runs out.
- 🧹 **De-duplicated by attorney ID.** Adjacent listing pages overlap by ~15-20% on Martindale; we de-dupe by Martindale's own numeric attorney ID so you get one row per person.
- 📤 **Clean exports.** One row per attorney, 12 fields. JSON + CSV exported automatically.

#### 🎯 Use cases

| Team | What they build |
|------|-----------------|
| **Legal marketing / SaaS GTM** | Outbound lists for vendors selling to small/solo law firms — by state, city, and practice area |
| **Lead-gen / referral networks** | Local attorney directories or referral matchmaking by practice area + city |
| **Legal-tech research** | Density maps of attorney supply per metro by practice area |
| **Existing platforms enriching their data** | Patching attorney records with verified phone, address, headshot, and firm website |
| **Competitive intelligence** | Market mapping for new geographies / practice-area expansion |

#### 📥 Supported inputs

The scraper accepts inputs in two mutually-exclusive modes — **URLs take strict precedence over filters**:

> ⚠️ **Precedence rule.** If `startUrls` has any non-empty entry, the practice-area / state / city dropdowns are completely ignored. Clear the URL list to use the dropdowns.

##### Mode 1 — Paste Martindale URLs (highest priority)

| URL pattern | Behaviour |
|---|---|
| `https://www.martindale.com/{practice-area}-lawyers/{city}/{state}/` | **City listing** — extracts ~30 attorneys/page, walks pagination |
| `https://www.martindale.com/{practice-area}-lawyers/{city}/{state}/?page=N` | **Paginated listing** — same, but starts from page N |
| `https://www.martindale.com/areas-of-law/{practice-area}-lawyers/{state}` | **State-wide listing** — single huge fetch (~5-10 MB body) |
| `https://www.martindale.com/attorney/{slug}-{numeric-id}/` | **Skipped** — detail pages flake on Cloudflare and add no fields beyond the listing data |

##### Mode 2 — Practice area + state (+ optional city) dropdowns

All three dropdowns are **multi-select**. The scraper expands the cartesian product:

````

{personal-injury-lawyers, family-lawyers} × {texas} × {dallas, austin, houston}
→ 6 listing URLs

````

**Edge cases:**
- Practice area + state, no city → `/areas-of-law/{practice-area}-lawyers/{state}` (state-wide listing — biggest possible result set per call).
- Practice area, no state, no city → national listing for that practice area.
- No practice area at all → falls back to the default Dallas personal-injury listing.

**Not supported:**
- Martindale articles, attorney rating pages, and the `martindale-hubbell` ratings subdomain
- Direct attorney detail pages (skipped — listings already have the full row data)

#### 🔄 How it works (technical)

1. **Resolve start URLs** from the cartesian product of dropdowns + any pasted `startUrls`.
2. **Skip any attorney-profile URLs** — Martindale's detail pages flake behind Cloudflare and add no extra fields.
3. **Fetch each listing page** through the 3-stack TLS race (impit + got-scraping + curl-impersonate Chrome 131). Only curl-impersonate reliably passes Martindale's CF challenge; the race makes that automatic.
4. **Parse the JSON-LD `@graph` block** — each block holds ~30 `LegalService` items with name, phone, image, address, firm website, and Martindale profile URL.
5. **Dedupe by Martindale attorney ID** to avoid the 15-20% overlap between adjacent paginated pages.
6. **Push one row per attorney** to the dataset; walk `?page=N` pagination automatically.

#### ⚙️ Input parameters

| Parameter | Type | Default | Description |
|---|---|---|---|
| `startUrls` | array | `["https://www.martindale.com/personal-injury-lawyers/dallas/texas/"]` | Optional Martindale URLs. Takes precedence over dropdowns. |
| `searchByCategory` | array (enum) | `["personal-injury-lawyers"]` | Practice-area slugs (20 options). Multi-select. |
| `searchByLocation` | array (enum) | `["texas"]` | US state slugs (51 options including DC). Multi-select. |
| `searchByCity` | array (string) | `["dallas"]` | Optional city slugs. Multi-select. |
| `maxItems` | integer | `5000` | Hard cap on attorney rows. ~30 per listing page; pagination auto-walks until this cap. |
| `maxConcurrency` / `minConcurrency` | integer | `5` / `1` | Parallel HTTP request limits. |
| `maxRequestRetries` | integer | `5` | Retries before a request is given up. |
| `proxy` | object | Apify residential, US | Martindale serves Cloudflare on every page — a US residential pool is **strongly required**. |

#### 📊 Output overview

Each scraped attorney is one **single dataset row** of `type: "attorney"`. No nested objects, no padding rows — one row equals one attorney.

#### 📦 Output sample

```json
{
    "type": "attorney",
    "attorneyId": "1640128",
    "id": "1640128",
    "name": "Alan Notinger",
    "firstName": "Alan",
    "lastName": "Notinger",

    "telephone": "817-516-6995",
    "primaryPhone": "8175166995",

    "address": "2929 Carlisle Street, Suite 115, Dallas, TX",
    "streetAddress": "2929 Carlisle Street, Suite 115",
    "addressLocality": "Dallas",
    "addressRegion": "TX",
    "city": "Dallas",
    "state": "TX",
    "postalCode": "75204",
    "addressCountry": "U.S.A.",

    "image": "https://www.martindale.com/LBM_Images/Lawyers/lawyer-alan-notinger-mr-photo-5205611.png",
    "firmWebsite": "https://www.kaplanlawgrouppllc.com/",
    "profileUrl": "https://www.martindale.com/attorney/mr-alan-s-notinger-1640128/?pa=282",
    "sameAs": ["https://www.martindale.com/attorney/mr-alan-s-notinger-1640128/?pa=282"],

    "scrapedAt": "2026-05-10T18:08:01.117Z"
}
````

#### 🗂 Output fields

| Group | Fields |
|---|---|
| **Identifiers** | `type`, `attorneyId`, `id` (alias), `profileUrl`, `scrapedAt` |
| **Person** | `name`, `firstName`, `lastName` |
| **Contact** | `telephone` (formatted), `primaryPhone` (digits-only 10-digit form), `address` (composed), `streetAddress`, `addressLocality` / `city`, `addressRegion` / `state`, `postalCode`, `addressCountry` |
| **Web presence** | `image` (headshot URL), `firmWebsite`, `sameAs[]` (full outbound link list — usually just the Martindale profile) |

#### ❓ FAQ

**Why no bio, practice areas, education, or ratings?**
Martindale's listing-page JSON-LD only exposes the basics (name, phone, address, image, firm website, profile URL). Richer fields — bio, practice areas, education, peer reviews, ratings — would require fetching each attorney's detail page, but those pages flake on Cloudflare often enough that we don't ship that path. For a richer attorney profile, see the **Avvo Lawyer Directory Scraper** instead.

**Why is the Cloudflare warning in the proxy description?**
Martindale uses Cloudflare aggressively. We probed three HTTP stacks; only curl-impersonate Chrome 131 (real TLS fingerprint) reliably passes the challenge. impit + got-scraping trip it most of the time. The 3-stack race the actor uses tries all three in parallel, so the user doesn't need to know — but residential US proxy is essential.

**How many pages does Martindale paginate?**
Varies by city. Big cities (Dallas, Houston, Los Angeles) paginate to 5+ pages of 30 attorneys; small cities may have just 1 page. The actor walks pagination automatically until either (a) no `Next` link is present or (b) `maxItems` is reached.

**Why are duplicate attorneys filtered out?**
Adjacent paginated pages on Martindale overlap by ~15-20% — the same attorney can appear on page 4 and page 5 of the same listing. We dedupe by Martindale's numeric attorney ID so the dataset has one row per person.

**Can I look up an attorney by their Martindale ID?**
Not via this actor's normal flow — Martindale's detail page (`/attorney/{slug}-{id}/`) JS-renders the rich data and flakes on CF. To find an attorney whose name you know, search the city listings.

**How do I limit results?**
Set `maxItems`. The actor stops as soon as the cap is reached.

#### 💬 Support

- For issues or feature requests, please use the **Issues** tab on the actor's Apify Console page.
- Author's website: <https://muhamed-didovic.github.io/>
- Email: <muhamed.didovic@gmail.com>

#### 🛠 Additional services

- Custom output shape, additional fields, or one-off datasets: <muhamed.didovic@gmail.com>
- Need a similar scraper for another legal directory (Avvo, FindLaw, Justia, Lawyers.com, Super Lawyers)? Drop an email — Avvo + FindLaw + State Bar are already shipped.
- For API access (no Apify fee, just a usage fee for the API): <muhamed.didovic@gmail.com>

#### 🔎 Explore more scrapers

If this Martindale Scraper was useful, see other legal-vertical scrapers at [memo23's Apify profile](https://apify.com/memo23) — Avvo (lawyer directory), FindLaw (law firms), and US State Bar (license verification).

***

### ⚠️ Disclaimer

This Actor is an independent tool and is not affiliated with, endorsed by, or sponsored by Martindale.com, Internet Brands, or any of their subsidiaries or affiliates. All trademarks mentioned are the property of their respective owners.

The scraper accesses only publicly available attorney profile and listing pages on martindale.com — no authenticated endpoints, no Martindale-Hubbell rating-only features, no content behind a logged-in session. Users are responsible for ensuring their use complies with martindale.com's Terms of Service, applicable data-protection law (GDPR, CCPA, etc.), and any state-bar rules around the marketing or use of attorney data in their jurisdiction.

***

### SEO Keywords

martindale scraper, scrape martindale, martindale.com scraper, martindale api, Apify martindale, us lawyer directory scraper, attorney directory scraper, lawyer leads scraper, legal marketing leads, attorney contact data, lawyer contact info scraper, us attorney database, attorney leads by city, lawyer leads by state, personal injury lawyer leads, family lawyer leads, criminal defense lawyer leads, avvo alternative scraper, findlaw alternative scraper, justia alternative scraper, super lawyers alternative scraper, lawyers.com alternative scraper, attorney email scraper, law firm leads, solo practitioner data, small law firm leads, attorney headshots, attorney phone number scraper, lawyer headshot data, martindale-hubbell scraper

# Actor input Schema

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

Paste any martindale.com URL — listing pages (`/{practice-area}-lawyers/{city}/{state}/`, `/areas-of-law/{practice-area}-lawyers/{state}`) and direct attorney URLs are auto-classified. Takes precedence over the dropdown filters below — clear this list to use the dropdowns. Pagination via `?page=N` is followed automatically when you paste a base URL.

## `searchByCategory` (type: `array`):

Select one or more practice areas. Combined with U.S. states + optional cities below as a cartesian product. Either form is accepted (`personal-injury` or `personal-injury-lawyers`) — the trailing `-lawyers` is added automatically.

## `searchByLocation` (type: `array`):

Select one or more US states (full state-name slugs). Empty = state-wide listings (one per practice area, very large response — typically 5MB+ per state).

## `searchByCity` (type: `array`):

Optional city slugs (e.g. `dallas`, `austin`, `san-antonio`). When set, narrows to `/{practice-area}-lawyers/{city}/{state}/`. Cities must be valid for the chosen state(s); Martindale uses lowercase-hyphenated city names.

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

Hard cap on the number of attorney rows collected across all listings. Each listing page yields ~30 attorneys; pagination auto-walks until this cap is hit or the source runs out of pages.

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

Maximum number of pages processed in parallel.

## `minConcurrency` (type: `integer`):

Minimum number of pages processed in parallel.

## `maxRequestRetries` (type: `integer`):

Number of retries before a failed request is given up.

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

Apify proxy settings. Martindale serves Cloudflare on every page — only the curl-impersonate Chrome 131 stack reliably passes the challenge. A US residential pool is strongly recommended.

## Actor input object example

```json
{
  "startUrls": [
    "https://www.martindale.com/personal-injury-lawyers/dallas/texas/"
  ],
  "searchByCategory": [
    "personal-injury-lawyers"
  ],
  "searchByLocation": [
    "texas"
  ],
  "searchByCity": [
    "dallas"
  ],
  "maxItems": 5000,
  "maxConcurrency": 5,
  "minConcurrency": 1,
  "maxRequestRetries": 5,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}
```

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "startUrls": [
        "https://www.martindale.com/personal-injury-lawyers/dallas/texas/"
    ],
    "searchByCategory": [
        "personal-injury-lawyers"
    ],
    "searchByLocation": [
        "texas"
    ],
    "searchByCity": [
        "dallas"
    ],
    "proxy": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "US"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("memo23/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": ["https://www.martindale.com/personal-injury-lawyers/dallas/texas/"],
    "searchByCategory": ["personal-injury-lawyers"],
    "searchByLocation": ["texas"],
    "searchByCity": ["dallas"],
    "proxy": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "US",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("memo23/martindale-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "startUrls": [
    "https://www.martindale.com/personal-injury-lawyers/dallas/texas/"
  ],
  "searchByCategory": [
    "personal-injury-lawyers"
  ],
  "searchByLocation": [
    "texas"
  ],
  "searchByCity": [
    "dallas"
  ],
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}' |
apify call memo23/martindale-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=memo23/martindale-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Martindale.com Lawyer Directory Scraper",
        "description": "Scrape US attorneys from Martindale.com — name, phone, full address with city/state/zip, firm website, headshot, and Martindale profile URL. 30 attorneys per listing page (no detail-page fetches needed). JSON/CSV out, billed per result.",
        "version": "0.1",
        "x-build-id": "B5IOxmXhVEuLJTc4a"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/memo23~martindale-scraper/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-memo23-martindale-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/memo23~martindale-scraper/runs": {
            "post": {
                "operationId": "runs-sync-memo23-martindale-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/memo23~martindale-scraper/run-sync": {
            "post": {
                "operationId": "run-sync-memo23-martindale-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "properties": {
                    "startUrls": {
                        "title": "Martindale URLs",
                        "type": "array",
                        "description": "Paste any martindale.com URL — listing pages (`/{practice-area}-lawyers/{city}/{state}/`, `/areas-of-law/{practice-area}-lawyers/{state}`) and direct attorney URLs are auto-classified. Takes precedence over the dropdown filters below — clear this list to use the dropdowns. Pagination via `?page=N` is followed automatically when you paste a base URL.",
                        "items": {
                            "type": "string"
                        }
                    },
                    "searchByCategory": {
                        "title": "Practice areas",
                        "uniqueItems": true,
                        "type": "array",
                        "description": "Select one or more practice areas. Combined with U.S. states + optional cities below as a cartesian product. Either form is accepted (`personal-injury` or `personal-injury-lawyers`) — the trailing `-lawyers` is added automatically.",
                        "items": {
                            "type": "string",
                            "enum": [
                                "personal-injury-lawyers",
                                "family-lawyers",
                                "divorce-lawyers",
                                "criminal-defense-lawyers",
                                "dui-lawyers",
                                "immigration-lawyers",
                                "bankruptcy-lawyers",
                                "estate-planning-lawyers",
                                "real-estate-lawyers",
                                "business-lawyers",
                                "employment-lawyers",
                                "workers-compensation-lawyers",
                                "medical-malpractice-lawyers",
                                "car-accident-lawyers",
                                "tax-lawyers",
                                "intellectual-property-lawyers",
                                "social-security-disability-lawyers",
                                "wrongful-death-lawyers",
                                "trusts-estates-lawyers",
                                "wills-lawyers"
                            ],
                            "enumTitles": [
                                "Personal Injury",
                                "Family Law",
                                "Divorce",
                                "Criminal Defense",
                                "DUI / DWI",
                                "Immigration",
                                "Bankruptcy",
                                "Estate Planning",
                                "Real Estate",
                                "Business / Corporate",
                                "Employment",
                                "Workers Compensation",
                                "Medical Malpractice",
                                "Car Accidents",
                                "Tax Law",
                                "Intellectual Property",
                                "Social Security Disability",
                                "Wrongful Death",
                                "Trusts & Estates",
                                "Wills"
                            ]
                        }
                    },
                    "searchByLocation": {
                        "title": "U.S. states",
                        "uniqueItems": true,
                        "type": "array",
                        "description": "Select one or more US states (full state-name slugs). Empty = state-wide listings (one per practice area, very large response — typically 5MB+ per state).",
                        "items": {
                            "type": "string",
                            "enum": [
                                "alabama",
                                "alaska",
                                "arizona",
                                "arkansas",
                                "california",
                                "colorado",
                                "connecticut",
                                "delaware",
                                "district-of-columbia",
                                "florida",
                                "georgia",
                                "hawaii",
                                "idaho",
                                "illinois",
                                "indiana",
                                "iowa",
                                "kansas",
                                "kentucky",
                                "louisiana",
                                "maine",
                                "maryland",
                                "massachusetts",
                                "michigan",
                                "minnesota",
                                "mississippi",
                                "missouri",
                                "montana",
                                "nebraska",
                                "nevada",
                                "new-hampshire",
                                "new-jersey",
                                "new-mexico",
                                "new-york",
                                "north-carolina",
                                "north-dakota",
                                "ohio",
                                "oklahoma",
                                "oregon",
                                "pennsylvania",
                                "rhode-island",
                                "south-carolina",
                                "south-dakota",
                                "tennessee",
                                "texas",
                                "utah",
                                "vermont",
                                "virginia",
                                "washington",
                                "west-virginia",
                                "wisconsin",
                                "wyoming"
                            ],
                            "enumTitles": [
                                "Alabama",
                                "Alaska",
                                "Arizona",
                                "Arkansas",
                                "California",
                                "Colorado",
                                "Connecticut",
                                "Delaware",
                                "Dist. of Columbia",
                                "Florida",
                                "Georgia",
                                "Hawaii",
                                "Idaho",
                                "Illinois",
                                "Indiana",
                                "Iowa",
                                "Kansas",
                                "Kentucky",
                                "Louisiana",
                                "Maine",
                                "Maryland",
                                "Massachusetts",
                                "Michigan",
                                "Minnesota",
                                "Mississippi",
                                "Missouri",
                                "Montana",
                                "Nebraska",
                                "Nevada",
                                "New Hampshire",
                                "New Jersey",
                                "New Mexico",
                                "New York",
                                "North Carolina",
                                "North Dakota",
                                "Ohio",
                                "Oklahoma",
                                "Oregon",
                                "Pennsylvania",
                                "Rhode Island",
                                "South Carolina",
                                "South Dakota",
                                "Tennessee",
                                "Texas",
                                "Utah",
                                "Vermont",
                                "Virginia",
                                "Washington",
                                "West Virginia",
                                "Wisconsin",
                                "Wyoming"
                            ]
                        }
                    },
                    "searchByCity": {
                        "title": "Cities (optional)",
                        "type": "array",
                        "description": "Optional city slugs (e.g. `dallas`, `austin`, `san-antonio`). When set, narrows to `/{practice-area}-lawyers/{city}/{state}/`. Cities must be valid for the chosen state(s); Martindale uses lowercase-hyphenated city names.",
                        "items": {
                            "type": "string"
                        }
                    },
                    "maxItems": {
                        "title": "Max items",
                        "minimum": 1,
                        "type": "integer",
                        "description": "Hard cap on the number of attorney rows collected across all listings. Each listing page yields ~30 attorneys; pagination auto-walks until this cap is hit or the source runs out of pages.",
                        "default": 5000
                    },
                    "maxConcurrency": {
                        "title": "Max concurrency",
                        "minimum": 1,
                        "type": "integer",
                        "description": "Maximum number of pages processed in parallel.",
                        "default": 5
                    },
                    "minConcurrency": {
                        "title": "Min concurrency",
                        "minimum": 1,
                        "type": "integer",
                        "description": "Minimum number of pages processed in parallel.",
                        "default": 1
                    },
                    "maxRequestRetries": {
                        "title": "Max request retries",
                        "minimum": 0,
                        "type": "integer",
                        "description": "Number of retries before a failed request is given up.",
                        "default": 5
                    },
                    "proxy": {
                        "title": "Proxy configuration",
                        "type": "object",
                        "description": "Apify proxy settings. Martindale serves Cloudflare on every page — only the curl-impersonate Chrome 131 stack reliably passes the challenge. A US residential pool is strongly recommended.",
                        "default": {
                            "useApifyProxy": true,
                            "apifyProxyGroups": [
                                "RESIDENTIAL"
                            ],
                            "apifyProxyCountry": "US"
                        }
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
