# Planity Scraper - Beauty Salons, Services, Prices & Reviews (`abotapi/planity-salon-scraper`) Actor

Scrape Planity salons across France, Germany and Belgium by city, service or URL. Extract service menus, prices, durations, photos, opening hours, staff, ratings and client reviews. Supports salon and search URLs, resume, incremental updates and MCP export.

- **URL**: https://apify.com/abotapi/planity-salon-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Features:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.60 / 1,000 salon records

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Planity Scraper: Salons, Services, Prices & Reviews

Scrape Planity, Europe's beauty and wellness booking platform: hair salons, barbers, nail studios, spas and beauty institutes across France, Germany and Belgium. Each salon record carries its contact block, rating summary, photo gallery, opening hours, written description and nested client reviews, plus the full service menu with prices and durations from the salon's own page. Search by plain city and service names, or paste salon and results links, and keep scheduled runs incremental so only new or changed salons come back.

### Why This Scraper?

- **Three ways in.** Plain-language city and service search ("Paris" + "Manucure"), pasted salon or results links, or a mix of both on any of the three countries.
- **Service menus with real prices.** Every bookable service variant ships with its price, duration, deposit mode and cancellation fees, read from the salon's own page.
- **Nested reviews.** Client reviews (rating, date, text) attach to the salon record as a nested list, up to 20 per salon; an Output tab unwinds them to one row per review.
- **Full photo galleries and team lists.** Salon photo sets and staff names, roles and photos ship on profile-enriched rows.
- **Recurring monitoring built in.** Incremental mode classifies NEW / UPDATED / UNCHANGED / EXPIRED against a saved baseline; booking-availability churn never bills a phantom update, service-price moves do.
- **Three countries, one actor.** France, Germany and Belgium share one input: pick the country for search mode, or let each pasted link pick its own from its locale prefix.
- **Send results anywhere.** MCP connectors pipe collected salons into Notion, Airtable, Linear or any MCP-connected app.

### Use Cases

- **Beauty suppliers and brands:** build and refresh lists of salons per city and service, with phone numbers and review volumes for territory planning.
- **Price monitoring:** track service menus of competitor salons on a schedule and get UPDATED rows only when a price or duration actually moved.
- **Market research:** compare rating distributions, review counts and photo depth across cities and service categories.
- **Review analysis:** collect client review text per salon for sentiment or quality scoring.
- **Lead generation:** export enriched salon profiles (contacts, hours, staff, legal identifiers where published) into your CRM.

### Data You Get

> Sample shape: values are illustrative placeholders, not from a live record.

| Field | Example |
| --- | --- |
| `name` | Sample Salon |
| `url` | https://www.planity.com/sample-salon-75001-paris |
| `salonId` | 00000000-0000-0000-0000-000000000000 |
| `categories` | \["Hair salon"] |
| `address` | 1, Sample Street |
| `city` | Paris |
| `postalCode` | 75001 |
| `phoneNumber` | +330000000000 |
| `email` | salon@example.com |
| `rating` | 4.85 |
| `reviewsCount` | 478 |
| `ratingAmbiance` | 4.87 |
| `serviceCount` | 42 |
| `priceFrom` | 25 |
| `priceTo` | 180 |
| `imageUrl` | https://mms.cdn.global.prodsharedsvc.planityapp.com/planity/image/upload/t_d_main,f_auto/00000000 |
| `imageUrls` | \["https://mms.cdn.global.prodsharedsvc.planityapp.com/planity/image/upload/t_d_main,f_auto/00000000"] |
| `photosCount` | 11 |
| `openingHoursText` | Monday: 10:00-19:00; ...; Sunday: closed |
| `openHours` | \[{"day": "Monday", "status": "open", "spans": \[{"openFrom": "10:00", "openTill": "19:00"}]}] |
| `acceptsGiftVoucher` | false |
| `acceptsGiftVoucher` | false |
| `staff` | \[{"name": "Sample Name", "title": "Colour specialist", "pictureUrl": "https://..."}] |
| `staffCount` | 6 |

