# Alignable Business Scraper — SMB Leads: Phone, Address, Site (`scrapersdelight/alignable-scraper`) Actor

Scrape every Alignable business in a city into SMB leads: name, full postal address, phone, website and industry tags. Enumerated from Alignable's own sitemap, so you get the whole city, not page one. Optional industry filter. No login. $2 per 1,000. Export JSON/CSV.

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

## Pricing

$2.00 / 1,000 per business 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/actors/running/actors-in-store.md#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

## Alignable Business Scraper — SMB Leads: Phone, Address, Site

Turn [Alignable](https://www.alignable.com) — the US small-business network — into a flat SMB lead
list. **One row per business**, carrying **businessId, name, url, geo\_id, business\_slug,
streetAddress, addressLocality, addressRegion, postalCode, telephone, website, categories, image,
has\_structured\_data and scraped\_at**. Scope it by city (paste the Alignable slug, `"Austin, TX"`, or
any Alignable URL) and optionally keep only the industries you want. **No login. No cookies. No
CAPTCHA solving. No browser.**

**Alignable's own sitemap lists 1,765,937 business profiles across 26,111 city slugs** — counted
live on 2026-08-12 by fetching all 36 gzipped shards through Apify, not quoted from a marketing
page. In a 400-profile Scottsdale walk, **70.0% of rows carried a full postal address and 62.2%
carried a phone number**. Click **Try for free** and hit **Start** — the Actor ships prefilled and
returns rows with zero edits.

***

### Quick start

This is literally the input the Actor ships with. Three entries, all the same city, showing the
three accepted formats — they normalise and deduplicate to one city before anything is fetched:

```json
{
  "citySlugs": [
    "scottsdale-az",
    "Scottsdale, AZ",
    "https://www.alignable.com/scottsdale-az/mirage-limousines"
  ]
}
```

Everything else defaults: `maxBusinessesPerCity` 200, no industry filter, Apify datacenter proxy.
Run exactly as shipped, three times, on Apify on 2026-08-12: **200 businesses in 43, 47 and 104 seconds**,
0 failures both times, at a measured fill of phone 72%, address 79%, website 75%.

**How to use it**

1. Click **Try for free**, then **Start**. You get 200 Scottsdale businesses.
2. Replace `citySlugs` with the cities you actually sell into.
3. Optionally add `categoryFilter` words (`"real estate"`, `"insurance"`) to keep only those industries.
4. Set `maxBusinessesPerCity` to `0` when you want the whole city.
5. Export the dataset as JSON, CSV, Excel or XML, or pull it from the API.

***

### The wedge: phone and a full postal address, on the whole city

Two things separate this Actor from the other Alignable scrapers in the store, and both are checkable.

**1. It reads the whole city, not the first directory page.** Alignable killed guest directory
pagination — `/{city}/directory` returns `410` past page 1 — so a scraper that reads the directory
card list gets one page. This Actor enumerates each city out of Alignable's **own public
`sitemap.xml` → `businesses*.xml.gz` shards**, which is the complete guest-visible index. Proof: the
sitemap holds 248 profile URLs under `boston-ma`, and a full uncapped Boston run enumerated and
returned exactly **248 businesses** — the entire city slug, independently counted twice.

**2. It returns telephone and a full street address.** The competing
`jungle_synthesizer/alignable-small-business-scraper` states in its own README that these fields are
impossible: *"Those fields lived only on individual profile detail pages, and Alignable now blocks
anonymous access to them."* That is not what the site does. The profile pages are public, they
return HTTP 200 to an anonymous request, and **claimed** profiles carry a schema.org `LocalBusiness`
JSON-LD block with the address and phone in it. Measured on 1,600 profiles across four cities today:
**62.2% of rows carried a structured address and 52.4% carried a phone.** Among the 996 claimed
profiles in that sample, `postalCode` was **100%** filled and `telephone` **84.2%**.

Here is a real row from that run, unedited:

```json
{
  "name": "LifeTyme Financial Group, LLC.",
  "streetAddress": "14362 N Frank Lloyd Wright Blvd Suite 1240",
  "addressLocality": "Scottsdale",
  "addressRegion": "AZ",
  "postalCode": "85260",
  "telephone": "602-774-4735",
  "website": "www.ltfusa.com"
}
```

***

### Read this before you buy rows

1. **Fill depends on the city slug, and the spread is enormous.** Alignable only publishes the
   structured block on **claimed** profiles. Claimed share measured today: `scottsdale-az` 70.0%,
   `miami-fl` 66.8%, `raleigh-nc` 63.2%, `austin-tx` 49.0% — and `boston-ma` **3.2%**. Check the
   fill tables below before you plan a run against a small slug.
2. **Slug size is the tell.** The four slugs with 1,900–5,500 profiles in the sitemap returned
   49–70% claimed. `boston-ma` has only 248 profiles in the whole sitemap and returned 3.2%. Big
   Alignable city slugs are live communities; small ones are mostly abandoned shells. Sizes for
   the cities measured: `san-antonio-tx` 7,686 · `raleigh-nc` 5,476 · `miami-fl` 5,059 ·
   `scottsdale-az` 4,788 · `austin-tx` 1,926 · `denver-co` 936 · `boston-ma` 248.
3. **`maxBusinessesPerCity` takes the first N in sitemap order, not the best N.** Sitemap order
   carries no quality ranking, so a 200-row sample of a 4,788-profile city is an arbitrary slice,
   not the top of the city. If you want the city, set it to `0`.
4. **`website` is whatever the owner typed.** 348 of the 914 websites in this sample (38.1%) arrived
   without an `http://` prefix (`www.ltfusa.com`). Prepend a scheme before you fetch them.
5. **`telephone` is a free-text field the owner typed, not a normalised number.** The 841 phone
   numbers in this sample arrived in **42 distinct shapes**: `(###) ###-####` (631),
   `###-###-####` (94), bare `##########` (60), `###.###.####` (8), and a long tail that includes
   two numbers in one string (`O (###) ###-#### C (###) ###-####`), vanity numbers
   (`###-WOW-OILS`), extensions, country prefixes, and one row whose phone is literally the string
   `<<not-applicable>>`. Parse defensively before you dial or import.
6. **There is no owner name and no e-mail address in the output.** See *Honest limits*.

***

### What you get — one row per business

| Field | Type | Notes |
|---|---|---|
| `businessId` | integer | Stable Alignable numeric id. **This is the dedupe key.** |
| `name` | string | Business name (from JSON-LD, falling back to the page `<title>`). |
| `url` | string | Canonical public profile URL. |
| `geo_id` | string | City slug the profile lives under, e.g. `scottsdale-az`. |
| `business_slug` | string | Profile slug from the URL. |
| `streetAddress` | string | null | Street line. Claimed profiles only. |
| `addressLocality` | string | null | City as Alignable stores it (sometimes upper-cased, e.g. `SCOTTSDALE`). |
| `addressRegion` | string | null | Two-letter state / province code. |
| `postalCode` | string | null | ZIP / postal code. |
| `telephone` | string | null | Phone, in the owner's own formatting. |
| `website` | string | null | Business website, from JSON-LD `sameAs`. Often has no `http://` prefix. |
| `categories` | array of strings | Alignable industry + service tags. 5.8 per row on average. |
| `image` | string | null | Logo image URL on `pictures.alignable.com`. |
| `has_structured_data` | boolean | `true` when the profile carried the schema.org block. **This is the claimed/unclaimed flag** and it predicts every nullable field above. |
| `scraped_at` | string | ISO-8601 UTC timestamp of the fetch. |

`null` means Alignable did not publish that field on that profile. It never means "we failed to
read it" — a failed fetch is logged and counted, and a run that produces zero rows throws.

***

### Field fill — measured on 1,848 live profiles, 2026-08-12

Five runs on Apify: 400 contiguous sitemap profiles each for Scottsdale, Miami, Raleigh and Austin,
plus every one of the 248 profiles under `boston-ma`. 1,848 profile fetches, **0 failures**.

**Four established city slugs, 1,600 rows combined:**

| Field | Fill |
|---|---|
| `businessId`, `name`, `url`, `geo_id`, `business_slug`, `has_structured_data`, `scraped_at` | **100.0%** |
| `categories` | 92.8% |
| `addressLocality` / `addressRegion` / `postalCode` | 62.2% |
| **`streetAddress`** | **62.1%** |
| `image` | 57.6% |
| **`website`** | **56.8%** |
| **`telephone`** | **52.4%** |

**The same 1,600 rows, restricted to the 996 claimed profiles (62.2% of the sample):**

| Field | Fill among claimed |
|---|---|
| `addressLocality`, `addressRegion`, `postalCode` | 100.0% |
| `streetAddress` | 99.8% |
| `image` | 92.5% |
| `website` | 91.3% |
| `telephone` | 84.2% |

**Per city — this is the table to plan against:**

| City slug | Profiles in sitemap | Rows measured | Claimed | Address | Phone | Website |
|---|---|---|---|---|---|---|
| `scottsdale-az` | 4,788 | 400 | 70.0% | 70.0% | 62.2% | 65.2% |
| `miami-fl` | 5,059 | 400 | 66.8% | 66.5% | 54.5% | 59.2% |
| `raleigh-nc` | 5,476 | 400 | 63.2% | 63.0% | 56.8% | 59.0% |
| `austin-tx` | 1,926 | 400 | 49.0% | 49.0% | 36.2% | 43.8% |
| `boston-ma` | 248 | 248 (all) | **3.2%** | **3.2%** | **0.8%** | **2.0%** |

**The headline that could mislead you: this is not a 100%-phone product, and on a thin city slug it
is barely a phone product at all.** Boston returned 2 phone numbers out of 248 businesses. That is
Alignable's data, not a scraping failure — the same pages return the same emptiness in a browser.
Every run prints its own measured fill in the log (`Fill — phone 62%, address 70%, website 65%`), so
you can see what a city is worth before you scale the run up.

***

### How to run it

**One city, capped (the default shape)**

```json
{ "citySlugs": ["austin-tx"], "maxBusinessesPerCity": 200 }
```

**Several cities at once** — the cap applies per city, so this is up to 600 rows:

```json
{ "citySlugs": ["scottsdale-az", "miami-fl", "raleigh-nc"], "maxBusinessesPerCity": 200 }
```

**Paste a URL instead of learning the slug format** — any Alignable URL works, the city is taken
from the path:

```json
{ "citySlugs": ["https://www.alignable.com/scottsdale-az/mirage-limousines", "Denver, CO"] }
```

**One industry across several cities** — the filter is a case-insensitive substring match against
the category tags, OR-ed together:

```json
{
  "citySlugs": ["scottsdale-az", "miami-fl"],
  "categoryFilter": ["real estate", "mortgage"],
  "maxBusinessesPerCity": 0
}
```

**The whole city** — `0` means no cap:

```json
{ "citySlugs": ["boston-ma"], "maxBusinessesPerCity": 0 }
```

Cost note on the uncapped mode: it forces a scan of all 36 sitemap shards, which took **173 seconds**
on Apify before the first profile was fetched. A capped run stops scanning as soon as it has enough
URLs — Scottsdale's 400 were found in 4 seconds because that city sits in the first two shards.

***

### Sample rows

A claimed profile (`has_structured_data: true`), exactly as it came out of the 2026-08-12 run:

```jsonc
{
  "businessId": 14593,
  "geo_id": "scottsdale-az",
  "business_slug": "lifetyme-financial-group-llc-2",

  "name": "LifeTyme Financial Group, LLC.",
  "url": "https://www.alignable.com/scottsdale-az/lifetyme-financial-group-llc-2",

  "streetAddress": "14362 N Frank Lloyd Wright Blvd Suite 1240",
  "addressLocality": "Scottsdale",
  "addressRegion": "AZ",
  "postalCode": "85260",

  "telephone": "602-774-4735",
  "website": "www.ltfusa.com",          // note: no scheme — 38.1% arrive this way

  "categories": [
    "Financial Coach", "Finance", "Life Insurance", "Budgeting",
    "Financial Planning", "Business Development", "Retirement Planning",
    "Free Consultation", "Financial Literacy"
  ],
  "image": "https://pictures.alignable.com/eyJidWNrZXQiOiJhbGlnbmFibGV3ZWItcHJvZHVjdGlvbiIsImtleSI6...",

  "has_structured_data": true,
  "scraped_at": "2026-08-13T02:10:20.984Z"
}
```

An unclaimed profile from the same Scottsdale run — this is what 30.0% of those 400 rows looked like:

```jsonc
{
  "businessId": 19983,
  "geo_id": "scottsdale-az",
  "business_slug": "scottsdale-peggy-richards-massage-therapy-and-rolfing-and-day-spa",

  "name": "Scottsdale Peggy Richards Massage Therapy and Rolf Method of SI and Make-up Artistry",
  "url": "https://www.alignable.com/scottsdale-az/scottsdale-peggy-richards-massage-therapy-and-rolfing-and-day-spa",

  "streetAddress": null,
  "addressLocality": null,
  "addressRegion": null,
  "postalCode": null,
  "telephone": null,
  "website": null,

  "categories": [
    "Massage Therapist", "Therapy", "Photo", "Photography",
    "Certificate", "Party", "Body", "Wedding", "Hair", "Makeup"
  ],
  "image": null,

  "has_structured_data": false,
  "scraped_at": "2026-08-13T02:10:21.923Z"
}
```

Things people misread in these rows:

- `has_structured_data: false` does **not** mean the business is fake or the fetch failed. It means
  nobody has claimed the profile, so Alignable renders no schema.org block. Name, id, URL and
  category tags are still there — 100% of unclaimed rows in this sample had a name and an id.
- `categories` mixes industry labels (`Financial Coach`) with service and keyword tags
  (`Free Consultation`, `Budgeting`) in one array. 3,160 distinct tag strings appeared across 1,848
  rows. Filter on substrings, not on an enum.
- `addressLocality` is the city as the owner typed it, which is sometimes upper-case (`SCOTTSDALE`).
  `geo_id` is the canonical slug — group on `geo_id`, display `addressLocality`.
- 6.7% of rows (124 of 1,848) carried no category tags at all, so `categoryFilter` will always drop
  those rows.

***

### Input

Fields are listed in the order they appear in the Console.

| Field | Type | Default | What it does |
|---|---|---|---|
| `citySlugs` | array | prefilled with three forms of `scottsdale-az` | The cities to scrape. Accepts the slug (`austin-tx`), the human form (`"Austin, TX"`), or any Alignable URL; all three are normalised to the same slug and deduplicated. The three prefilled entries are deliberately the same city, shown to demonstrate the formats. |
| `categoryFilter` | array | `[]` | Case-insensitive substring match against `categories`, OR-ed. Applied **after** the profile is fetched, so it costs the Actor time but never costs you money — filtered rows are not pushed and not charged. |
| `maxBusinessesPerCity` | integer | `200` | Cap per city, and your cost ceiling: 200 businesses = $0.40. `0` = the whole city. Duplicates are dropped before billing and never count against it. |
| `requestConcurrency` | integer | `5` | Parallel profile fetches, 1–10. At 5, the fetch phase of a 400-profile city took 78–194 seconds with 0 failures (92–228 seconds of total run time, sitemap scan included). |
| `requestDelayMs` | integer | `250` | Politeness pause per worker after each profile. 0 blocks were seen at this setting across 1,848 fetches. |
| `proxyConfiguration` | object | Apify datacenter | Leave it. Datacenter measured 100% (see the transport table). |

#### Finding a city slug

The slug is what appears in the URL: `alignable.com/austin-tx` → `austin-tx`. It is lowercase city
name, a dash, then the two-letter state or province code. Multi-word cities use dashes
(`fort-lauderdale-fl`, `oklahoma-city-ok`), and some large metros are split into neighbourhood slugs
(`midtown-new-york-ny` carries 3,271 profiles while `new-york-ny` carries 883). If a slug has no
profiles in the sitemap, the run stops with an error naming the city instead of quietly returning
nothing.

The ten largest city slugs measured on 2026-08-12: `san-antonio-tx` (7,686), `orlando-fl` (6,060),
`raleigh-nc` (5,476), `indianapolis-in` (5,364), `colorado-springs-co` (5,212), `miami-fl` (5,059),
`tampa-fl` (4,957), `scottsdale-az` (4,788), `fort-worth-tx` (4,664), `mississauga-on` (4,379).

Of the 26,111 city slugs in the sitemap: 259 hold 1,000+ profiles, 3,407 hold 100–999, 8,357 hold
10–99, 9,358 hold 2–9, and 4,730 hold exactly one.

***

### Pricing

**$0.002 per business returned — $2 per 1,000.** Charged on the `business-scraped` event. There is
no actor-start fee and no monthly platform fee from this Actor.

You are charged for rows delivered. You are **not** charged for a profile that was fetched and then
removed by `categoryFilter`, not charged for retries or proxy traffic, not charged for a failed
fetch, and never charged twice for the same `businessId` — duplicates are dropped before billing.
Rows are charged as they are pushed (`Actor.pushData(items, 'business-scraped')`), so if you hit a
budget cap you get whole rows and stop, not a half-billed dataset.

| Run | Businesses | Cost |
|---|---|---|
| The prefilled default run | 200 | $0.40 |
| Three cities at the default cap | 600 | $1.20 |
| The whole of `boston-ma` | 248 | $0.50 |
| The whole of `scottsdale-az` | 4,788 | $9.58 |
| The whole of `san-antonio-tx`, the largest city slug | 7,686 | $15.37 |

`maxBusinessesPerCity` is your hard cost cap. Set it to `0` only when you have priced the city — the
per-city sizes are listed above.

***

### Honest limits

- **No e-mail addresses.** Alignable publishes none on the public profile, for any business. There
  is no `email` field and nothing in this Actor can produce one. If you need e-mail, take the
  `website` field (56.8% of rows) and run it through `local-business-enricher`, which crawls a site
  for contact addresses.
- **No owner name.** `jungle_synthesizer/alignable-small-business-scraper` returns one and calls it
  "the reason this scraper exists"; this Actor does not return it today. That is a real gap in our
  output, not a claim that the data does not exist — say so if you need it and it goes on the
  build list.
- **No connections count, no "years in business", no rating, no social links.** Another store
  listing advertises those fields with a sample record whose phone number is `+1-512-555-0123`. This
  Actor ships only fields it read off a live page, and those five are not among them.
- **A thin city slug is mostly empty rows.** `boston-ma`: 248 businesses, 8 with an address, 2 with
  a phone. Bought blind that is a bad purchase, which is why the number is in the fill table and not
  in a footnote. Run a capped 200-row probe on a new city first — it costs $0.40 and the log prints
  the fill.
- **Within-run dedupe only.** A run deduplicates on `businessId`, so one run never bills you twice
  for the same business. Across runs there is no resume cursor: running `austin-tx` twice returns
  the same businesses again. Deduplicate on `businessId` on your side, or keep one city per dataset.
- **No ranking and no "newest first" mode.** The sitemap is not ordered by quality. It *does* carry
  a `<lastmod>` timestamp on every entry (`2025-12-11T17:28:52-05:00` on the first one in shard 1),
  but this Actor neither returns that value nor sorts on it today, so a capped run is an arbitrary
  slice of the city. That is a gap in this Actor, not in the source — file an issue if you want it.
- **US and Canada in practice.** The slug format is city + state/province code, and Canadian slugs
  such as `mississauga-on` (4,379) and `toronto-on` (367) are present. Other locales are not.
- **It does not log in.** Checked again on 2026-08-12: `/{city}/directory` returns HTTP 200 on page
  one and **HTTP 410 on `?page=2`**, and `/biz/search` **302-redirects to `/biz_users/sign_in`**.
  Those are the two surfaces a login would buy you, and this Actor touches neither. There is no
  workaround here that does not involve an account.

***

### How it works, and what was measured

1. Fetch `https://www.alignable.com/sitemap.xml` — the gzipped sitemap index that Alignable's own
   `robots.txt` advertises — and read the 36 `businesses*.xml.gz` shard URLs out of it.
2. Stream each shard, gunzip it, and keep the `<loc>` entries whose path starts with your city
   slug. Shards are **not** globally sorted and a city can straddle a shard boundary, so a capped
   run stops scanning the moment every city has enough URLs and an uncapped run reads all 36.
3. Fetch each profile page over plain HTTP — no browser, no JS execution — and parse the schema.org
   `LocalBusiness` JSON-LD block plus the category tag anchors out of the server-rendered HTML.
4. Deduplicate on `businessId` **before** anything is pushed, then push and charge row by row.

#### Transport, measured through Apify on 2026-08-12

| Rung | Result |
|---|---|
| Apify datacenter proxy (the default) → `sitemap.xml` | HTTP 200 |
| Apify datacenter proxy → all 36 gzipped shards | 36/36 HTTP 200, 1,765,937 URLs parsed |
| Apify datacenter proxy → profile pages, 5 runs | **1,848/1,848 HTTP 200. 0 failures, 0 warnings logged.** |
| Apify RESIDENTIAL, pinned session → profile pages | 20/20 usable — no better, and slower (36s vs 22s for the same 20 pages) |

There is no ladder to climb here: **Alignable is not walled.** No Cloudflare challenge, no
JavaScript gate, no cookie minting, no CAPTCHA. That is why the proxy default is the cheap
datacenter rung and why the Actor runs at 1 GB of memory without a browser. Every number in this
table was measured through an Apify proxy; no home-broadband result is reported anywhere on this
page.

#### Uniqueness, measured on contiguous walks

Sparse sampling proves nothing about duplicates, so the walks were contiguous — the first 400
`<loc>` entries a city has in sitemap order, and for Boston every entry it has.

- 1,848 rows returned → **1,848 distinct `businessId` values and 1,848 distinct URLs. 0 duplicates.**
- Across the full sitemap: 1,765,937 `<loc>` entries, **0 of them repeated**.
- `boston-ma` enumerated 248 URLs and returned 248 rows — the sitemap count and the run agree
  exactly, which is the coverage proof for the whole-city mode.

Deduplication happens on `businessId` before the row is pushed, so the dataset — and your bill —
never holds the same business twice within a run.

***

### When a run fails

- **Zero rows is always an error, never a green empty dataset.** If nothing survives, the run throws
  with the counts: `No businesses emitted (N fetched, N filtered out, N failed) — failing rather
  than pushing 0 rows.`
- **A bad city slug stops the run** with `No business URLs found for <slug> — check the city
  slug(s).` rather than returning an empty dataset that looks like "this city has no businesses".
- **A failed profile fetch is retried up to five times** with a fresh proxy session and a backoff,
  and is counted in the final log line if it still fails. It is never billed.
- Every run ends with a line you can audit: `Done. N businesses pushed. Fill — phone X%, address
  Y%, website Z%. (F filtered, E failed.)`

***

### Who buys this

- **SMB SaaS and fintech sellers (POS, payroll, bookkeeping, lending)** — `categories` plus
  `postalCode` gives you a territory-and-vertical list; `telephone` is the channel.
- **Local marketing and web agencies** — the businesses with `website: null` in a city you serve are
  a pre-qualified "no website" prospect list; in Scottsdale that was 34.8% of 400 rows.
- **Insurance brokers and commercial-property services** — filter `categoryFilter` to
  `["insurance", "real estate", "contractor"]` and you get an addressable book with street
  addresses attached.
- **Sales-intelligence and data vendors** — `businessId` is a stable join key and `has_structured_data`
  tells you which rows are worth enriching before you spend enrichment credits on them.
- **Field sales teams** — 62.2% of rows in an established city carry a full postal address with a
  ZIP, which is a route-planning input, not just a mailing address.
- **Franchise and territory analysts** — run several city slugs and count `categories` to see which
  verticals are dense where; 3,160 distinct tags appeared in 1,848 rows.

***

### How this differs from our other lead scrapers

| Actor | What it is | Why you would use it instead |
|---|---|---|
| **Alignable Business Scraper** (this one) | Every business in an Alignable city slug, with the address and phone Alignable's claimed profiles publish | You want SMB owner-operator businesses by city, and a stable id to join on |
| [`manta-business-scraper`](https://apify.com/scrapersdelight/manta-business-scraper) | Manta's US SMB directory | Manta indexes a different, larger set of US small businesses — use it when the Alignable slug for your city is thin |
| [`bbb-scraper`](https://apify.com/scrapersdelight/bbb-scraper) | Better Business Bureau listings, ratings and phones | You need accreditation status and complaint/rating signals, not just contact data |
| [`yelp-business-scraper`](https://apify.com/scrapersdelight/yelp-business-scraper) | Yelp business pages | You need consumer reviews and star ratings on consumer-facing businesses |
| [`local-business-enricher`](https://apify.com/scrapersdelight/local-business-enricher) | Takes a website and finds contact names, roles and e-mail addresses | Alignable publishes no e-mail — feed this Actor's `website` field into that one |

The honest one-liner: Alignable is where you go for owner-operated small businesses grouped by city
with a phone number attached; it is not where you go for reviews, ratings, or e-mail.

***

### FAQ

**How do I scrape Alignable?**
Put a city slug into `citySlugs` and press Start. The Alignable Business Scraper reads Alignable's
own sitemap for that city and fetches each public profile — no login, no browser, no proxy tuning.
The prefilled default returned 200 Scottsdale businesses in 43, 47 and 104 seconds on three runs on
2026-08-12.

**Does Alignable Business Scraper need an account, a login or cookies?**
No. Every page it reads is public and returns HTTP 200 to an anonymous request. It never
authenticates, never solves a CAPTCHA, and never touches Alignable's login-walled `/biz/search` API.

**How do I get small business leads by city?**
`{"citySlugs": ["austin-tx", "denver-co"], "maxBusinessesPerCity": 0}`. The cap is per city, so `0`
gives you every business Alignable lists in each. Price it first: `austin-tx` is 1,926 profiles
($3.85) and `denver-co` is 936 ($1.87).

**How much does Alignable Business Scraper cost to run?**
$0.002 per business returned, $2 per 1,000, with no start fee. The default 200-row run is $0.40.
The largest single city slug in the whole directory, `san-antonio-tx` at 7,686 profiles, is $15.37.

**Can I get the whole Alignable directory in one run?**
Technically yes, city by city — the sitemap holds 1,765,937 profiles across 26,111 slugs, which is
about $3,532 at the per-row rate — but you should not. Pick the 20–50 city slugs your buyers are in;
259 slugs hold 1,000+ profiles and those are where the claimed, contactable businesses are.

**Does it return phone numbers and street addresses?**
Yes, on claimed profiles, which was 62.2% of a 1,600-row sample across four cities. Among those
claimed rows, `postalCode` was 100% filled and `telephone` 84.2%. Another Alignable actor's README
claims these fields cannot be fetched anonymously; the sample rows above are anonymous fetches.

**Does it return the owner's name?**
No. That field is not in this Actor's output today. It is the one thing a competing Alignable actor
returns that this one does not, and it is on the build list — file an issue if you need it and it
will move up.

**Does it return e-mail addresses?**
No, and nothing can, from this source — Alignable does not publish them on the public profile. Take
the `website` field and run `local-business-enricher` on it.

**Do I get charged for rows that get filtered out?**
No. `categoryFilter` runs after the fetch but before the push, and only pushed rows are charged. A
narrow filter costs the Actor time and proxy budget, not your money.

**Two runs of the same city — will I get duplicates?**
Within one run, no: rows are deduplicated on `businessId` before they are pushed or billed, and
1,848 rows across five runs produced 1,848 distinct ids. Across separate runs, yes — there is no
resume cursor, so deduplicate on `businessId` on your side.

**Does Alignable Business Scraper need proxies?**
It uses the Apify datacenter pool by default and that is the right setting: 1,848 of 1,848 profile
fetches returned HTTP 200 with 0 failures. RESIDENTIAL also measured 20/20 but was slower and
costs more. Alignable is not behind Cloudflare and serves plain server-rendered HTML.

**Will a run ever succeed with zero rows?**
No. Zero rows throws, with the fetched / filtered / failed counts in the message, and an unknown
city slug throws before any profile is fetched. A green run always means real rows.

**Something looks wrong — how do I debug it?**
Read the last log line. It gives pushed count, measured phone/address/website fill, and the filtered
and failed counts, so a low-fill result is immediately distinguishable from a blocked one (fill low,
failed 0 = a thin city; failed high = a transport problem).

**Can I run it on a schedule or from the API?**
Yes — Apify Schedules, the API, and the standard integrations (Make, Zapier, Google Sheets, Slack,
webhooks) all work with it, and the dataset exports to JSON, CSV, Excel and XML.

***

### Legal and fair use

This Actor reads only publicly visible pages: the XML sitemap that Alignable's own `robots.txt`
advertises, and the public business profile pages it lists. It does not log in, does not solve
CAPTCHAs, and does not touch the paths Alignable's `robots.txt` disallows (`/biz`, `/biz_users/`,
`/claims`, `/dashboard`, `/l/`, `/r/` and the rest).

Stated plainly rather than dressed up: Alignable's `robots.txt` grants a named allowlist of
search-engine crawlers and disallows all other user agents. Scraping publicly visible business
listings is generally permissible in the United States, but **complying with Alignable's Terms of
Service, and with how you use the data, is your responsibility.**

Rows describe businesses, but small-business contact data can also be personal data — handle it in
line with GDPR, CCPA/CPRA, PIPEDA, and the outreach rules that apply to you (CAN-SPAM for e-mail,
TCPA and state do-not-call lists for phone). Do not use this data for consumer profiling.

Alignable® is a trademark of its owner. This Actor is not affiliated with, endorsed by, or
sponsored by Alignable.

***

### Feedback

Found a missing field or want a new filter? Open an issue on the **Issues** tab — owner name,
recommendation counts and a cross-run resume cursor are all on the list and requests move them up.

# Actor input Schema

## `citySlugs` (type: `array`):

One entry per city. Three formats are accepted and normalised to the same slug: the Alignable slug ("scottsdale-az"), the human form ("Scottsdale, AZ"), or any Alignable URL ("https://www.alignable.com/scottsdale-az/mirage-limousines"). The three prefilled entries are all the SAME city, shown so you can see every accepted format — the Actor deduplicates them to one city before it fetches anything. Slug = lowercase city + dash + two-letter state/province code, exactly as it appears in the alignable.com URL. Measured city sizes: san-antonio-tx 7,686 profiles, scottsdale-az 4,788, austin-tx 1,926, denver-co 936, boston-ma 248.

## `categoryFilter` (type: `array`):

Keep only businesses whose Alignable category tags contain any of these words (case-insensitive substring match, OR-ed together). Real tag values look like "Real Estate Agent", "Financial Coach", "Limo Service", "Life Insurance". Leave empty to get every business in the city. Cost note: filtering happens AFTER the profile is fetched, so a narrow filter costs the Actor time and proxy budget but costs you nothing — filtered-out rows are never pushed and never charged.

## `maxBusinessesPerCity` (type: `integer`):

Hard cap on profiles fetched per city, and therefore your cost ceiling: 200 businesses = $0.40. Set 0 for every business in the city — measured city sizes run from 1 to 7,686 profiles (san-antonio-tx is the largest), so 0 on a big city can cost $15+. Duplicates are dropped before billing and never count against this cap.

## `requestConcurrency` (type: `integer`):

Parallel profile fetches. 5 is the measured default: the fetch phase of a 400-profile city run took 78-194 seconds with 0 HTTP failures (92-228 seconds of total run time, sitemap scan included). Raising it to 10 speeds the fetch phase up but gives the site less breathing room; the sitemap scan is serial either way.

## `requestDelayMs` (type: `integer`):

Politeness pause each worker waits after finishing a profile before starting the next one. At 250 ms with 5 workers, 1,848 profile fetches returned 0 blocks and 0 failures. Raise it if you are running a whole large city and want to be gentler; set 0 only for small runs.

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

Alignable is not walled. Measured through Apify on 2026-08-12: the sitemap index and all 36 gzipped shards returned HTTP 200 over the shared datacenter pool, and 1,848 of 1,848 profile pages returned HTTP 200 with 0 failures. So the cheap datacenter rung is the default and you should leave it alone — RESIDENTIAL also measured 20/20 but was slower (36s vs 22s for the same 20 pages) and costs more. Switch groups only if you ever see 403s.

## Actor input object example

```json
{
  "citySlugs": [
    "austin-tx",
    "denver-co"
  ],
  "categoryFilter": [
    "real estate",
    "insurance"
  ],
  "maxBusinessesPerCity": 200,
  "requestConcurrency": 5,
  "requestDelayMs": 250,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `businesses` (type: `string`):

The dataset of scraped Alignable businesses (one item per business).

# 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 = {
    "citySlugs": [
        "scottsdale-az",
        "Scottsdale, AZ",
        "https://www.alignable.com/scottsdale-az/mirage-limousines"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapersdelight/alignable-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 = { "citySlugs": [
        "scottsdale-az",
        "Scottsdale, AZ",
        "https://www.alignable.com/scottsdale-az/mirage-limousines",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("scrapersdelight/alignable-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 '{
  "citySlugs": [
    "scottsdale-az",
    "Scottsdale, AZ",
    "https://www.alignable.com/scottsdale-az/mirage-limousines"
  ]
}' |
apify call scrapersdelight/alignable-scraper --silent --output-dataset

```

## MCP server setup

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