# NMLS Consumer Access Scraper (`memo23/nmls-scraper`) Actor

Scrape NMLS Consumer Access for mortgage companies, MLOs, and branches — state licenses with statuses and dates, regulatory disclosures with PDF links, trade names, and sponsor/branch relationships. Search by name, NMLS ID, or state; CAPTCHA solved automatically. Clean JSON/CSV output.

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

## Pricing

from $1.00 / 1,000 record scrapeds

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

## NMLS Consumer Access Scraper

**Turn NMLS Consumer Access into structured mortgage-licensing data.** Scrape any company, individual (MLO), or branch from nmlsconsumeraccess.org — legal name, NMLS ID, addresses, every state license with status and dates, regulatory-action disclosures with PDF links, trade names, and sponsor/branch relationships. Search by name, look up NMLS IDs directly, or sweep a whole state. JSON or CSV out, priced per result.

#### How it works

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

#### ✨ Why use this scraper?

Vetting mortgage lenders? Building a state-by-state licensing dataset? Monitoring regulatory actions against loan originators? NMLS Consumer Access is the authoritative public registry — but it's gated behind a CAPTCHA and offers no export.

- 🎯 **Four seeding modes.** Search by company/person name, fetch exact NMLS IDs, sweep a state, or paste detail-page URLs — all classified automatically.
- 🤖 **CAPTCHA handled for you.** NMLS gates every page behind a BotDetect "Turing Test". The actor solves it once per session and reuses the unlocked session for every request in the run.
- 📜 **Full state-license tables.** Every license per entity: regulator, license name/number, status (`Approved`, `Voluntary Surrender`, …), original issue date, status date, renewal year, and trade names attached to that license. Large lenders return 90–180 license rows each.
- ⚖️ **Regulatory disclosures with PDF links.** Consent orders, final orders, and settlements — each with a direct document URL.
- 🔗 **Relationship expansion.** Optionally follow an individual's sponsoring companies and a company's branches (`maxSeedDepth`), so one seed can map an entire lender's footprint.
- 🏷 **Trade-name history.** Current and prior trade names plus prior legal names — track rebrands like Avex Funding → Better Mortgage.
- 📤 **Clean exports.** One row per entity, JSON + CSV, no duplicate rows within a run.

#### 🎯 Use cases

| Team | What they build |
|------|-----------------|
| **Lead generation / sales** | Contact lists of licensed mortgage companies and MLOs by state, with phone, email, and website |
| **Compliance & risk** | License-status monitoring and disclosure (regulatory-action) alerts on counterparties |
| **Fintech / lending platforms** | Partner-vetting pipelines keyed on NMLS ID with license coverage per state |
| **Market research** | State-by-state maps of active mortgage lenders, brokers, and servicers |
| **Recruiters** | MLO sourcing with sponsoring-company relationships |
| **Journalists / researchers** | Datasets on enforcement actions and license surrenders across the industry |

#### 📥 Supported inputs

Pick a `mode` and fill in the matching field:

| Mode | Input field | Behaviour |
|---|---|---|
| `by_name` | `names` | Runs an NMLS search per query. Add a city/state/ZIP to narrow (e.g. `Rocket Mortgage Detroit MI`) — NMLS rejects overly broad searches. |
| `by_id` | `nmlsIds` | Fetches each ID's detail page directly. Each ID is tried as company → individual → branch (or restricted via `entityType`). |
| `by_state` | `states` | Two-letter codes (`TX`, `CA`). Searches the whole state; pair with `entityType` to keep it focused. |
| `by_url` | `startUrls` | Exact NMLS URLs — detail pages like `https://www.nmlsconsumeraccess.org/EntityDetails.aspx/COMPANY/3030` or search URLs like `https://www.nmlsconsumeraccess.org/Home.aspx/SubSearch?searchText=...`. |

**Not supported:**

- Hosts outside `nmlsconsumeraccess.org`
- Federal-registry-only pages that have no standard entity detail layout

#### 🔄 How it works

1. **Solve the gate once.** NMLS redirects every request to a BotDetect CAPTCHA page. The actor solves it (CapSolver, with a local-OCR first attempt) and keeps the unlocked session cookie for the whole run.
2. **Seed** from your names / IDs / states / URLs — searches go through NMLS's own JSON search endpoint.
3. **Fetch each entity's detail page** in parallel over the shared session, re-solving transparently if the session ever expires.
4. **Parse** company / individual / branch layouts into one structured row each: identity, addresses, contacts, state licenses, disclosures, trade names.
5. **Optionally expand** to related entities — an individual's sponsoring companies, a company's branches — up to `maxSeedDepth` hops and `maxSeedExpansions` entities.
6. **Push one row per entity** to the dataset; export JSON/CSV from the Storage tab.