Each salon row is one dataset record; client reviews nest inside it (the Client reviews tab unwinds them), and the Service menus tab unwinds every bookable service variant to one row.

### How to Use

1. Pick a mode: search by city and service, or paste links.
2. Fill the mode's fields: cities (plus optional service, keyword, sort and minimum rating) for search mode; salon or results links for URL mode.
3. Set Max items to bound the run; keep Fetch salon profiles on for service menus and reviews.
4. Run the actor and export the dataset, or connect an MCP connector to pipe results into your apps.

**Search Paris barbers:**

```json
{
  "mode": "search",
  "market": "fr",
  "cities": ["Paris"],
  "service": "Barbier",
  "maxItems": 20
}
```

**Track service prices in Lyon incrementally:**

```json
{
  "mode": "search",
  "cities": ["Lyon"],
  "service": "Coiffeur",
  "maxItems": 100,
  "fetchDetails": true,
  "incrementalMode": true,
  "stateKey": "lyon-coiffeur-watch"
}
```

**Read specific salons (any country, locale prefix decides):**

```json
{
  "mode": "urls",
  "urls": [
    "https://www.planity.com/y-coiffure-75004-paris",
    "https://www.planity.com/de-DE/ck-hair-atelier-58511-ludenscheid"
  ],
  "fetchDetails": true,
  "maxReviews": 20
}
```

**Walk a results link:**

```json
{
  "mode": "urls",
  "urls": ["https://www.planity.com/coiffeur/paris-75"],
  "maxItems": 200
}
```

#### Run it from your code

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("abotapi/planity-salon-scraper").call(run_input={
    "mode": "search",
    "cities": ["Paris"],
    "service": "Barbier",
    "maxItems": 20,
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["name"], item.get("priceFrom"))
```

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });
const run = await client.actor('abotapi/planity-salon-scraper').call({
    mode: 'search',
    cities: ['Paris'],
    service: 'Barbier',
    maxItems: 20,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.map((i) => [i.name, i.priceFrom]));
```

You can also connect the actor through Make, Zapier, n8n, Google Sheets or webhooks from the Integrations tab.

#### Card vs profile fields

Every row ships the catalogue card: name, address, city, phone, rating and review counts, opening hours, photo gallery, the written description, booking-feature flags and the salon's recent reviews. With Fetch salon profiles on (the default), the salon's own page adds the service menu with prices and durations, staff, sub-ratings, access notes, opening-hours exceptions and legal identifiers where the salon publishes them.

#### Resume and recurring updates

Incremental mode saves a baseline (keyed to the cities, service, country, links and toggles you ran with) and classifies each salon on the next run: NEW, UPDATED, UNCHANGED or EXPIRED. Fields that move without the salon changing (booking-slot availability, appointment counters, the review list itself) are excluded from change detection; review counters and ratings stay tracked, and any service-price or duration move bills the row UPDATED. Unchanged salons are suppressed (and never charged) unless Emit unchanged records is on. Resume from run ID treats a previous run's records as already collected.

### Send results into your apps (MCP connectors)

Optionally send collected salons into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize a connector under Apify, then select it in the `mcpConnectors` input: Notion gets a rich page-per-salon export; other connectors get a best-effort write/digest. Set `notionParentPageUrl` for Notion and `maxNotifyListings` to cap the export per connector. Leave the input empty to skip: the export never changes the dataset output, and the connector receives a condensed, human-readable summary per salon (the complete record always stays in the Apify dataset). Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com).

