Miller Thomson APImillerthomson.com ↗
Access Miller Thomson lawyer profiles, contact details, practice areas, bar admissions, and office filters via 3 structured API endpoints.
What is the Miller Thomson API?
The Miller Thomson API exposes 3 endpoints covering the firm's full team directory at millerthomson.com, returning structured lawyer profiles with fields including email, phone numbers, bar admissions with parsed jurisdiction and year, practice areas, industries, and education. Use list_lawyers to page through or bulk-fetch the roster with filters for office, job title, expertise, and industry. Use get_lawyer to retrieve a single attorney's complete profile by slug.
curl -X GET 'https://api.parse.bot/scraper/c82933d6-1e33-4b28-a5bb-9a36f78882fa/list_lawyers?all=true&location=292' \ -H 'X-API-Key: $PARSE_API_KEY'
Typed, relational, agent-ready
A generated client with real types, enums, and the links between objects — the structure a flat JSON response can't carry. Autocompletes in your editor and reads cleanly to coding agents.
- Fully typed · autocompletes
- Objects link to objects
- Typed errors & pagination
Typed Python client. Set up the SDK in your uv project, then pull this API’s typed client:
uv add parse-sdk uv run parse init uv run parse add --marketplace millerthomson-com-api
uv run parse add --marketplace pulls a pinned snapshot of this canonical API — it won’t change underneath you. To customize it, subscribe and swap to your own copy.
"""Walkthrough: Miller Thomson team directory — browse offices, list lawyers, drill into a profile."""
from parse_apis.millerthomson_com_api import MillerThomson, LawyerNotFound
client = MillerThomson()
# Discover available filter options (offices, titles, expertise, industries).
catalog = client.filter_catalogs.get()
for office in catalog.location[:3]:
print(office.label, f"(id={office.id})")
# Pick the first office and list lawyers there.
office_id = catalog.location[0].id
for lawyer in client.lawyers.list(location=office_id, limit=5):
print(lawyer.name, "-", lawyer.title, "|", lawyer.office)
# Drill down: grab the first lawyer from a name search and fetch full profile.
hit = client.lawyers.list(search="Smith", limit=1).first()
if hit is not None:
try:
detail = client.lawyers.get(slug=hit.slug)
except LawyerNotFound:
print("Profile no longer available")
else:
print(detail.name, detail.title)
print("Email:", detail.email)
print("Practices:", ", ".join(detail.practices))
for admission in detail.bar_admissions or []:
print(f" Bar: {admission.jurisdiction} ({admission.year})")
print("exercised: filter_catalogs.get / lawyers.list / lawyers.get")
Returns team members matching the optional office, job-title, expertise, industry and free-text filters, each enriched from their profile page with email, phone numbers, offices, practice areas (practices), industries, bar admissions (with parsed jurisdiction and year), education and languages. Listing pages hold 12 members each. When all is false (default) the call fetches `pages` consecutive listing pages starting at `page` and reports has_more/total_pages for continuation; when all is true it walks every listing page (page and pages are ignored) and returns the whole filtered roster in one response. Each member costs one extra profile-page request; up to 300 profiles are enriched per call and any beyond that are returned with detail_fetched=false and counted in details_skipped, so callers fetching the whole firm (about 650 people) should filter by location or fetch in pages. Profiles that could not be loaded are listed in detail_failures. Filter values may be the numeric id or the exact label/slug from list_filter_options; a value matching no option returns an empty roster with a note. Non-lawyer staff appear too (empty bar_admissions); use job_title to narrow.
| Param | Type | Description |
|---|---|---|
| all | boolean | When true, walks every listing page and returns the entire filtered roster in one response, ignoring page and pages. |
| page | integer | First listing page to fetch (1-based, 12 members per page). Ignored when all is true. |
| pages | integer | Number of consecutive listing pages to fetch starting at page; capped at 60. Ignored when all is true. |
| search | string | Free-text search over the team directory (name keywords). Omitted = no text filter. |
| industry | string | Industry filter: id, label or slug from list_filter_options.industry. Omitted = all. |
| location | string | Office filter: id, label or slug from list_filter_options.location (e.g. 292 or Montréal). Omitted = all offices. |
| expertise | string | Practice-area filter: id, label or slug from list_filter_options.expertise (e.g. Tax). Omitted = all. |
| job_title | string | Job-title filter: id, label or slug from list_filter_options.job_title (e.g. Associate). Omitted = all titles. |
{
"type": "object",
"fields": {
"all": "boolean echo of the all mode",
"note": "string, present only when a filter value matched no option",
"page": "integer first listing page fetched",
"search": "string echo of the search input",
"filters": "object echoing the raw location/job_title/expertise/industry inputs",
"lawyers": "array of team members; each has name, slug (input for get_lawyer), profile_url, office (listing card office label), title, detail_fetched, and when detail_fetched is true: email (may be null), phones, offices, practices (practice areas), industries, bar_admissions (objects with text, jurisdiction, year), bar_admission_years, education, languages",
"has_more": "boolean, true when listing pages remain after the last one fetched",
"total_pages": "integer listing pages available for these filters",
"pages_fetched": "integer listing pages fetched in this call",
"total_results": "integer total members matching the filters as reported by the site",
"detail_failures": "array of {slug, name, upstream_status} for profiles that could not be loaded",
"details_fetched": "integer profiles enriched in this call",
"details_skipped": "integer members beyond the 300-profile enrichment cap"
},
"sample": {
"data": {
"all": false,
"page": 1,
"search": "",
"filters": {
"industry": "",
"location": "292",
"expertise": "",
"job_title": ""
},
"lawyers": [
{
"name": "John Doe",
"slug": "ayda-abedi",
"email": "[email protected]",
"title": "Partner",
"office": "Montréal",
"phones": [
"+1 (555) 012-3456"
],
"offices": [
"Montréal"
],
"education": [
"Bachelor of Laws, University of Montréal"
],
"languages": [
"English",
"French",
"Persian"
],
"practices": [
"Class Actions",
"Construction Litigation",
"Estates and Trusts Litigation",
"Insurance Defence"
],
"industries": [],
"profile_url": "https://www.millerthomson.com/en/your-team/ayda-abedi/",
"bar_admissions": [
{
"text": "Quebec, 2013",
"year": 2013,
"jurisdiction": "Quebec"
}
],
"detail_fetched": true,
"bar_admission_years": [
2013
]
}
],
"has_more": true,
"total_pages": 10,
"pages_fetched": 1,
"total_results": 109,
"detail_failures": [],
"details_fetched": 12,
"details_skipped": 0
},
"status": "success"
}
}About the Miller Thomson API
Endpoints and Data Coverage
The API covers three operations against the Miller Thomson team directory. list_filter_options returns the full filter vocabularies — location, job_title, expertise, and industry — each as an array of options with id, label, slug, and parent_id. Pass any of those values directly to list_lawyers as filter parameters. list_lawyers accepts up to five filters simultaneously (location, job_title, expertise, industry, search) and returns paginated results, 12 members per page by default. Setting all: true walks all listing pages and returns the complete filtered roster in a single response.
Profile Fields
Each entry in the list_lawyers response includes name, slug, profile_url, office, title, and detail fields pulled from the individual profile page: email, phones, offices, practices, industries, bar_admissions (with jurisdiction and year parsed out), and education. The slug field feeds directly into get_lawyer for a single-profile lookup returning the same field set plus languages.
Pagination and Bulk Fetching
list_lawyers exposes page, pages, total_pages, pages_fetched, has_more, and total_results to support incremental or bulk access. The pages parameter caps at 60 consecutive pages per call. When all is set to true, page and pages are ignored and the endpoint returns every matching member. The note field is present in the response only when a filter value matched no known option, making it straightforward to detect misconfigured filter inputs.
The Miller Thomson API is a managed, monitored endpoint for millerthomson.com — not a raw scraper you maintain. Every endpoint is automatically health-checked on a schedule, and when millerthomson.com changes and a check fails, the API is automatically queued for repair and re-verified. It is built to keep working as the site underneath it changes.
This isn't an official millerthomson.com API — it's an independent, maintained REST wrapper over public data. Where the source has no official API (or only a limited one), Parse gives you a stable contract over a source that never promised one, and keeps it current. Need a new endpoint or field? You can revise it yourself in plain English and the agent rebuilds it against the live site in minutes — contributing the change back to the shared API is free.
Will this API break when the source site changes?+
Is this an official API from the source site?+
Can I fix or extend this API myself if I need a new endpoint or field?+
What happens if I call an endpoint that has an issue?+
- Build a searchable internal directory of Miller Thomson lawyers filtered by practice area and office location.
- Extract bar admission data — jurisdiction and year — for all lawyers in a specific province or territory.
- Compile contact records (email, phone, office) for attorneys specializing in a given industry such as Energy or Technology.
- Enumerate all job titles in the firm using
list_filter_optionsto understand the organizational structure. - Cross-reference a lawyer's education and languages fields for multilingual client matching workflows.
- Monitor the firm's roster for new hires or role changes by periodically calling
list_lawyerswithall: true. - Populate a CRM with structured lawyer profiles including practices, industries, and offices from
get_lawyer.
| Tier | Price | Credits/month | Rate limit |
|---|---|---|---|
| Free | $0/mo | 200 | 5 req/min |
| Hobby | $30/mo | 1,000 | 20 req/min |
| Developer | $100/mo | 5,000 | 100 req/min |
| Team | $300/mo | 20,000 | 300 req/min |
| Company | $1,000/mo | 100,000 | 500 req/min |
Each endpoint has a fixed posted price per successful call — most fall between 1 and 10 credits — shown on this API's page before you run it. Exceeding the rate limit returns a 429 response. Authenticate with the X-API-Key header.
Does Miller Thomson publish an official developer API?+
How do filters work in `list_lawyers`, and where do the valid filter values come from?+
list_filter_options first to retrieve the current location, job_title, expertise, and industry vocabularies. Each option includes an id, label, and slug — any of those three forms can be passed to the corresponding list_lawyers parameter. If a value matches no known option, the response includes a note field flagging the mismatch.Is email always present in a lawyer profile?+
email field in both list_lawyers detail entries and get_lawyer responses can be null when the profile does not list one. The phones field is an array and may also be empty for some profiles.Does the API return a lawyer's biography or published articles?+
Can I retrieve all lawyers across the entire firm in one call?+
all: true in list_lawyers and the endpoint walks every listing page for the active filters, returning the complete roster in a single response. The pages parameter caps at 60 per call when not using all mode, and total_pages and has_more indicate how many pages remain.