# Martindale Lawyer Directory Scraper (`automation-lab/martindale-lawyer-directory`) Actor

Export public US Martindale lawyer records from location directories and attorney profiles, including names, business phones, office addresses, firms and websites for legal-market research.

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

## Pricing

from $1.92 / 1,000 lawyer returneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Martindale Lawyer Directory Scraper

Refresh a public US **Martindale lawyer directory** into structured lawyer records for legal-market research. Supply location-directory URLs or individual attorney profiles and export names, business phones, office addresses, source links and listed websites. Optional profile visits add the listed firm and fuller professional details where available.

### Who is this for?

Legal-market researchers can refresh a city directory without copying each listing by hand. Business-development analysts can compare office locations and publicly listed business contacts across periodic snapshots. Data teams can join records by Martindale profile ID rather than a potentially ambiguous name.

This is a public professional directory extractor, not a verified licensing database, email finder or legal-advice service.

### Why use this Actor?

- Location pages can produce multiple lawyer records without visiting every profile.
- Explicit profiles and discovered records use the same US-only output contract.
- Canonical profile URLs deduplicate records across supplied inputs within each run.
- Source URLs and observation timestamps support downstream auditing.
- Optional detail visits retain the same lawyer row instead of creating duplicate entities.
- Denied or unrecognized pages fail clearly rather than becoming successful empty exports.

### Getting started

1. Open a US city directory on Martindale and copy its URL.
2. Add it to **Directory or attorney URLs**.
3. Leave **Maximum pages per directory** at 1 for a small initial snapshot.
4. Set a global **Maximum lawyers per run**.
5. Enable **Visit linked attorney profiles** if you need listed firm names.
6. Run the Actor and inspect its default dataset.
7. Download JSON, CSV or Excel using Apify's dataset export tools.

Example input:

```json
{
  "startUrls": [{"url": "https://www.martindale.com/all-lawyers/new-york/new-york/"}],
  "maxItems": 30,
  "maxPages": 1,
  "enrichProfiles": false
}
```

### Supported Martindale lawyer directory inputs

Use HTTPS URLs with either `www.martindale.com` or `martindale.com`:

- `/all-lawyers/<US-state>/<city>/`
- `/all-lawyers/<US-state>/<city>/?page=2`
- `/attorney/<name>-<numeric-id>/`

The state is its full lowercase hyphenated name, such as `new-york`. Washington, DC uses `district-of-columbia`. Firm pages, other countries, arbitrary search URLs, extra query filters and external domains are rejected. Linked profiles must publish a US office address. No location or practice-area search API is implied.

### Input parameters

| Parameter | Default | Meaning |
|---|---|---|
| `startUrls` | Required | 1–100 directory/profile URL objects |
| `maxItems` | 30 | 1–1000 unique lawyer rows globally across all inputs; Console prefill is 20 for a small first run |
| `maxPages` | 1 | 1–20 consecutive pages per supplied directory, including its starting page |
| `enrichProfiles` | false | Visit each accepted directory lawyer's exact profile before emitting its row |

Zero does not mean unlimited. The global row cap takes precedence over pagination. Explicit profile inputs always receive profile parsing, regardless of the enrichment toggle. Detail visits do not count against `maxPages`. Already visited page URLs are skipped within a run.

### Extracted fields

| Field | Meaning |
|---|---|
| `lawyerName` | Public professional name |
| `profileUrl` | Canonical Martindale attorney URL |
| `lawyerId` | Source's numeric ID, stored as a string |
| `phones` | Distinct listed business phone strings; possibly empty |
| `firm` | Listed firm; often null in directory-only mode |
| `website` | Listed external business website, or null; not visited |
| `address` | Street, city, region, postal code and country |
| `sourceUrl` | Directory or explicit profile that produced the record |
| `observedAt` | Extraction timestamp |
| `profileEnriched` | Whether exact-profile data was used |

Fields reflect what the source publishes, not independently verified facts. Tracking phone numbers and shared firm office addresses can appear. Optional or missing contact values remain null or empty rather than being inferred.

### Output example

The following illustrates the actual output shape with anonymized values:

```json
{
  "lawyerName": "Example Lawyer",
  "profileUrl": "https://www.martindale.com/attorney/example-lawyer-123/",
  "lawyerId": "123",
  "phones": ["212-555-0100"],
  "firm": null,
  "website": "https://example.com/",
  "address": {
    "street": "100 Example Avenue",
    "city": "New York",
    "region": "NY",
    "postalCode": "10001",
    "country": "US"
  },
  "sourceUrl": "https://www.martindale.com/all-lawyers/new-york/new-york/",
  "observedAt": "2026-10-27T14:00:00.000Z",
  "profileEnriched": false
}
```

