# Businesses Without Websites: No-Website Leads (OpenStreetMap) (`malmurk/osm-no-website-leads`) Actor

Find local businesses with no website, a dead website or only a Facebook/Instagram page in any city: dentists, salons, restaurants, shops and 35+ niches. Phone (E.164), email and address from OpenStreetMap, chains filtered out. Leads for web designers and agencies. $3 per 1,000 leads.

- **URL**: https://apify.com/malmurk/osm-no-website-leads.md
- **Developed by:** [Catalin Friptu](https://apify.com/malmurk) (community)
- **Categories:** Lead generation, Marketing, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 business founds

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?

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

## Businesses Without Websites: No-Website Leads Finder (OpenStreetMap)

**Find local businesses with no website** in any city or region: restaurants, dentists, beauty salons, car repair shops, lawyers and 30+ other niches. The Actor lists every business that has **no website listed in OpenStreetMap**, **only a Facebook or Instagram page**, or **a website link that is dead**, and gives you name, category, address, phone (E.164), email, social links, opening hours and coordinates. Ready for a spreadsheet, a CRM or a cold-calling list.

Built for **web designers, web agencies, freelancers and local marketers** who sell first websites, redesigns, Google Business profiles or booking pages. No Google Maps scraping, no proxies, no login: the data comes from [OpenStreetMap](https://www.openstreetmap.org/), and every listed website is checked live.

**Price: $3 per 1,000 leads.** Businesses that are filtered out (live website, chain, duplicate) are free.

### What does Businesses Without Websites Finder do?

- Turns a **city or region name** (e.g. `Chișinău, Moldova`, `Graz, Austria`, `Austin, Texas`) or a **bounding box** into a search area.
- Collects every named business in the categories you pick, plus any **custom OpenStreetMap tag** (`shop=bicycle`, `craft=carpenter`...).
- **Checks every listed website live** and labels it `none`, `social_only`, `dead` or `live`. A dead domain or a "website" that is really a Facebook page counts as a lead.
- **Skips chains and well-known venues** (on by default), so you don't pitch a website to a franchise.
- **Removes duplicates** and converts phone numbers to international **E.164** format (`+37360000001`).
- Exports to **CSV, Excel, JSON or Google Sheets**, or straight into your tools through the API, Make, Zapier or n8n.

### Example output

A real run with the default input (dentists in Chișinău, Moldova). Phone digits and emails are masked here; the dataset has them in full.

| name | category | phone_e164 | email | website_status | website_check_detail |
|---|---|---|---|---|---|
| Vita-Lic | dentist | +37322xxxx15 | v...@inbox.ru | `none` | no website listed in OpenStreetMap (unverified) |
| Galodent-M | dentist | | | `social_only` | website redirects to www.facebook.com (via galodent.md; listed host has no DNS) |
| SG Dent | dentist | +37322xxxx31 | | `dead` | DNS lookup failed |
| Dental.md | dentist | | | `dead` | timeout |
| Demi Dent | dentist | +37368xxxx31 | d...@gmail.com | `none` | no website listed in OpenStreetMap (unverified) |
| Centru Medical „Ivilius” | dentist | +37369xxxx53 | i...@gmail.com | `social_only` | website tag points to a social profile |

That run returned 100 leads in about 90 seconds.

### Who it's for

- **Web design and development agencies** looking for local clients who need a first website.
- **Freelancers and local marketers** offering websites, Google Business profiles, booking pages or social-to-web upgrades.
- **Sales teams and lead-generation agencies** building a call list of small businesses in one city and niche (dentists, salons, car repair shops, cafes...).
- **AI sales agents** that need a fresh list of prospects on demand (see [Use with AI agents and MCP](#use-with-ai-agents-and-mcp)).

### How to find businesses without a website

1. Click **Try for free** and create a free Apify account (no credit card needed).
2. Type a **Location**, e.g. `Kraków, Poland`, and pick one or more **Business categories**.
3. Set **Maximum results** to cap the cost, then click **Start**.
4. Open the **Output** tab and download the leads as CSV or Excel, or connect them to Google Sheets, Make, Zapier or n8n.
5. **Check each lead before you contact it**: `none` means "no website in OpenStreetMap", not "no website anywhere" (see [Limitations](#limitations)).

### How it works

1. Turns your **location** into a search area through OpenStreetMap's Nominatim geocoder. You can pass an exact **bounding box** instead.
2. Runs **one OpenStreetMap (Overpass API) query per category** and collects every named business in the area.
3. Removes duplicates. A business is often mapped twice in OpenStreetMap, once as a point and once as a building outline. The Actor drops the second copy when the name and phone match, or when the same name appears within 100 m.
4. Skips chains and well-known venues (`excludeChains`, on by default). OpenStreetMap often leaves out the website of chain locations, so a Dave & Buster's or a Maggiano's would otherwise look like a business without a website. A business with no website listed is skipped when:

   - it has a `brand`, `brand:wikidata`, `brand:wikipedia`, `operator:wikidata`, `wikidata` or `wikipedia` tag;
   - its name is a known brand of the same kind of business in the OpenStreetMap [name-suggestion-index](https://nsi.guide) for the area's country or state;
   - another business with the same name in the search lists a website or brand tag; or
   - the same name appears at two or more locations more than 100 m apart.

   The last two rules ignore generic names, because two places with such a name are usually unrelated: names the name-suggestion-index has reviewed and rejected as brands (e.g. `Dental Clinic`, `Стоматология`) plus a short built-in list (e.g. `Stomatologie`, `Pizzeria`, `Frizerie`).

   Businesses whose listed website is dead are always kept.
5. Classifies each business's web presence:

| `website_status` | Meaning |
|---|---|
| `none` | No website listed in OpenStreetMap (unverified) and no social profile. The business may still have a website that OpenStreetMap doesn't know about. |
| `social_only` | Only a Facebook/Instagram profile, a website field that points to one, or a domain that redirects to one. |
| `dead` | The listed website doesn't resolve (DNS), refuses connections, times out, or returns HTTP 404/410. |
| `live` | The listed website answered. Any other HTTP status counts as live, including 403 bot walls and 5xx errors, because a server is still there. |
| `unchecked` | A website is listed but verification was turned off. |

6. By default it keeps only `none`, `social_only` and `dead`: businesses with no working website *on record*. Check them before outreach.
7. Converts phone numbers to **E.164** (`+37360000001`). The country comes from the business address or the search area, and can be overridden.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `location` | string | | City or region name, e.g. `"Chișinău, Moldova"`. |
| `boundingBox` | string | | Instead of `location`: `"south,west,north,east"`, e.g. `"46.35,14.08,46.38,14.13"`. Used when both are set. |
| `categories` | array | `["dentist"]` | Any of: restaurant, cafe, bar, pub, fast_food, bakery, dentist, doctors, clinic, pharmacy, veterinary, hairdresser, beauty, massage, tattoo, fitness_centre, hotel, guest_house, car_repair, car_wash, tyres, florist, clothes, shoes, jewelry, optician, furniture, hardware, electronics, mobile_phone, pet, butcher, convenience, lawyer, accountant, estate_agent, plumber, electrician, photographer. |
| `customTags` | array | | Any other OpenStreetMap tag as `key=value` (e.g. `shop=bicycle`, `craft=carpenter`), or just `key`. |
| `onlyWithoutWebsite` | boolean | `true` | Keep only `none` / `social_only` / `dead`. Turn it off to get every business with its status. |
| `verifyWebsites` | boolean | `true` | Check each listed website with one HEAD/GET request. |
| `excludeChains` | boolean | `true` | Skip chains and well-known venues that have no website listed (see step 4 above). |
| `requirePhone` | boolean | `false` | Skip businesses without a phone number. |
| `maxItems` | integer | `100` | Stop after this many results, which also caps the cost. |
| `phoneCountry` | string | auto | 2-letter country code for local phone numbers. Normally detected automatically. |

Example:

```json
{
  "location": "Chișinău, Moldova",
  "categories": ["dentist", "beauty", "car_repair"],
  "customTags": ["shop=bicycle"],
  "onlyWithoutWebsite": true,
  "verifyWebsites": true,
  "requirePhone": true,
  "maxItems": 200
}
```

### Output

One dataset item per business. Example (phone digits and email shortened here):

```json
{
  "name": "Public & Vegan Kitchen",
  "category": "restaurant",
  "osm_tag": "amenity=restaurant",
  "address": "Ljubljanska cesta 4, 4260 Bled",
  "street": "Ljubljanska cesta",
  "housenumber": "4",
  "postcode": "4260",
  "city": "Bled",
  "country": "SI",
  "lat": 46.3666858,
  "lon": 14.1096165,
  "phone": "+386 xx xxx x12",
  "phone_e164": "+386xxxxxx12",
  "phones_e164": ["+386xxxxxx12"],
  "email": "p...@gmail.com",
  "website": null,
  "website_status": "social_only",
  "website_check_detail": "no website listed in OpenStreetMap (unverified); social profile listed",
  "facebook": "https://www.facebook.com/PublicBarVeganKitchen/",
  "instagram": null,
  "opening_hours": "Tu-Th, Su 12:00-00:00; Fr, Sa 12:00-03:00",
  "osm_id": "node/4578415854",
  "osm_url": "https://www.openstreetmap.org/node/4578415854",
  "source": "OpenStreetMap",
  "license": "ODbL 1.0, (c) OpenStreetMap contributors, https://www.openstreetmap.org/copyright"
}
```

`website`, `facebook` and `instagram` are `null` when OpenStreetMap has no value. `website_check_detail` explains each verdict, e.g. `HTTP 404`, `DNS lookup failed`, `timeout`, or `website redirects to www.facebook.com (via example.md; listed host has no DNS)`. You can export the dataset as CSV, Excel, JSON or XML from the Storage tab, or through the API.

### How much does it cost to find businesses without a website?

The Actor uses **pay-per-event** pricing: **$3 per 1,000 leads** ($0.003 per business written to the dataset), plus a start fee of $0.00005 per run. Apify platform usage is included in that price.

| Leads | Price |
|---|---|
| 100 | $0.30 |
| 1,000 | $3.00 |
| 10,000 | $30.00 |

Businesses that are filtered out, such as those with a live website, chains, duplicates, or ones without a phone when `requirePhone` is on, cost nothing. The run stops at `maxItems` or at the maximum charge you set for the run, whichever comes first. Apify's free plan includes monthly credit, enough to try the Actor on a few cities.

### Use with AI agents and MCP

Give Claude, Cursor, ChatGPT or your own agent a "find businesses without a website" tool through the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp). Add this server URL to your MCP client:

```
https://mcp.apify.com?tools=malmurk/osm-no-website-leads
```

Then ask, for example: *"Find 50 hair salons in Valencia, Spain with no website and a phone number, and put them in a table."* The agent fills in `location`, `categories`, `requirePhone` and `maxItems` itself. Keep `maxItems` small in agent workflows to control cost.

### Integrations and API

- **Make, Zapier and n8n:** run the Actor from a scenario, zap or workflow and push new leads to Google Sheets, HubSpot, Pipedrive, Airtable or email. See the [Make](https://docs.apify.com/platform/integrations/make), [Zapier](https://docs.apify.com/platform/integrations/zapier) and [n8n](https://docs.apify.com/platform/integrations/n8n) guides.
- **Schedules:** run the same city every week with [Apify Schedules](https://docs.apify.com/platform/schedules) to catch newly mapped businesses.
- **Webhooks:** get notified when a run finishes ([webhooks](https://docs.apify.com/platform/integrations/webhooks)).
- **API:** one HTTP call runs the Actor and returns the leads.

```bash
curl -X POST "https://api.apify.com/v2/acts/malmurk~osm-no-website-leads/run-sync-get-dataset-items?token=<YOUR_API_TOKEN>&format=csv" \
  -H "Content-Type: application/json" \
  -d '{"location": "Graz, Austria", "categories": ["hairdresser", "beauty"], "requirePhone": true, "maxItems": 50}'
```

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_API_TOKEN>")
run = client.actor("malmurk/osm-no-website-leads").call(run_input={
    "location": "Graz, Austria", "categories": ["hairdresser"], "maxItems": 50,
})
for lead in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(lead["name"], lead["phone_e164"], lead["website_status"])
```

### FAQ

**How is this different from Google Maps "no website" scrapers?**
It uses OpenStreetMap, an open database with a license that allows reuse (ODbL), instead of scraping Google Maps. There are no proxies and no CAPTCHAs, so it's cheap and stable. The trade-off: OpenStreetMap has no ratings or reviews, and coverage is best in European cities. It also checks listed websites live, so dead domains and Facebook-only "websites" become leads.

**Does `none` mean the business has no website at all?**
No. It means OpenStreetMap has no website for it. Mappers often skip that field, so search the business name before you contact it.

**Which countries work?**
Any country OpenStreetMap covers. Big European cities are usually mapped in the most detail. Test a small `maxItems` first for a new city.

**Can I get emails?**
Yes, when mappers added one (`email` field). Most leads have a phone number rather than an email. Use `requirePhone` to keep only callable leads.

**Is it legal?**
The data is published by OpenStreetMap under the ODbL, which allows reuse with attribution. You are responsible for how you contact businesses: cold outreach is regulated in many countries (e.g. GDPR / ePrivacy in the EU).

**I found a bug or need a new category.**
Open an issue in the **Issues** tab. Custom OpenStreetMap tags (`customTags`) cover most niches that aren't in the list.

### Related Actors

- [Website Screenshot & URL to PDF](https://apify.com/malmurk/website-screenshot-pdf): capture full-page screenshots of your leads' websites (or their Facebook pages) for a before/after pitch.
- [PDF Text Extractor with OCR](https://apify.com/malmurk/pdf-text-extractor): turn PDFs, Word files and scans into text and Markdown for AI.

### Data source and license

Contains information from **© OpenStreetMap contributors**, available under the [Open Database License (ODbL) 1.0](https://www.openstreetmap.org/copyright). Every item carries `source` and `license` fields. If you publish or redistribute the data, credit OpenStreetMap contributors and follow the ODbL share-alike terms for derived databases.

The chain filter uses brand names from the OpenStreetMap [name-suggestion-index](https://github.com/osmlab/name-suggestion-index) (BSD-3-Clause), bundled with the Actor and regenerated with `scripts/build_brand_index.py`.

The Actor uses the public Nominatim and Overpass APIs within their usage policies:

- It identifies itself with a User-Agent.
- It sends at most 1 Nominatim request per second, and usually only one per run.
- It sends one Overpass query per category, with server-side timeouts, retries with exponential backoff that honor `Retry-After`, and a fallback endpoint (`overpass.private.coffee`).
- An Overpass answer cut off by a server limit (a `remark`, missing timestamps, or no area data) is retried, never reported as "0 found". If every attempt fails within about 3.5 minutes, that category is skipped, and the run fails with an error when all categories fail.

### Limitations

- **OpenStreetMap coverage varies.** Big cities in Europe are usually well mapped. Small towns and some countries are not. If a business isn't in OpenStreetMap, the Actor can't find it.
- **`none` means "no website listed in OpenStreetMap", not "no website".** OpenStreetMap volunteers often skip the website field. The live check catches dead links and social-only links that *are* listed, but it can't discover a website OpenStreetMap doesn't mention. Search each business's name before you contact it.
- **The chain filter is not complete.** It only knows chains that OpenStreetMap or the name-suggestion-index mark as brands, or that appear more than once in your search. A chain with a single untagged location in the area, or a well-known independent venue, can still appear (in an Austin test run: Grand Lux Café, Pappasitos, Fonda San Miguel). It can also wrongly skip unrelated businesses that share a generic name missing from its lists at two or more locations. Turn `excludeChains` off if you want chain locations too, or to see everything.
- `live` is deliberately generous: a site behind a bot wall or temporarily down counts as live, so you don't pitch a website to someone who already has one.
- Phone numbers and emails are whatever mappers entered. Invalid numbers are kept in `phone` but left out of `phone_e164`.
- Very large areas (whole countries) can exceed Overpass's query time limit. Split them into cities or bounding boxes.
- You are responsible for how you use the contact data. Cold outreach is regulated in many countries (e.g. GDPR / ePrivacy in the EU).

### Local development

```bash
python -m venv .venv && .venv/Scripts/pip install -r requirements-dev.txt   # Windows; use .venv/bin on Linux/macOS
.venv/Scripts/python -m pytest                 # offline tests, no network
apify run --purge --input '{"location":"Chișinău, Moldova","categories":["dentist"],"maxItems":5}'
```

To test pay-per-event behavior locally, set `ACTOR_TEST_PAY_PER_EVENT=true` and `ACTOR_MAX_TOTAL_CHARGE_USD=<n>`. Locally, the SDK prices each result at $1 plus $1 for the dataset item, so `n=10` stops after 5 results. The charge log is written to `storage/datasets/charging-log/`.

# Actor input Schema

## `location` (type: `string`):

City or region name, geocoded with OpenStreetMap Nominatim (e.g. 'Chișinău, Moldova', 'Graz, Austria'). Leave empty if you use a bounding box.

## `boundingBox` (type: `string`):

Alternative to Location: 'south,west,north,east' in decimal degrees (e.g. '46.95,28.75,47.10,28.95'). Takes precedence over Location when both are set.

## `categories` (type: `array`):

OpenStreetMap categories to search. Each category is one Overpass API query.

## `customTags` (type: `array`):

Extra OpenStreetMap tags to search, one per line, as 'key=value' (e.g. 'shop=bicycle', 'craft=carpenter') or just 'key' for any value. See https://wiki.openstreetmap.org/wiki/Map_features

## `onlyWithoutWebsite` (type: `boolean`):

Keep only businesses whose website status is none (no website listed in OpenStreetMap, unverified), social_only or dead. Turn off to get every business with its website status.

## `verifyWebsites` (type: `boolean`):

Send one HEAD/GET request to each listed website to tell live sites from dead links. When off, businesses with a listed non-social website are reported as 'unchecked' and excluded by the filter above.

## `excludeChains` (type: `boolean`):

OpenStreetMap often leaves out the website of chain locations, so they look like businesses without a website. When on, a business with no website listed is skipped if it has a brand or Wikidata/Wikipedia tag, its name is a known brand in the OpenStreetMap name-suggestion-index for the area's country or state, another business with the same name in the area lists a website, or the same name appears at two or more locations in the area. Businesses with a listed but dead website are always kept. Not every chain is caught: verify leads before outreach.

## `requirePhone` (type: `boolean`):

Skip businesses that have no phone number in OpenStreetMap.

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

Stop after this many results. You pay per result, so this also caps the cost of the run.

## `phoneCountry` (type: `string`):

2-letter ISO country code used to convert local phone numbers to international E.164 format (e.g. 'DE'). Detected from the area automatically; set it only to override.

## Actor input object example

```json
{
  "location": "Chișinău, Moldova",
  "categories": [
    "dentist"
  ],
  "onlyWithoutWebsite": true,
  "verifyWebsites": true,
  "excludeChains": true,
  "requirePhone": false,
  "maxItems": 50
}
```

# Actor output Schema

## `results` (type: `string`):

No description

# 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 = {
    "location": "Chișinău, Moldova",
    "categories": [
        "dentist"
    ],
    "maxItems": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("malmurk/osm-no-website-leads").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 = {
    "location": "Chișinău, Moldova",
    "categories": ["dentist"],
    "maxItems": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("malmurk/osm-no-website-leads").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 '{
  "location": "Chișinău, Moldova",
  "categories": [
    "dentist"
  ],
  "maxItems": 50
}' |
apify call malmurk/osm-no-website-leads --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,malmurk/osm-no-website-leads"
        }
    }
}
```

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/w2Wpo3qJnjcRrK6Pu/builds/WE7FSa15l9tUqDcVB/openapi.json
