# OpenStreetMap Scraper - OSM POI Data, No-Website Leads (`snow_leo_data/no-website-local-business-leads-openstreetmap`) Actor

Local business leads with no website of their own, from OpenStreetMap: 3932 of 7673 named Austin businesses in one query, 80.5 seconds. Phones normalised to E.164, 2607 chain outlets removed before billing, any city worldwide. Businesses without websites finder, OSM points of interest.

- **URL**: https://apify.com/snow\_leo\_data/no-website-local-business-leads-openstreetmap.md
- **Developed by:** [Snow Leo Data](https://apify.com/snow_leo_data) (community)
- **Categories:** Lead generation, Automation, MCP servers
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$2.60 / 1,000 leads

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

## Local Business Leads With No Website — from OpenStreetMap

Find local businesses that have **no website** — the single easiest pitch in
web design, SEO, and digital marketing. This Actor reads OpenStreetMap, the
open map database that powers Apple Maps, Strava, Craigslist and thousands of
other products, through its public Overpass API. No proxies. No headless
browser. No API key. One request sweeps an entire city.

A missing website here is a **fact in the data**, not something a model
guessed: the record simply has no website tag on it. The same goes for a
missing phone number, a missing address and missing opening hours. Every row
links back to the exact map object it came from, so any claim in this dataset
can be checked at the source in one click.

Data source: OpenStreetMap, © OpenStreetMap contributors, licensed ODbL 1.0.

***

### What this Actor measured before it was published

Every number below came from `tools/measure.py` on 2026-09-12. Nothing here is
an estimate.

| Measurement | Austin, Texas | Manchester, UK |
|---|---|---|
| Named businesses found in the whole city | 7,673 | 5,905 |
| With **no website at all** | 3,932 | 4,261 |
| Overpass queries needed | 1 | 1 |
| Wall-clock time for the sweep | 80.5 s | 13.4 s |
| Records carrying a phone number | 3,192 | 943 |
| Phone numbers normalised to E.164 | 3,192 (100%) | 943 (100%) |
| Records publishing opening hours | 3,049 | 1,059 |
| Opening hours parsed into a weekly schedule | 2,781 | 937 |
| Chain outlets identified and flagged | 2,607 | 1,205 |

Two of those rows deserve a second look.

**3,741 Austin businesses have a website, but only 3,500 of them keep it in
the plain `website` tag.** The other 241 keep it in `contact:website`, `url`,
`brand:website` or `operator:website`. A scraper that checks one tag would
have handed you 241 businesses in one city that already have a website — and
charged you for every one of them. This Actor checks six website tags and six
phone tags before it decides that something is missing.

**2,607 of 7,673 Austin records are chain outlets.** A branch manager cannot
buy a website from you; head office already did. One checkbox removes all of
them before you are billed.

***

### How it works

#### What exactly does this Actor return?

Every row is one business, with **59 fields** in the default mode:

- **Identity** — `name`, `name_international`, `brand`, `operator`, `is_chain`
- **Category** — `category`, `category_group`, plus the raw `osm_key` and
  `osm_value` so nothing is hidden behind our own naming
- **The website question** — `has_website`, `website`, and `website_tag`, which
  names the exact tag the address was found in
- **Contact** — `has_phone`, `phone` in E.164, `phone_raw` as the map stores
  it, `phone_tag`, `phone_all` when a business lists several numbers, `email`,
  `facebook`, `instagram`, and `social_only` for businesses that have a
  Facebook or Instagram page but no site of their own
- **Address, split into parts** — `address`, `street`, `house_number`, `unit`,
  `postcode`, `neighbourhood`, `city`, `county`, `state`, `country_code`,
  `latitude`, `longitude`, `distance_from_centre_km`
- **Opening hours, machine-readable** — `opening_hours` as published,
  `opening_hours_weekly` as a Monday-to-Sunday object of time ranges,
  `opening_hours_parsed`, `open_24_7`, `open_days_per_week`
- **Extra detail where the map has it** — `cuisine`, `wheelchair`,
  `internet_access`, `payment_methods`, `service_options`, `description`
- **Lead qualification** — `lead_score`, `lead_tier`, `lead_reasons`
- **Provenance** — `osm_type`, `osm_id`, `osm_url`, `last_edited`,
  `months_since_edit`, `search_area`, `change_type`, `data_source`,
  `data_licence`, `attribution`, `scraped_at`

An empty field always means one thing: the map does not hold that value. It is
never filled in by inference, pattern-matching or a language model.

#### How often is each contact field actually there?

The map is volunteered data, so a contact field is present only when a mapper
typed it in. Measured on 16 September 2026 over one Austin, Texas sweep —
3 000 named businesses across every category this Actor covers, of which
**1 586 had no website of their own**:

| Field | Businesses with no website that carry it |
|---|---:|
| `address`, `latitude`, `longitude`, `category` | every row |
| `opening_hours` | 327 (20.6%) |
| `phone` | 133 (8.4%) |
| `email` | 5 (0.3%) |
| `facebook` / `instagram` | 8 (0.5%) |

Reproduce it against the live Overpass API:

```bash
python3 - <<'PY'
from src import source, categories
rules = categories.selectors([g[0] for g in categories.GROUPS], [])
els = source.run_query(source.build_query(
    ('bbox', 30.20, -97.80, 30.35, -97.68), rules, limit=3000, with_meta=False))
rows = [e.get('tags') or {} for e in els if (e.get('tags') or {}).get('name')]
no_site = [t for t in rows if not (t.get('website') or t.get('contact:website'))]
print(len(rows), len(no_site),
      sum(1 for t in no_site if t.get('phone') or t.get('contact:phone')))
PY
```

This is why `requirePhone` exists: switch it on and the run keeps only the
businesses you can call, and you are not charged for the rest. What every row
always carries is the name, the address, the coordinates and the category —
enough to walk in or post a letter, which is how this list is normally worked.

#### How is the lead score calculated?

Six checks, each one a plain fact about the record, and each one listed by name
in `lead_reasons` so you can audit the number instead of trusting it:

| Check | Points | Why it matters |
|---|---|---|
| `no_website` | 40 | There is something to sell |
| `reachable_by_phone` | 25 | You can start the conversation today |
| `full_street_address` | 15 | A real premises, not a dropped pin |
| `independent_not_chain` | 10 | Somebody local can say yes |
| `opening_hours_published` | 5 | The record is maintained |
| `recently_edited` | 5 | Someone saw this place in the last two years |

The total is rescaled to 0-100. `lead_tier` is HIGH at 70 and above, MEDIUM
from 45, LOW below that. There is no hidden model and no black box: if you
disagree with the weighting, filter on the underlying fields instead.

#### How do I choose the area to sweep?

Three ways, in order of precedence:

1. **A bounding box** — `south,west,north,east` in decimal degrees. Exact,
   repeatable, and the right tool for splitting a large metro across several
   runs.
2. **A radius** — kilometres or miles from the centre of the place you named.
3. **The administrative boundary** — the default. Name a city and you get the
   real city limits, not a circle that clips half the suburbs and spills into
   farmland on the other side.

The place name is resolved through Nominatim, OpenStreetMap's own geocoder,
which prefers a genuine administrative boundary over a same-named point of
interest. Add a country code when a name is ambiguous.

#### Which business categories can I ask for?

Fourteen groups, each of which maps onto real OpenStreetMap tags. The mapping
is printed in the run report, so you always know exactly what was asked for:
restaurants and cafes; hair, beauty and nails; dentists, doctors and vets;
plumbers, electricians and builders; automotive; offices; lawyers and
accountants; estate agents; every kind of shop; hotels and guest houses; gyms
and leisure; schools and training; pet services; photographers and florists.

Leave the list empty and all fourteen are swept at once — that is how the
whole-city numbers at the top of this page were produced.

For anything the list does not cover, `customTags` takes raw OpenStreetMap
pairs such as `shop=florist`, `craft=roofer` or `office=architect`, or a bare
key such as `club` to take every value of it.

#### How does monitor mode avoid charging me twice?

Turn on `onlyNew` and the Actor keeps a record of every place it has returned,
in a **named** key-value store that survives between runs. On the next run it
compares each place against what it remembers and assigns a `change_type`:

- **NEW** — this place was not in the previous runs
- **UPDATED** — its name, website, phone, address, opening hours or category
  changed
- **UNCHANGED** — nothing you care about moved

Unchanged records are not written to the dataset, so you are not billed for
them. Set `emitUnchanged` if you want the full picture anyway.

The fingerprint deliberately ignores coordinates and edit timestamps. Mappers
nudge a pin by a metre and fix a neighbouring tag constantly; if those counted,
every run would declare the whole city updated and every run would cost you
full price.

#### Where do the results go besides the dataset?

Four delivery channels, all optional, all plain HTTP:

- **Slack** — incoming webhook URL
- **Discord** — incoming webhook URL
- **Telegram** — bot token plus chat ID
- **Your own webhook** — a JSON POST with the run summary and up to 200 leads,
  with custom headers if your endpoint needs authentication

`notifyOnlyChanges` narrows the message to new and updated places.
`notificationLimit` controls how many leads are listed inside one message; the
full set always lands in the dataset either way.

A broken notification channel never fails the run. The leads are already
delivered and already paid for; an expired Telegram token must not turn a
successful run into a failed one. Every channel's outcome is written to the run
report instead.

#### What happens when the source struggles?

Overpass is a shared public service with a real query budget. When a sweep is
too heavy for one request, this Actor splits the area into quadrants and
retries each one, up to four levels deep, deduplicating the places that fall
into two cells at once. It also knows three Overpass mirrors and moves to the
next one after a 429 or a 504.

A naive client does none of this, receives a gateway timeout on a large city,
and reports that the source is down.

#### What does a run cost me in results?

Every filter in the input runs **before** anything is written to the dataset.
Rows removed by `websiteFilter`, `requirePhone`, `requireAddress`,
`excludeChains`, `minLeadScore` and the rest never reach the dataset and cost
you nothing. The run report breaks down exactly what each filter removed, so an
empty result is always explained rather than mysterious.

The Actor also respects your per-run spending limit itself. Apify stops
charging when you hit the limit but does not stop the run; this Actor reads the
remaining budget at start-up and stops at it.

***

### Honest limitations

Rule of this repository: anything a competing Actor does that this one does
not is named here rather than quietly omitted.

**No ratings and no review counts.** OpenStreetMap does not have a rating
system, so `minRating` and `minReviews` style filters are not available here
and never will be. Scrapers built on Google Maps can offer them. If ranking by
star rating is essential to your workflow, this is not your tool.

**Coverage is volunteer-made and varies by city.** OpenStreetMap is edited by
people, not by a company with a street-view fleet. In some cities its business
coverage is excellent; in others it is thinner than a commercial map. We
measured 7,673 named businesses inside the Austin city limits and 5,905 in
Manchester. Before committing to a market, run a small sweep and look at the
count — it costs almost nothing and it is the only honest way to know.

**Opening hours are not always parsable.** OpenStreetMap's opening-hours syntax
supports seasonal rules, public holidays and sunrise-relative times. This Actor
parses the common weekday-and-time subset — 2,781 of the 3,049 Austin records
that publish hours — and honestly reports `opening_hours_parsed: false` for the
rest, leaving the original string untouched in `opening_hours`. It never
pretends to have understood a schedule it did not.

**A phone number is only converted when it can be converted.** If the map
stores a local number and the country is unknown, `phone` stays empty rather
than guessing a dialling code. A wrong number costs you a call to a stranger;
an empty field costs you nothing.

**Fewer delivery channels than the biggest competitor.** Delivery here is
Slack, Discord, Telegram and a generic webhook. There is no WhatsApp Cloud API
channel and no Notion or issue-tracker connector; a competing Actor has both.
If your pipeline ends in one of those, send the generic webhook into your
automation tool instead.

**Descriptions come in one format.** OpenStreetMap stores a plain-text
`description` tag and nothing else, so there is no HTML or Markdown variant of
it to choose between. Competitors that scrape listing pages can offer that
choice; there is simply no second format in this source to offer.

**No Google Maps place IDs, CIDs or Google URLs.** The identifiers here are
OpenStreetMap identifiers. They are stable, public and free to look up, but
they are not the same namespace.

***

### Input

Leave everything empty and press **Start**: you get up to 200 website-less
businesses in Austin, Texas, which is enough to see the shape of the data
before deciding anything.

```json
{
  "location": "Manchester, United Kingdom",
  "categories": ["beauty_wellness", "food_drink"],
  "websiteFilter": "no_website",
  "requirePhone": true,
  "excludeChains": true,
  "maxResults": 500
}
```

A monitored city, checked daily, billing only for movement:

```json
{
  "location": "Austin, Texas, United States",
  "onlyNew": true,
  "requirePhone": true,
  "minLeadScore": 70,
  "slackWebhookUrl": "https://hooks.slack.com/services/...",
  "notifyOnlyChanges": true,
  "maxResults": 1000
}
```

An exact territory, for splitting a metro between runs:

```json
{
  "boundingBox": "30.15,-97.95,30.45,-97.60",
  "categories": ["home_trades"],
  "requireAddress": true,
  "maxResults": 1000
}
```

### Output

One example row, trimmed to the interesting fields:

```json
{
  "name": "Bird's Barber Shop",
  "category": "hairdresser",
  "category_group": "beauty_wellness",
  "has_website": false,
  "website": "",
  "has_phone": true,
  "phone": "+15123409288",
  "phone_raw": "+1 512 340 9288",
  "address": "4815 W Braker Ln, Unit 530, Austin, TX, 78759",
  "street": "W Braker Ln",
  "house_number": "4815",
  "unit": "530",
  "postcode": "78759",
  "city": "Austin",
  "state": "TX",
  "country_code": "US",
  "latitude": 30.3999206,
  "longitude": -97.7481906,
  "opening_hours": "Mo-Su 09:00-19:00",
  "opening_hours_weekly": {
    "monday": ["09:00-19:00"],
    "sunday": ["09:00-19:00"]
  },
  "open_days_per_week": 7,
  "is_chain": false,
  "lead_score": 100,
  "lead_tier": "HIGH",
  "lead_reasons": [
    "no_website",
    "reachable_by_phone",
    "full_street_address",
    "independent_not_chain",
    "opening_hours_published",
    "recently_edited"
  ],
  "osm_url": "https://www.openstreetmap.org/node/1249568015",
  "last_edited": "2025-11-23T16:08:37Z",
  "months_since_edit": 10,
  "change_type": "NEW",
  "data_licence": "ODbL 1.0",
  "attribution": "© OpenStreetMap contributors, ODbL 1.0 (openstreetmap.org/copyright)"
}
```

Set `compactOutput` to get 14 core fields plus the attribution instead — a
smaller payload for LLM pipelines and agent tools. Set `excludeEmptyFields` to
drop nulls and empty lists from every record.

***

### FAQ

#### Who actually buys this data?

Web design studios, SEO consultants, digital marketing agencies, SaaS tools
sold to local businesses, franchise development teams, and market researchers
mapping a category across a city. The common thread is an offer that only makes
sense to a business that does not already have what you sell.

#### Is scraping OpenStreetMap legal?

OpenStreetMap data is open data, published deliberately for reuse under the
Open Database Licence 1.0. The Overpass API is the project's own public query
endpoint. This Actor honours the usage policy: a descriptive User-Agent, one
request at a time, a pause between requests, and no attempt to work around any
protection — because there is none to work around.

The licence has one requirement you must carry forward: **attribution**. Every
row carries an `attribution` field with the required credit, and it survives
both compact mode and empty-field trimming. If you publish or redistribute
anything derived from this data, credit © OpenStreetMap contributors.

#### Does this collect personal data?

No. It returns business records from a public map: trading name, business
address, business phone number, business category and opening hours. It does
not collect names of individuals, personal email addresses, personal phone
numbers or anything behind a login. Records that are not businesses at all —
named benches, parks, car parks — are filtered out before billing.

#### Why OpenStreetMap instead of Google Maps?

Three practical reasons. It needs no proxies and no headless browser, so the
run costs a fraction of a scraped equivalent and there is no monthly proxy
bill baked into the price. It covers every country equally, because there is
one global database rather than a per-market product. And the data is licensed
for reuse, which is a different legal position from data scraped out of a
consumer product's interface.

The trade-off is coverage and the absence of ratings, both named in the
limitations above.

#### How fresh is the data?

Overpass serves the live OpenStreetMap database, usually within a minute or two
of the latest edit. Freshness of an individual record is a different question,
and this Actor answers it directly: `last_edited` is the timestamp of the last
edit to that map object and `months_since_edit` is the age in months. A record
last touched eight years ago deserves a verification call before a sales call,
and now you can sort by that.

#### Can I sweep a whole country?

Technically the bounding box will accept one, but Overpass will refuse a query
that large, and the Actor will spend a long time splitting it into cells. The
practical unit is a city or a metropolitan area. To cover a country, run one
task per city on a schedule — that also keeps monitor mode meaningful, because
each task remembers its own area.

#### What happens on the very first run of monitor mode?

Everything is NEW, because the memory starts empty. That first run is your
baseline and it is priced like any other run. From the second run onwards you
pay only for movement. If you want to seed the memory cheaply, do the first run
with a tight filter.

#### Why do some records have no address?

Because nobody has mapped one yet. In Austin, 5,200 of 7,673 businesses have a
full street address in the map; the rest have a name, a category and a
position but no postal address. Use `requireAddress` if a postal address is
non-negotiable for your campaign — it filters before billing, so the ones
without cost you nothing.

#### Can I get businesses that DO have a website?

Yes. Set `websiteFilter` to `has_website` to invert the filter, or to `any` to
turn it off entirely and take the full business directory of a city. The same
59 fields come back either way, which makes this a reasonable local-business
directory scraper even when the no-website angle is not what you are after.

#### How do I verify a lead before calling it?

Open its `osm_url`. It goes straight to the map object this row was built from,
with the full tag list and the complete edit history. Every claim in every row
can be checked at the source, which is not something a scraped screenshot of a
consumer map can offer.

***

Data: © OpenStreetMap contributors, ODbL 1.0 —
<https://www.openstreetmap.org/copyright>

#### What else is this called?

Names buyers use for this job: OpenStreetMap business data, OSM POI scraper, OSM amenity export, Overpass API businesses, businesses without a website, no-website leads. One Overpass query per area returns the amenities with their addresses, phones and website tags.

Other names for what a row carries: wifi internet access venues (`internet_access`), international name transliteration (`name_international`), service options for delivery and takeaway (`service_options`), lead tier segmentation (`lead_tier`), independent versus chain business and chain detection by brand or operator (`is_chain`, `brand`, `operator`). Telegram bot notifications are supported through the `telegramToken` input.

# Actor input Schema

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

Any place on the map: `Austin, Texas`, `Manchester, UK`, `Lyon, France`, `Bali, Indonesia`. The whole administrative area is swept unless you set a radius below.

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

Leave empty to sweep every category at once. Each one maps to real OpenStreetMap tags — the mapping is printed in the run report.

## `maxResults` (type: `integer`):

How many leads land in your dataset, counted AFTER the no-website, phone and chain filters — businesses removed by filters never count and are never charged. Leave 0 and you get 200, so a first run stays cheap.

## `websiteFilter` (type: `string`):

`no_website` is the point of this Actor: businesses with nothing in any website tag. `has_website` flips it, `any` turns the filter off.

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

Keep only businesses with a working phone number, normalised to international format. Recommended for cold calling.

## `requireAddress` (type: `boolean`):

Keep only businesses with both a street and a house number — the ones you could post a letter to.

## `requireOpeningHours` (type: `boolean`):

A sign the record is actively maintained rather than a stale pin.

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

Drops every outlet that carries a brand tag — a Starbucks branch cannot buy a website from you. In Austin this removes 2,607 of 7,673 records.

## `minLeadScore` (type: `integer`):

0-100, computed from the record itself: no website, reachable by phone, full street address, independent, opening hours published, edited in the last 24 months. Every point is listed in `lead_reasons`.

## `categoryContains` (type: `string`):

Free-text narrowing on the category, e.g. `dent`, `hair`, `plumb`. Case-insensitive.

## `nameContains` (type: `string`):

Keep only businesses whose name contains this text.

## `excludeKeywords` (type: `array`):

Drop a lead if any of these words appears in its name, category, brand or operator.

## `minOpenDaysPerWeek` (type: `integer`):

Uses the parsed weekly schedule. Businesses that publish no hours at all are kept, not dropped — silence is not the same as closed.

## `searchRadiusKm` (type: `integer`):

Distance from the centre of the place. Leave 0 to sweep the whole administrative area instead of a circle.

## `searchRadiusMiles` (type: `integer`):

Alternative to kilometres. Used only when the km radius is left at 0.

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

`south,west,north,east` in decimal degrees, e.g. `30.15,-97.95,30.45,-97.60`. Overrides both the place name and the radius — use it to sweep an exact territory or to split a big market between runs.

## `countryCode` (type: `string`):

Two letters, e.g. `us`, `gb`, `de`. Disambiguates place names — many towns share a name — and supplies the dialling code when a phone number is stored without one.

## `language` (type: `string`):

Two-letter code, e.g. `en`. Fills `name_international` from the `name:<code>` tag when the map has one. The local name is always returned in `name`.

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

For anything the category list does not cover: `shop=florist`, `craft=roofer`, `office=architect`, or just `club` for every value of that key. Added on top of the categories you picked.

## `onlyNew` (type: `boolean`):

Remembers every place returned in earlier runs in a named key-value store. On the next run you are charged only for businesses that are new or whose name, website, phone, address, hours or category changed. Each row carries `change_type`: NEW, UPDATED or UNCHANGED.

## `emitUnchanged` (type: `boolean`):

Off by default: paying twice for a record that has not moved is the thing monitor mode exists to prevent.

## `notifyOnlyChanges` (type: `boolean`):

Applies to the channels below. Works together with monitor mode.

## `notificationLimit` (type: `integer`):

How many leads to list inside one message (1-20). The full set is always in the dataset.

## `slackWebhookUrl` (type: `string`):

Incoming webhook URL. Leave empty to skip Slack.

## `discordWebhookUrl` (type: `string`):

Incoming webhook URL. Leave empty to skip Discord.

## `telegramToken` (type: `string`):

Token from @BotFather. Needs the chat ID below as well.

## `telegramChatId` (type: `string`):

Chat or channel ID the bot posts into.

## `webhookUrl` (type: `string`):

Receives a JSON POST with the run summary and up to 200 leads — for n8n, Make, Zapier or your own CRM.

## `webhookHeaders` (type: `object`):

Extra headers for your webhook, e.g. {"Authorization": "Bearer ..."}.

## `compactOutput` (type: `boolean`):

Returns 14 core fields plus the required attribution instead of all 59. Smaller payloads for LLM pipelines and MCP tools.

## `excludeEmptyFields` (type: `boolean`):

Removes null, empty-string and empty-list fields from every record. The attribution field is always kept — the licence requires it.

## Actor input object example

```json
{
  "location": "Austin, Texas, United States",
  "categories": [
    "beauty_wellness"
  ],
  "maxResults": 200,
  "websiteFilter": "no_website",
  "requirePhone": false,
  "requireAddress": false,
  "requireOpeningHours": false,
  "excludeChains": false,
  "minLeadScore": 0,
  "categoryContains": "",
  "nameContains": "",
  "excludeKeywords": [],
  "minOpenDaysPerWeek": 0,
  "searchRadiusKm": 0,
  "searchRadiusMiles": 0,
  "boundingBox": "",
  "countryCode": "",
  "language": "",
  "customTags": [],
  "onlyNew": false,
  "emitUnchanged": false,
  "notifyOnlyChanges": false,
  "notificationLimit": 10,
  "telegramChatId": "",
  "webhookHeaders": {},
  "compactOutput": false,
  "excludeEmptyFields": false
}
```

# Actor output Schema

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

All collected rows

# 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": "Austin, Texas, United States",
    "categories": [
        "beauty_wellness"
    ],
    "maxResults": 200
};

// Run the Actor and wait for it to finish
const run = await client.actor("snow_leo_data/no-website-local-business-leads-openstreetmap").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": "Austin, Texas, United States",
    "categories": ["beauty_wellness"],
    "maxResults": 200,
}

# Run the Actor and wait for it to finish
run = client.actor("snow_leo_data/no-website-local-business-leads-openstreetmap").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": "Austin, Texas, United States",
  "categories": [
    "beauty_wellness"
  ],
  "maxResults": 200
}' |
apify call snow_leo_data/no-website-local-business-leads-openstreetmap --silent --output-dataset

```

## MCP server setup

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

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/3XFn9ZWdzUJDJCwbq/builds/myRDPabcj1TFaGrQu/openapi.json