### Input Parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `mode` | select | search | Search by city and service, or paste links. |
| `market` | select | fr | Country catalogue (France, Germany, Belgium), used by both modes: search mode runs on it, and a pasted link without a locale prefix falls back to it (a `/de-DE/`-style prefix picks its own country). |
| `cities` | stringList | \["Paris"] | Search mode only. Plain city names (Paris, Lyon, Berlin, Bruxelles). |
| `service` | text | Coiffeur | Search mode only. Plain service name narrowing the feed server-side (Coiffeur, Barbier, Manucure, Spa, Massage). Empty searches every service. Pasted results links carry their own service in the link path. |
| `searchQuery` | text | (empty) | Keyword matched against salon names and descriptions; both modes (search and pasted results-page links). |
| `sortBy` | select | relevance | Results order: recommended, rating, price low-first, price high-first; both modes (search and pasted results-page links). |
| `minRating` | number | (empty) | Keep only salons rated at least this many stars; both modes (pasted salon links are always read in full). |
| `urls` | stringList | (7 example links) | Salon links (read in full), service-and-city results links (walked forward) or bare salon slugs; one example of every supported shape. |
| `fetchDetails` | checkbox | true | Read each salon's own page for the service menu, staff, sub-ratings, hours and legal identifiers. |
| `fetchReviews` | checkbox | true | Attach client reviews to each salon as a nested list. Reviews ride pages this run already reads; no extra requests. |
| `maxReviews` | number | 20 | Upper bound on reviews per salon (the pages carry up to 20). 0 skips review collection. |
| `maxItems` | number | 20 | Hard cap on salon records for the whole run. |
| `maxPages` | number | 0 | Bounds results pages per city walk; 0 defers to Max items. |
| `proxyConfiguration` | proxy | Residential, FR | Residential exits serve every salon page reliably; the exit country follows the searched country unless pinned here. |
| `resumeFromRunId` | text | (empty) | A previous run's ID whose records are treated as already collected. |
| `incrementalMode` | checkbox | false | Classify NEW / UPDATED / UNCHANGED / EXPIRED against the saved baseline. |
| `stateKey` | text | (empty) | Baseline name; empty derives it from the run's scope. |
| `emitUnchanged` | checkbox | false | Also push salons whose tracked fields did not change. |
| `emitExpired` | checkbox | false | After a complete scan, push baseline salons this run did not see. |
| `ignoreFieldsForChanges` | stringList | (empty) | Extra fields excluded from change detection beyond the built-in volatile set. |
| `mcpConnectors` | stringList | (empty) | Connector IDs to export collected salons to. |
| `notionParentPageUrl` | text | (empty) | Parent page URL for the Notion connector. |
| `maxNotifyListings` | number | 50 | Upper bound on records handed to each connector. |

### Output Example

> Sample shape: values are illustrative placeholders, not from a live record.

```json
{
  "recordId": "planity:fr:00000000-0000-0000-0000-000000000000",
  "kind": "salon",
  "salonId": "00000000-0000-0000-0000-000000000000",
  "slug": "sample-salon-75001-paris",
  "name": "Sample Salon",
  "url": "https://www.planity.com/sample-salon-75001-paris",
  "market": "fr",
  "country": "France",
  "currency": "EUR",
  "categories": ["Hair salon"],
  "address": "1, Sample Street",
  "city": "Paris",
  "postalCode": "75001",
  "latitude": 48.86,
  "longitude": 2.35,
  "phoneNumber": "+330000000000",
  "rating": 4.85,
  "reviewsCount": 478,
  "openingHoursText": "Monday: 10:00-19:00; Tuesday: 10:00-19:00; Wednesday: 10:00-19:00; Thursday: 10:00-20:00; Friday: 10:00-21:00; Saturday: 10:00-19:00; Sunday: closed",
  "hasAvailabilities": true,
  "imageUrl": "https://mms.cdn.global.prodsharedsvc.planityapp.com/planity/image/upload/t_d_main,f_auto/00000000-0000-0000-0000-000000000000",
  "imageUrls": ["https://mms.cdn.global.prodsharedsvc.planityapp.com/planity/image/upload/t_d_main,f_auto/00000000-0000-0000-0000-000000000000"],
  "photosCount": 11,
  "services": [
    {
      "groupName": "Cut",
      "name": "Sample cut and style",
      "durationMin": 30,
      "price": 35,
      "bookable": true
    }
  ],
  "serviceCount": 42,
  "priceFrom": 25,
  "priceTo": 180,
  "staff": [{"name": "Sample Name", "title": "Colour specialist"}],
  "staffCount": 6,
  "reviews": [
    {"reviewRating": 5, "reviewDate": "2026-01-01", "reviewText": "Sample review text"}
  ],
  "reviewsDelivered": 20,
  "reviewsCollected": true,
  "detailFetched": true,
  "sourceMode": "search",
  "scrapedAt": "2026-01-01T00:00:00+00:00"
}
```

