# Demo Website Generator for Local Businesses (`sebastian_elustondo/local-business-demo-site-generator`) Actor

Turn Google Maps leads into ready-to-pitch demo websites with real reviews, photos and a WhatsApp CTA, plus outreach messages.

- **URL**: https://apify.com/sebastian\_elustondo/local-business-demo-site-generator.md
- **Developed by:** [Sebastian Elustondo](https://apify.com/sebastian_elustondo) (community)
- **Categories:**
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $150.00 / 1,000 demo site generateds

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

## Demo Website Generator for Local Businesses

Turn Google Maps leads into ready-to-pitch demo websites. For every business you feed it, this Actor builds a polished one-page site with the business's real name, Google rating, reviews, photos and a WhatsApp button, takes desktop and mobile screenshots, and writes the outreach messages you send to close the deal.

It is the step after any "businesses without websites" Google Maps scraper: those give you leads, this gives you the demo and the pitch for each lead.

### Who it is for

- Web design agencies and freelancers who sell websites to local businesses (barbershops, restaurants, clinics, gyms, salons, auto shops, bakeries).
- AI agencies and lead-gen operators who want to send a finished demo instead of a cold "do you need a website?" message.
- Anyone running the classic "no website leads" workflow who is tired of building demos by hand.

### How it works

1. Run any Google Maps scraper that finds businesses without a website, for example `compass/crawler-google-places` with a "no website" filter, or one of the dedicated leads scrapers.
2. Paste the run's default dataset ID into this Actor's `datasetId` field (or paste businesses as JSON in `businesses`).
3. Get, for each business: a self-contained `index.html` demo, a desktop and a mobile screenshot, a pitch document with four messages, and a dataset row with all the links.
4. Send message 1 with the screenshot on WhatsApp. When they reply, send the demo link. Close on a 10-minute call.

Businesses that already have a `website` in the dataset are skipped by default. Closed places are skipped too.

### Input

| Field | Type | Description |
|---|---|---|
| `businesses` | array | Businesses as JSON objects (see below). Only `name` is required. |
| `datasetId` | string | Dataset ID of a Google Maps scraper run. Items are mapped automatically. |
| `includeBusinessesWithWebsite` | boolean | Default `false`: items with a `website` are skipped. |
| `maxBusinesses` | integer | Stop after this many demos. Default 50. |
| `language` | string | `auto`, `es`, `en` or `pt`. Auto picks the language from the country. |
| `agencyName` | string | Your studio name, used in the footer credit and the pitch. |
| `senderName` | string | First name that signs the messages. |
| `agencyEmail` | string | Optional alternative contact in the pitch. |
| `agencyWebsite` | string | Optional link on the footer credit. |
| `priceLine` | string | Optional. Written into the pitch as a note for the call, never in message 1. |
| `takeScreenshots` | boolean | Default `true`. Desktop and mobile full-page PNGs. |
| `footerCredit` | boolean | Default `true`. "Demo site created by {agency}" in the footer. |
| `accentColor`, `backgroundColor` | string | Optional hex overrides. By default each category gets its own palette. |

#### The business object

```json
{
  "name": "Barbería La Estación",
  "category": "Barbería",
  "address": "Av. Triunvirato 4500, Villa Urquiza, CABA",
  "city": "Buenos Aires",
  "country": "AR",
  "phone": "011 4521-0000",
  "whatsapp": "5491145210000",
  "rating": 4.9,
  "reviewsCount": 151,
  "reviews": [{ "author": "Martín G.", "text": "Excelente atención.", "stars": 5, "date": "2025-06-01" }],
  "photos": ["https://lh3.googleusercontent.com/p/..."],
  "hours": [{ "day": "Tuesday", "hours": "10:00 to 20:00" }],
  "instagram": "laestacion",
  "mapsUrl": "https://www.google.com/maps/place/...",
  "description": "",
  "language": "es"
}
```

Field names used by popular Google Maps scrapers are accepted as aliases: `title`, `categoryName`, `categories`, `totalScore`, `reviewsCount`, `phoneUnformatted`, `imageUrls`, `imageUrl`, `openingHours`, `countryCode`, `url`, and the review fields `name`, `text`, `stars`, `publishedAtDate`. When `whatsapp` is missing the phone is used, with the country dial code added when needed (Argentina mobiles get the required `9`).

### Output

One dataset item per business:

```json
{
  "name": "Steinway Barber Shop",
  "slug": "steinway-barber-shop-a6008c",
  "category": "Barber shop",
  "preset": "barber",
  "language": "en",
  "city": "Astoria, NY",
  "country": "US",
  "address": "25-02 Steinway St, Astoria, NY 11103",
  "phone": "+1 718-728-5727",
  "whatsapp": "17187285727",
  "rating": 4.9,
  "reviewsCount": 116,
  "photosUsed": 0,
  "reviewsUsed": 3,
  "demoUrl": "https://api.apify.com/v2/key-value-stores/.../records/steinway-barber-shop-a6008c.html",
  "screenshotDesktopUrl": "https://api.apify.com/v2/key-value-stores/.../records/steinway-barber-shop-a6008c-desktop.png",
  "screenshotMobileUrl": "https://api.apify.com/v2/key-value-stores/.../records/steinway-barber-shop-a6008c-mobile.png",
  "pitchUrl": "https://api.apify.com/v2/key-value-stores/.../records/steinway-barber-shop-a6008c-pitch.md",
  "whatsappLink": "https://wa.me/17187285727?text=Hi%21%20I%20would%20like%20to%20book%20an%20appointment.",
  "whenToSend": "Tuesday or Wednesday, 9 to 10 am, before the first chair. Never Friday or Saturday.",
  "message1": "Hi! Is this Steinway Barber Shop? I'm Sebastián from Quovra. ...",
  "generatedAt": "2026-09-02T22:16:09+00:00"
}
```

The key-value store holds the files: `{slug}.html`, `{slug}-desktop.png`, `{slug}-mobile.png` and `{slug}-pitch.md`. The `demoUrl` opens the demo directly in a browser, so you can share it as is or download the HTML and host it under your own domain.

### What the demo site includes

- Header with the business name and a booking or contact button.
- Hero with the category and city, the name as headline, a category tagline, the main WhatsApp (or call) button, and the reputation seal: the real Google rating, stars, review count and the best short review.
- Three "why us" points and a hero visual: the first Google photo when available, or a category illustration.
- Services or menu cards for the category, with prices marked "Ask" (nothing is invented).
- Photo gallery with the business's Google photos, or labeled placeholder slots.
- Real Google reviews, with a link to the listing when available.
- Opening hours and address with a "Get directions" link.
- Contact band, footer credit and a floating WhatsApp button.

The HTML is a single file with inline CSS and a few lines of inline JavaScript (scroll reveals). No external scripts, fonts or trackers. It is marked `noindex` because it is a demo.

### Categories and languages

Presets: barbershop and hair salon, restaurant and food, veterinary and pet, beauty and nails, gym and fitness, health and dental, auto repair, bakery and café, real estate, and a generic local business fallback. The preset is chosen from the category text (English, Spanish or Portuguese keywords) and sets the palette, type treatment, illustration, services and copy.

Languages: Spanish (River Plate "vos" for Argentina, Uruguay and Paraguay, neutral "tú" elsewhere), English and Portuguese. Auto mode picks the language from the country code.

### The pitch document

For every business you get a Markdown file with:

- When to send (per category: before opening for barbers and salons, before lunch service for restaurants, the afternoon lull for clinics, and so on).
- Message 1 for WhatsApp: names the business's real rating and review count, mentions the missing website, attaches the screenshot, asks for a 10-minute call. No link, no price.
- Message 2 when they reply, with the demo link.
- Follow-up 1 on day 4 or 5 and follow-up 2 on day 10 to 12 with the competitor lever.
- Three "what the business gains" bullets in owner language, and notes for the call.

Square brackets mark the things only you know, such as `[COMPETITOR]` or `[OWNER NAME]`.

### Pricing

This Actor uses pay-per-event pricing. You pay for what is generated:

| Event | Charged when |
|---|---|
| `demo-generated` | A demo site and its pitch are written for one business. |
| `screenshot` | Desktop and mobile screenshots are taken for one business (only when `takeScreenshots` is on). |

Turn screenshots off for cheaper runs when you only need the HTML.

### Limitations

- The demo is static HTML meant to be hosted by you (Cloudflare Pages, Netlify, your own server) or opened from the key-value store link. It is not a website builder and there is no editor.
- Photos are hot-linked from Google. Google image URLs can expire; when a photo fails to load the site shows a placeholder automatically. Download the photos before publishing a site for a paying client.
- Services, prices and taglines are category defaults, written to be true for most businesses of that kind. Prices are never invented; every card says "Ask".
- Reviews come from your input. The Actor does not scrape Google Maps itself; chain it after a scraper that returns reviews if you want them on the demo.
- Reading `datasetId` requires the run to be in your Apify account (or a dataset shared with you).

### Integrations

Run it from the API, chain it after a scraper, or call it from Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_API_TOKEN>")

## 1. Find businesses without a website (any Google Maps scraper works)
leads = client.actor("compass/crawler-google-places").call(run_input={
    "searchStringsArray": ["barber shop"],
    "locationQuery": "Astoria, New York",
    "maxCrawledPlacesPerSearch": 30,
    "website": "withoutWebsite",
    "maxReviews": 5,
    "maxImages": 6,
})

## 2. Build demos for them
run = client.actor("<YOUR_USERNAME>/local-business-demo-site-generator").call(run_input={
    "datasetId": leads["defaultDatasetId"],
    "agencyName": "My Studio",
    "senderName": "Alex",
})

for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["name"], item["demoUrl"], item["screenshotDesktopUrl"])
```

### FAQ

**Can I use my own template?** Not yet. Category presets are built in; colors can be overridden with `accentColor` and `backgroundColor`.

**Does it write in my language?** Sites and pitches are available in Spanish, English and Portuguese. Set `language` or let the country decide.

**Does the demo need a server?** No. It is one HTML file. Open it, upload it anywhere, or share the key-value store link.

**Can I remove the footer credit?** Set `footerCredit` to `false`.

**What if a business has no phone?** The site still renders, with "Get directions" as the main action, and the pitch flags that you need to get the number before sending.

# Actor input Schema

## `businesses` (type: `array`):

List of businesses to build demos for. Each object: <code>{name, category, address, city, country (ISO-2), phone, whatsapp, rating, reviewsCount, reviews:\[{author,text,stars,date}], photos:\[urls], hours:\[{day,hours}], instagram, mapsUrl, description, language}</code>. Only <code>name</code> is mandatory. Field names from popular Google Maps scrapers (title, categoryName, totalScore, imageUrls, phoneUnformatted, openingHours...) are accepted too.

## `datasetId` (type: `string`):

Default dataset ID of a finished run of any Google Maps scraper (for example <code>compass/crawler-google-places</code> or any "businesses without websites" leads scraper). Its items are read and mapped automatically.

## `includeBusinessesWithWebsite` (type: `boolean`):

By default, dataset items whose <code>website</code> field is filled are skipped, since they are not "no website" leads.

## `maxBusinesses` (type: `integer`):

Stop after this many demos (counts both sources).

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

Language of the demo sites and pitch messages. <b>Auto</b> picks Spanish for Spanish-speaking countries, Portuguese for Brazil and Portugal, English otherwise; a per-business <code>language</code> field wins.

## `agencyName` (type: `string`):

Shown in the demo footer credit and used in the pitch messages.

## `senderName` (type: `string`):

First name that signs the pitch messages ("Hi, I'm ... from ...").

## `agencyEmail` (type: `string`):

Optional. Added to the pitch messages as an alternative contact.

## `agencyWebsite` (type: `string`):

Optional. Linked from the demo footer credit.

## `priceLine` (type: `string`):

Optional free text, for example <code>USD 300 setup + USD 30/month</code>. It is only written into the pitch document (as a note for the call, never in the first message).

## `takeScreenshots` (type: `boolean`):

Render a desktop and a mobile full-page screenshot of every demo (Chromium). Turn off for faster, cheaper runs.

## `footerCredit` (type: `boolean`):

Add "Demo site created by {agency}" to the footer of every demo.

## `accentColor` (type: `string`):

Optional hex color (for example <code>#1E7A4C</code>). By default each business category gets its own palette.

## `backgroundColor` (type: `string`):

Optional hex color (for example <code>#FAF7F0</code>).

## Actor input object example

```json
{
  "businesses": [
    {
      "name": "Barbería La Estación",
      "category": "Barbería",
      "address": "Av. Triunvirato 4500, Villa Urquiza, CABA",
      "city": "Buenos Aires",
      "country": "AR",
      "phone": "011 4521-0000",
      "rating": 4.9,
      "reviewsCount": 151,
      "reviews": [
        {
          "author": "Martín G.",
          "text": "Excelente atención, siempre salgo conforme.",
          "stars": 5
        },
        {
          "author": "Lucas P.",
          "text": "Puntuales y prolijos. El mejor fade del barrio.",
          "stars": 5
        }
      ],
      "photos": [],
      "hours": [
        {
          "day": "Tuesday",
          "hours": "10:00 to 20:00"
        },
        {
          "day": "Saturday",
          "hours": "10:00 to 18:00"
        }
      ]
    }
  ],
  "includeBusinessesWithWebsite": false,
  "maxBusinesses": 50,
  "language": "auto",
  "agencyName": "Your Agency",
  "takeScreenshots": true,
  "footerCredit": true
}
```

# Actor output Schema

## `demos` (type: `string`):

No description

## `files` (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 = {
    "businesses": [
        {
            "name": "Barbería La Estación",
            "category": "Barbería",
            "address": "Av. Triunvirato 4500, Villa Urquiza, CABA",
            "city": "Buenos Aires",
            "country": "AR",
            "phone": "011 4521-0000",
            "rating": 4.9,
            "reviewsCount": 151,
            "reviews": [
                {
                    "author": "Martín G.",
                    "text": "Excelente atención, siempre salgo conforme.",
                    "stars": 5
                },
                {
                    "author": "Lucas P.",
                    "text": "Puntuales y prolijos. El mejor fade del barrio.",
                    "stars": 5
                }
            ],
            "photos": [],
            "hours": [
                {
                    "day": "Tuesday",
                    "hours": "10:00 to 20:00"
                },
                {
                    "day": "Saturday",
                    "hours": "10:00 to 18:00"
                }
            ]
        }
    ],
    "agencyName": "Your Agency"
};

// Run the Actor and wait for it to finish
const run = await client.actor("sebastian_elustondo/local-business-demo-site-generator").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 = {
    "businesses": [{
            "name": "Barbería La Estación",
            "category": "Barbería",
            "address": "Av. Triunvirato 4500, Villa Urquiza, CABA",
            "city": "Buenos Aires",
            "country": "AR",
            "phone": "011 4521-0000",
            "rating": 4.9,
            "reviewsCount": 151,
            "reviews": [
                {
                    "author": "Martín G.",
                    "text": "Excelente atención, siempre salgo conforme.",
                    "stars": 5,
                },
                {
                    "author": "Lucas P.",
                    "text": "Puntuales y prolijos. El mejor fade del barrio.",
                    "stars": 5,
                },
            ],
            "photos": [],
            "hours": [
                {
                    "day": "Tuesday",
                    "hours": "10:00 to 20:00",
                },
                {
                    "day": "Saturday",
                    "hours": "10:00 to 18:00",
                },
            ],
        }],
    "agencyName": "Your Agency",
}

# Run the Actor and wait for it to finish
run = client.actor("sebastian_elustondo/local-business-demo-site-generator").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 '{
  "businesses": [
    {
      "name": "Barbería La Estación",
      "category": "Barbería",
      "address": "Av. Triunvirato 4500, Villa Urquiza, CABA",
      "city": "Buenos Aires",
      "country": "AR",
      "phone": "011 4521-0000",
      "rating": 4.9,
      "reviewsCount": 151,
      "reviews": [
        {
          "author": "Martín G.",
          "text": "Excelente atención, siempre salgo conforme.",
          "stars": 5
        },
        {
          "author": "Lucas P.",
          "text": "Puntuales y prolijos. El mejor fade del barrio.",
          "stars": 5
        }
      ],
      "photos": [],
      "hours": [
        {
          "day": "Tuesday",
          "hours": "10:00 to 20:00"
        },
        {
          "day": "Saturday",
          "hours": "10:00 to 18:00"
        }
      ]
    }
  ],
  "agencyName": "Your Agency"
}' |
apify call sebastian_elustondo/local-business-demo-site-generator --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,sebastian_elustondo/local-business-demo-site-generator"
        }
    }
}

```

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/e96sJIDCZoubdBvKo/builds/55pZzUe5JHomCcefM/openapi.json