The `RUN_SUMMARY` key-value-store record includes saved rows, page requests, retries and received response bytes. Failed runs may retain partial dataset rows; do not treat them as a completed snapshot.

### How much does it cost to extract Martindale lawyer records?

Billing uses one **start** event per run plus one **item** event per unique lawyer returned. Profile enrichment has no separate event charge. Consult the Actor's current Pricing tab for your Apify subscription tier's exact rates.

The start event is $0.005. Per-lawyer prices are $0.0036708 on FREE, $0.003192 on BRONZE, $0.0024898 on SILVER and $0.0019152 on GOLD, PLATINUM and DIAMOND. These tiers follow Apify's qualifying aggregate monthly Store spend, not this Actor's run volume.

For example, on BRONZE, 1 returned lawyer is approximately $0.008192, 30 lawyers $0.10076 and 100 lawyers $0.3242, including one start event. These are Actor-charge examples, not guaranteed invoices or payout forecasts. Duplicates and rejected records are not charged as items. A start charge can remain on an upstream-failed run. Use Apify's maximum-charge setting to control your spending; a billing cap can stop collection before your requested item cap.

### Pagination and coverage

The Actor follows only the next consecutive page link within the same supplied directory. Results depend on the source's current ordering, sponsored placements and available public structured records. Deduplication can make the output smaller than the number of visible cards. No nationwide completeness or exhaustive all-lawyers guarantee is offered.

Use the same input on later runs and compare profile IDs in your own database. The Actor does not store historical differences or send change alerts itself.

### Proxy, retries and performance

US residential Apify Proxy is used automatically for all source requests. Requests run sequentially. Each URL has at most three attempts; transient access/network failures use a fresh residential session with bounded backoff. Invalid and nonexistent sources are not blindly retried. No browser, images, scripts, contact forms or external business websites are fetched.

Directory-only mode minimizes requests. Enrichment visits one additional profile per accepted directory record and takes longer. Source changes and access controls may affect availability.

### Integrations

Send CSV exports to a spreadsheet for city-by-city legal-market analysis. Push dataset rows to a CRM only under your applicable contact/privacy rules. Use a scheduled Apify run plus a webhook or Make, Zapier or n8n workflow to load snapshots into your database; join on `lawyerId` and compare fields downstream. Apify provides scheduling and delivery; this Actor itself does not implement monitoring or alerts.

### API examples

Start a run once and retain its returned run ID:

```bash
curl -X POST 'https://api.apify.com/v2/acts/automation-lab~martindale-lawyer-directory/runs' \
  -H "Authorization: Bearer $APIFY_TOKEN" -H 'Content-Type: application/json' \
  -d '{"startUrls":[{"url":"https://www.martindale.com/all-lawyers/new-york/new-york/"}],"maxItems":30}'
```

JavaScript:

```js
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/martindale-lawyer-directory').call({
  startUrls: [{ url: 'https://www.martindale.com/all-lawyers/new-york/new-york/' }], maxItems: 30,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems({ limit: 30 });
```

Python:

```python
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('automation-lab/martindale-lawyer-directory').call(run_input={
    'startUrls': [{'url': 'https://www.martindale.com/all-lawyers/new-york/new-york/'}], 'maxItems': 30
})
rows = client.dataset(run['defaultDatasetId']).list_items(limit=30).items
```

### MCP integration

For clients supporting authenticated HTTP MCP, select this stable Actor-specific endpoint:

```bash
claude mcp add --transport http apify \
  'https://mcp.apify.com?tools=automation-lab/martindale-lawyer-directory'
```

Claude Desktop, Cursor and VS Code clients can use this equivalent configuration:

```json
{"mcpServers":{"apify":{"url":"https://mcp.apify.com?tools=automation-lab/martindale-lawyer-directory"}}}
```

Discover actual tool names and schemas with `tools/list`; Actor selection also includes run, dataset, key-value-store and abort tools. Discovery does not authorize execution. Example prompt: “Extract 30 public lawyers from this Martindale New York directory and show names, phone numbers and source URLs.”

For an authorized invocation, start once and retain the run/storage IDs. They are metadata, not rows. `call-actor` waits at most 45 seconds (`waitSecs`, default 30). If still running, check that same run with bounded 2/4/8/10-second backoff and a 120-second total deadline. Set client timeouts above the selected wait plus transport margin. On timeout recover the known run, never restart just to poll. At the deadline report pending and stop.