### Plan Requirement

This actor is pay-per-event: a start event per run, a per-record event for each salon returned, and enrichment events when profile pages were read or reviews collected. See the Pricing tab for the current per-event rates; Max items bounds how many records a run can return.

### FAQ

#### How much does it cost to run?

You pay per event: one start event per run, one record event per salon returned, and the enrichment events only for salons whose profile page was actually read or whose reviews were actually collected. Suppressed unchanged salons in incremental mode bill nothing. See the Pricing tab for the current rates, and keep Max items in mind when scheduling.

#### Is it legal to scrape Planity?

This actor collects only publicly available salon information. You remain responsible for using the data in line with the site's terms of use, privacy law such as GDPR, and your own applicable regulations; personal data (staff names, review authors) carries a lawful-basis requirement on your side.

#### Can I get only new or changed salons on a schedule?

Yes: run with incrementalMode on and a fixed stateKey. The first run bootstraps the baseline; later runs push only NEW and UPDATED salons (plus EXPIRED tombstones when Emit expired is on and the scan was complete). A service-price move bills the row UPDATED with changedFields naming the moved fields.

#### Why did a run return fewer results than the city's total?

Max items bounds the run, and each city walk gets an equal share; salons also publish at different depths (some hide prices or durations, and preview listings without live menus are excluded). Raise Max items or walk again to page deeper.

#### Why did my run fail instead of returning an empty dataset?

When every request this run made was refused, the run fails loudly with a connection-problem message instead of returning an empty dataset: an empty result should mean an empty search, never a blocked page. Re-run shortly; the connections recover on their own.

#### Can I use it with AI agents or MCP?

Yes. The actor ships an MCP connector surface: select a connector in mcpConnectors and each collected salon is also piped to that app as a condensed summary. The full records stay in the dataset for API consumption by any agent.

### 🔗 More scrapers you might like

Pair this actor with these related scrapers from the same team:

<table>
<tr><td>🧩 <a href="https://apify.com/abotapi/booksy-beauty-business-scraper"><b>Booksy Scraper</b></a><br>Scrape Booksy beauty businesses across the US, UK and Poland by category, location or...</td><td>🧩 <a href="https://apify.com/abotapi/fresha-salon-spa-scraper"><b>Fresha Scraper</b></a><br>Scrape Fresha salon and spa listings: business profiles, service menus with prices and...</td></tr>
<tr><td>🛒 <a href="https://apify.com/abotapi/wolt-restaurants-scraper"><b>Wolt Scraper</b></a><br>Scrape Wolt restaurants and full menus by city or URL. Extract 60+ fields including...</td><td>🛒 <a href="https://apify.com/abotapi/doordash-scraper"><b>DoorDash Scraper</b></a><br>Extract structured doordash.com data at scale. Search by keyword or paste store URLs to...</td></tr>
<tr><td>📇 <a href="https://apify.com/abotapi/gowork-eu-scraper"><b>GoWork Scraper</b></a><br>Extract company profiles from GoWork France and Germany, including contact details...</td><td>📇 <a href="https://apify.com/abotapi/pagesjaunes-fr-scraper"><b>PagesJaunes Scraper</b></a><br>Scrape PagesJaunes.fr (French Yellow Pages) business listings across France. Extract...</td></tr>
</table>