#### ⚙️ Input parameters

| Parameter | Type | Default | Description |
|---|---|---|---|
| `mode` | string | `by_name` | Seeding mode: `by_name` / `by_id` / `by_state` / `by_url`. |
| `names` | array | `["Rocket Mortgage Detroit MI"]` | Search queries for `by_name` mode. |
| `nmlsIds` | array | `["3030"]` | Exact NMLS IDs for `by_id` mode. |
| `states` | array | — | Two-letter state codes; the query in `by_state` mode, a filter otherwise. |
| `entityType` | string | `""` (any) | Restrict to `COMPANY` or `INDIVIDUAL`. |
| `startUrls` | array | — | Exact NMLS detail/search URLs for `by_url` mode. |
| `includeDisclosures` | boolean | `true` | Include regulatory-action documents in each row. |
| `maxSeedDepth` | integer | `0` | Relationship-expansion hops (0 = seeds only, max 3). |
| `maxSeedExpansions` | integer | `100` | Cap on entities added by expansion. |
| `maxItems` | integer | `1000` | Hard cap on rows collected (controls billing). |
| `maxConcurrency` | integer | `5` | Parallel detail fetches over the shared session. |
| `proxy` | object | Apify `RESIDENTIAL` | NMLS sits behind Cloudflare and blocks datacenter IPs — keep residential. |

Example — fetch two lenders by ID and map their branches:

```json
{
    "mode": "by_id",
    "nmlsIds": ["330511", "1850"],
    "maxSeedDepth": 1,
    "maxSeedExpansions": 50,
    "maxItems": 100
}
```

Example — all companies matching a name search:

```json
{
    "mode": "by_name",
    "names": ["Better Mortgage New York NY"],
    "entityType": "COMPANY",
    "maxItems": 20
}
```

#### 📊 Output overview

Each entity is one dataset row with `entityType` set to `COMPANY`, `INDIVIDUAL`, or `BRANCH`. All three share the same core shape (identity, addresses, `stateLicenses[]`, `disclosures[]`); companies add corporate fields (`businessStructure`, `sponsoredMlos`, trade-name history, `branchListUrl`), individuals add `employers[]` and sponsor references in `relatedRefs[]`.

#### 📦 Output sample

One company row, trimmed (this lender returns 93 license entries; two shown):

```json
{
    "nmlsId": "330511",
    "entityType": "COMPANY",
    "name": "Better Mortgage Corporation",
    "streetAddress": "1 World Trade Center 80th Floor New York, NY 10007",
    "mailingAddress": "1 World Trade Center 80th Floor New York, NY 10007",
    "phone": "415-523-8837",
    "email": "licensingteam@better.com",
    "website": ["www.better.com", "www.neohomeloans.com"],
    "otherTradeNames": ["Better Mortgage", "NEO Home Loans"],
    "priorOtherTradeNames": ["Avex Funding Corporation", "Better", "Better.com"],
    "priorLegalNames": ["Avex Funding Corporation"],
    "sponsoredMlos": "353",
    "fiscalYearEnd": "12/31",
    "formedIn": "California, United States",
    "dateFormed": "02/25/2003",
    "businessStructure": "Corporation",
    "regulatoryActions": "Yes",
    "stateLicenses": [
        {
            "regulator": "Arizona",
            "licenseName": "Mortgage Banker License",
            "authorizedToConductBusiness": "Yes",
            "licenseNumber": "BK-0942146",
            "originalIssueDate": "05/03/2017",
            "status": "Approved",
            "statusDate": "09/30/2025",
            "renewedThrough": "2026",
            "otherTradeNames": "Better Mortgage; NEO Home Loans"
        },
        {
            "regulator": "Alaska",
            "licenseName": "Mortgage Broker/Lender License - Other Trade Name #1",
            "authorizedToConductBusiness": "No",
            "licenseNumber": "AK330511",
            "originalIssueDate": "08/04/2020",
            "status": "Voluntary Surrender",
            "statusDate": "07/14/2022",
            "renewedThrough": "None"
        }
    ],
    "disclosures": [
        {
            "title": "Consent Order",
            "url": "https://www.nmlsconsumeraccess.org/EntityDetails.aspx/Artifact/Other.pdf?q=277502-368825"
        },
        {
            "title": "Settlement",
            "url": "https://www.nmlsconsumeraccess.org/EntityDetails.aspx/Artifact/Settlement.pdf?q=308059-398860"
        }
    ],
    "branchListUrl": "https://www.nmlsconsumeraccess.org/EntityDetails.aspx/Branches/330511",
    "sourceUrl": "https://www.nmlsconsumeraccess.org/EntityDetails.aspx/COMPANY/330511",
    "scrapedAt": "2026-07-22T21:32:55.719Z"
}
```