After success, read the returned dataset ID with explicit limit, offset and needed fields. An example context budget is pages of 20 source rows, at most 100 rows and 64 KiB of serialized result content. Enforce byte limits host-side before model injection, disclose omissions and retain source continuation offsets. If your client cannot intercept oversized responses, do not claim a hard byte guarantee. Keep full exports outside model context. Report partial/truncated results and terminal failures honestly. Tool caching and context savings depend on the client; no universal token reduction is promised.

### Legality, responsible use and diagnostics

Collect only public professional information for a lawful purpose. Respect the source's terms, applicable privacy laws and restrictions on outreach. Public availability does not establish consent for unsolicited marketing. This Actor is independent of Martindale-Hubbell and does not provide legal advice or guarantee current licensing.

The Actor does not use AI to process input or records. Apify hosts datasets, input and run logs under your account's storage retention settings; delete those through Apify Console/API. No cross-run cache is retained. Use the Actor's Apify Issues tab for support.

Failed operations send sanitized diagnostic input, exceptions and Actor/build/run IDs to our private GlitchTip service for repair. Secret fields and URL queries are removed and reports are retained for 30 days.

### FAQ and troubleshooting

**Can it find email addresses?** No. Hidden email, contact forms, lead purchasing and signed-in data are excluded.

**Does it cover Canada?** No. Only US directory paths and US-address profiles are supported.

**Why is firm null?** Directory structured data often omits it. Enable profile enrichment; missing profile values can still remain null.

**Why did the run fail with existing rows?** A later page or profile may have failed. Those rows are partial. Check the failed source and logs before using them as a full snapshot.

**Why fewer rows than requested?** The source may exhaust, repeat sponsored lawyers or omit supported structured records, or your page/billing limit may stop collection. An unrecognized or entirely empty page produces an explicit error rather than silent success.

### Related workflows

This Actor is intentionally source-specific and standalone. Use Apify's dataset exports, schedules and webhooks to integrate the records with your own research tools. It does not claim interchangeable coverage with other legal directories.

# Changelog

This Actor's version history is a separate document: https://apify.com/automation-lab/martindale-lawyer-directory/changelog.md

# Actor input Schema

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

Supply 1–100 public HTTPS martindale.com/all-lawyers/<US-state>/<city>/ directories or /attorney/<name>-<id>/ profiles. Only an optional positive page query parameter on directories is allowed. Canada, firm URLs, search filters and other domains are rejected. Profiles must list a US address.

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

Global cap on unique profile URLs across all inputs after deduplication. Default 30, range 1–1000; zero/unlimited is not supported. A directory fetch may contain more records than this cap; only accepted records are emitted and charged.

## `maxPages` (type: `integer`):

Directory page cap per supplied start URL, including its initial page. Default 1, range 1–20; follow only consecutive same-directory page links until exhaustion or the global maxItems cap. Explicit profiles use one page; optional enrichment requests do not count toward this page cap. Repeated page URLs are fetched once per run.

## `enrichProfiles` (type: `boolean`):

Default false returns directory-listed contacts/address/website; firm may be null. When true, visit each accepted lawyer's linked profile to obtain listed firm, full name, phones and address. Explicit profile inputs are always parsed in full. No separate enrichment charge, but failed profile retrieval fails the run with partial rows retained.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.martindale.com/all-lawyers/new-york/new-york/"
    }
  ],
  "maxItems": 20,
  "maxPages": 1,
  "enrichProfiles": false
}
```

# Actor output Schema

## `dataset` (type: `string`):

Unique US lawyer rows in the default dataset.

## `summary` (type: `string`):

Saved rows, page requests, retries and response bytes.

# API

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

## JavaScript example

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

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

// Prepare Actor input
const input = {
    "startUrls": [
        {
            "url": "https://www.martindale.com/all-lawyers/new-york/new-york/"
        }
    ],
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/martindale-lawyer-directory").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "startUrls": [{ "url": "https://www.martindale.com/all-lawyers/new-york/new-york/" }],
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/martindale-lawyer-directory").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{
  "startUrls": [
    {
      "url": "https://www.martindale.com/all-lawyers/new-york/new-york/"
    }
  ],
  "maxItems": 20
}' |
apify call automation-lab/martindale-lawyer-directory --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automation-lab/martindale-lawyer-directory"
        }
    }
}
```

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/vAaNrf2TFtBr32whN/builds/DEl1BHzs97uaZjSx6/openapi.json