👉 [Browse all abotapi scrapers](https://apify.com/abotapi)

### 💬 Support

- 🐞 **Found a bug or a missing field?** Open a ticket on the [Issues tab](https://apify.com/abotapi/planity-salon-scraper/issues/open). We usually reply within hours.
- 🛠️ **Need another site, extra fields or a private build?** Email <contact@abotapi.com>.
- ⭐ **Enjoying it?** A quick review on the actor page helps other users find it.

# Actor input Schema

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

Search by city and service names, or paste your own links: service-and-city results pages walk forward, a salon link (or a bare salon slug) is always read in full.

## `market` (type: `string`):

The catalogue to search (each country has its own salons and service names). Applies to both modes: search mode runs on this country, and a pasted link without its own locale prefix falls back to it - a link that carries a /de-DE/ or /fr-BE/ prefix picks its own country instead.

## `cities` (type: `array`):

Search mode only (ignored when Links are provided). Plain city names, for example Paris, Lyon, Berlin, Bruxelles. Each city runs on the selected country. Supported countries: France, Germany, Belgium.

## `service` (type: `string`):

Search mode only (ignored when Links are provided - pasted results links carry their own service in the link path). Plain service name, for example Coiffeur, Barbier, Manucure, Spa, Massage, Ongles, Brushing, Tatouage. Narrows the results feed server-side. Leave empty to search every service.

## `searchQuery` (type: `string`):

Optional keyword filter matched against salon names and descriptions, for example 'balayage' or 'barbier'. Applies to both modes: search mode and every pasted results-page link walk the feed with it.

## `sortBy` (type: `string`):

Order of the results feed. Applies to both modes: search mode and every pasted results-page link walk in this order. Price sorts follow the catalogue's own price ranking; salons that publish no prices sort last.

## `minRating` (type: `number`):

Keep only salons rated at least this many stars. Applies to both modes: the collected rows of search mode and of pasted results-page link walks are filtered by it (pasted salon links are always read in full).

## `urls` (type: `array`):

Service-and-city results links (they walk forward), salon links (always read in full; the link's locale prefix also picks the country) or bare salon slugs. A link's path decides what it points at - its readable slug never does.

## `fetchDetails` (type: `boolean`):

Read each collected salon's own page for the service menu with prices and durations, staff, sub-ratings, opening-hours exceptions, access notes and legal identifiers. Turning this off returns the faster results-card shape.

## `fetchReviews` (type: `boolean`):

Attach each collected salon's client reviews (rating, text, date) to the salon record as a nested list. Reviews ride pages this run already reads, so this toggle costs no extra requests. Applies to both modes; a salon with no published reviews ships without them.

## `maxReviews` (type: `integer`):

Upper bound on client reviews collected per salon (the pages this actor reads carry up to 20). 0 skips review collection entirely.

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

Hard cap on salon records for the whole run. The walk stops once it is reached.

## `maxPages` (type: `integer`):

Does NOT cap the run on its own: 0 or empty walks every results page and defers to Max items. A positive value bounds how many results pages each city walk covers.

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

Residential exits serve every salon page reliably; the exit country defaults to the country being searched. The run falls back to a backup connection automatically and tells you in the log. Pick a country pin to override the default.

## `resumeFromRunId` (type: `string`):

A previous run's ID whose records are treated as already collected: they are seeded as seen and skipped this run.

## `incrementalMode` (type: `boolean`):

Compare each salon against the saved baseline and classify NEW / UPDATED / UNCHANGED / EXPIRED. Unchanged salons are suppressed unless Emit unchanged is on. Fields that move without the salon changing (booking-slot availability, appointment counters, the review list itself) are excluded from change detection; the cities, service, country, links and toggles you run with are part of the scope, so changing them starts a fresh baseline.

## `stateKey` (type: `string`):

Optional name for the incremental baseline. Leave empty to derive it from the run's scope (cities, service, country, links, toggles); set the same key on every scheduled run to share one baseline.

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

Also push salons whose tracked fields did not change (changeType UNCHANGED) instead of suppressing them.

## `emitExpired` (type: `boolean`):

After a complete scan, push baseline salons this run did not see (changeType EXPIRED, for example salons that closed). Never fires on a capped, resumed or partially failed walk.

## `ignoreFieldsForChanges` (type: `array`):

Extra record fields excluded from change detection beyond the built-in volatile set (booking availability, appointment counters, the nested review list).

## `mcpConnectors` (type: `array`):

Connector IDs to export the collected records to after the run (Apify MCP). Leave empty to skip the export.

## `notionParentPageUrl` (type: `string`):

Parent page URL when one of the connectors is Notion.

## `maxNotifyListings` (type: `integer`):

Upper bound on records handed to each connector.

## Actor input object example

```json
{
  "mode": "search",
  "market": "fr",
  "cities": [
    "Paris"
  ],
  "service": "Coiffeur",
  "sortBy": "relevance",
  "urls": [
    "https://www.planity.com/coiffeur/paris-75",
    "https://www.planity.com/y-coiffure-75004-paris",
    "https://www.planity.com/de-DE/friseur/berlin",
    "https://www.planity.com/de-DE/ck-hair-atelier-58511-ludenscheid",
    "https://www.planity.com/fr-BE/institut-de-beaute/libin",
    "https://www.planity.com/fr-BE/lg-sens-4880-aubel-GOzz",
    "y-coiffure-75004-paris"
  ],
  "fetchDetails": true,
  "fetchReviews": true,
  "maxReviews": 20,
  "maxItems": 20,
  "maxPages": 0,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "FR"
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "maxNotifyListings": 50
}
```

# Actor output Schema

## `overview` (type: `string`):

Every salon collected by the run.

## `detail` (type: `string`):

Profile fields from each salon's own page.

## `services` (type: `string`):

Every bookable service, one row per service.

## `reviews` (type: `string`):

Client reviews unwound one row per review.

## `changes` (type: `string`):

Change classification per salon when incremental mode is on.

# 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 = {
    "cities": [
        "Paris"
    ],
    "service": "Coiffeur",
    "urls": [
        "https://www.planity.com/coiffeur/paris-75",
        "https://www.planity.com/y-coiffure-75004-paris",
        "https://www.planity.com/de-DE/friseur/berlin",
        "https://www.planity.com/de-DE/ck-hair-atelier-58511-ludenscheid",
        "https://www.planity.com/fr-BE/institut-de-beaute/libin",
        "https://www.planity.com/fr-BE/lg-sens-4880-aubel-GOzz",
        "y-coiffure-75004-paris"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "FR"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/planity-salon-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 = {
    "cities": ["Paris"],
    "service": "Coiffeur",
    "urls": [
        "https://www.planity.com/coiffeur/paris-75",
        "https://www.planity.com/y-coiffure-75004-paris",
        "https://www.planity.com/de-DE/friseur/berlin",
        "https://www.planity.com/de-DE/ck-hair-atelier-58511-ludenscheid",
        "https://www.planity.com/fr-BE/institut-de-beaute/libin",
        "https://www.planity.com/fr-BE/lg-sens-4880-aubel-GOzz",
        "y-coiffure-75004-paris",
    ],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "FR",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/planity-salon-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 '{
  "cities": [
    "Paris"
  ],
  "service": "Coiffeur",
  "urls": [
    "https://www.planity.com/coiffeur/paris-75",
    "https://www.planity.com/y-coiffure-75004-paris",
    "https://www.planity.com/de-DE/friseur/berlin",
    "https://www.planity.com/de-DE/ck-hair-atelier-58511-ludenscheid",
    "https://www.planity.com/fr-BE/institut-de-beaute/libin",
    "https://www.planity.com/fr-BE/lg-sens-4880-aubel-GOzz",
    "y-coiffure-75004-paris"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "FR"
  }
}' |
apify call abotapi/planity-salon-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,abotapi/planity-salon-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/VyDCPb0CjAFpl2Qsc/builds/stNdBYdYviRIDgTdy/openapi.json