#### 🗂 Key output fields

| Group | Fields |
|---|---|
| **Identity** | `nmlsId`, `entityType` (`COMPANY` / `INDIVIDUAL` / `BRANCH`), `name`, `sourceUrl`, `scrapedAt` |
| **Contact** | `streetAddress`, `mailingAddress`, `phone`, `tollFreeNumber`, `fax`, `email`, `website[]` |
| **Names history** | `otherTradeNames[]`, `priorOtherTradeNames[]`, `priorLegalNames[]`, `otherNames[]` |
| **Corporate (companies)** | `businessStructure`, `formedIn`, `dateFormed`, `fiscalYearEnd`, `stockSymbol`, `sponsoredMlos`, `branchListUrl` |
| **Licenses** | `stateLicenses[].{regulator, licenseName, licenseNumber, authorizedToConductBusiness, originalIssueDate, status, statusDate, renewedThrough, otherTradeNames, authorizedToRepresent}` |
| **Disclosures** | `regulatoryActions`, `selfReportedDisciplinaryActions`, `disclosures[].{title, url}` (direct PDF links) |
| **Relationships** | `employers[]` (individuals), `relatedRefs[].{entityType, nmlsId, name}` (sponsors/branches found on the page) |

#### ❓ FAQ

**How does the actor get past the NMLS CAPTCHA?**
NMLS serves a BotDetect image CAPTCHA before any data page. The actor solves it automatically at run start and reuses the unlocked session for all requests, re-solving transparently if the session expires mid-run. Solving is built in — you don't configure anything.

**Which entity types are returned?**
Companies, individuals (MLOs), and branches. In `by_id` mode each ID is tried as company → individual → branch until one matches; use `entityType` to skip the guessing.

**Why do my name searches return nothing or fail?**
NMLS rejects overly broad queries (it caps result sets server-side). Narrow the search — add a city, state, or ZIP to the name, e.g. `Better Mortgage New York NY`.

**How do I get a company's branches or an MLO's employer?**
Set `maxSeedDepth: 1`. Companies expand to their branch entities (fetched from NMLS's branch-locations endpoint), individuals expand to their sponsoring companies. `maxSeedExpansions` caps the total.

**Do I need my own proxy?**
No — the default Apify residential proxy works. Note that NMLS blocks datacenter IPs, so switching the proxy group away from `RESIDENTIAL` will typically break the run.

**Can it access licensee-only or authenticated NMLS data?**
No. Only the public Consumer Access pages are scraped — no NMLS portal logins, no non-public data.

**How do I limit cost?**
Set `maxItems`. The actor stops queuing new entities once the cap is reached; billing is per result plus a flat per-run start fee.

#### 💬 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 other licensing registries or professional directories? Drop an email.
- For API access (no Apify fee, just a usage fee for the API): <muhamed.didovic@gmail.com>

#### 🔎 Explore more scrapers

If this NMLS scraper was useful, see other scrapers and actors at [memo23's Apify profile](https://apify.com/memo23) — covering job boards, real estate, directories, social media, and more.

#### 🤖 For AI Agents & LLM Apps

Quick reference for agents calling this actor via the Apify API or MCP:

- **Purpose:** look up US mortgage companies, loan originators (MLOs), and branches in the NMLS Consumer Access registry, with state licenses and regulatory disclosures.
- **Minimal input (tested):**

```json
{ "mode": "by_id", "nmlsIds": ["330511"], "maxItems": 1 }
```

- **Key output fields (flat list):** `nmlsId`, `entityType`, `name`, `streetAddress`, `phone`, `email`, `website`, `otherTradeNames`, `businessStructure`, `sponsoredMlos`, `stateLicenses` (array of license objects), `disclosures` (array of `{title, url}`), `sourceUrl`.
- **Billing:** pay-per-event — a flat fee per run start plus a fee per dataset row. Cap spend with `maxItems`.
- **Behaviors to know:** the actor solves NMLS's CAPTCHA itself (adds ~10–60 s at run start); requires residential proxy (default is correct); broad name searches are rejected by NMLS, so include a city/state in `names`; one row per entity, `entityType` tells you which parser produced it.

***

### ⚠️ Disclaimer

This Actor is an independent tool and is not affiliated with, endorsed by, or sponsored by the Nationwide Multistate Licensing System (NMLS), the State Regulatory Registry LLC (SRR), the Conference of State Bank Supervisors (CSBS), or any of their subsidiaries or affiliates. All trademarks mentioned are the property of their respective owners.

The scraper accesses only publicly available pages on nmlsconsumeraccess.org — the same consumer-facing registry data anyone can view in a browser. No authenticated NMLS portal content, licensee-only features, or non-public data is accessed. Users are responsible for ensuring their use complies with nmlsconsumeraccess.org's Terms of Use, applicable data-protection law (GDPR, CCPA, etc.), and any contractual obligations of their own organisation.

***

### SEO Keywords

nmls scraper, nmls consumer access scraper, scrape nmls, nmls api, nmls data export, nmls license lookup api, mortgage license data, mortgage company license scraper, mlo license scraper, mortgage loan originator data, nmls id lookup, state mortgage license database, mortgage lender verification, mortgage broker license data, loan officer license lookup, mortgage compliance data, nmls regulatory actions data, mortgage industry dataset, mortgage lead generation data, nmls branch locations data, mortgage licensing api alternative

# Actor input Schema

## `mode` (type: `string`):

How the crawl is seeded. `By name` searches NMLS for each query. `By NMLS ID` fetches specific IDs directly. `By state` searches a whole state (pair with entity type). `By URL` crawls exact detail/search URLs you paste below.

## `names` (type: `array`):

Company or person names to search (mode = By name). Add a city, state, or ZIP to narrow — NMLS rejects overly broad searches. Example: `Rocket Mortgage Detroit MI`.

## `nmlsIds` (type: `array`):

Exact NMLS ID numbers to fetch directly (mode = By NMLS ID). Each is tried as a company, individual, then branch (or restricted by the Entity type below).

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

Two-letter US state codes (e.g. `TX`, `CA`). Used as the query in By-state mode, or as a filter in other modes.

## `entityType` (type: `string`):

Restrict results to companies or individuals (MLOs). Leave as `Any` to include both.

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

Exact nmlsconsumeraccess.org URLs (mode = By URL) — detail pages like `/EntityDetails.aspx/COMPANY/3030` or search URLs `/Home.aspx/SubSearch?searchText=...`.

## `includeDisclosures` (type: `boolean`):

Include the disclosures section (regulatory action documents) in each row.

## `maxSeedDepth` (type: `integer`):

Follow relationships out from each seed entity: an individual's sponsoring companies and a company's branches. 0 = seeds only, 1 = seeds + their direct relations, etc.

## `maxSeedExpansions` (type: `integer`):

Hard cap on how many related entities the expansion may add, so a broad seed can't balloon the run.

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

Hard cap on the number of entities collected (controls billing).

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

Maximum number of detail pages fetched in parallel over the shared session.

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

NMLS is behind Cloudflare and blocks datacenter IPs — residential proxies are required. Defaults to Apify RESIDENTIAL.

## Actor input object example

```json
{
  "mode": "by_name",
  "names": [
    "Rocket Mortgage Detroit MI"
  ],
  "nmlsIds": [
    "3030"
  ],
  "entityType": "",
  "includeDisclosures": true,
  "maxSeedDepth": 0,
  "maxSeedExpansions": 100,
  "maxItems": 1000,
  "maxConcurrency": 5,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# 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 = {
    "names": [
        "Rocket Mortgage Detroit MI"
    ],
    "nmlsIds": [
        "3030"
    ],
    "proxy": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("memo23/nmls-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 = {
    "names": ["Rocket Mortgage Detroit MI"],
    "nmlsIds": ["3030"],
    "proxy": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("memo23/nmls-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 '{
  "names": [
    "Rocket Mortgage Detroit MI"
  ],
  "nmlsIds": [
    "3030"
  ],
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call memo23/nmls-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,memo23/nmls-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/htzLi3hodwIDZHDR5/builds/0Q2pWb0yudcV1FhGO/openapi.json
