{
  "openapi": "3.0.1",
  "info": {
    "title": "Planity Scraper - Beauty Salons, Services, Prices & Reviews",
    "description": "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.",
    "version": "0.1",
    "x-build-id": "stNdBYdYviRIDgTdy"
  },
  "servers": [
    {
      "url": "https://api.apify.com/v2"
    }
  ],
  "paths": {
    "/acts/abotapi~planity-salon-scraper/run-sync-get-dataset-items": {
      "post": {
        "operationId": "run-sync-get-dataset-items-abotapi-planity-salon-scraper",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/acts/abotapi~planity-salon-scraper/runs": {
      "post": {
        "operationId": "runs-sync-abotapi-planity-salon-scraper",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor and returns information about the initiated run in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/runsResponseSchema"
                }
              }
            }
          }
        }
      }
    },
    "/acts/abotapi~planity-salon-scraper/run-sync": {
      "post": {
        "operationId": "run-sync-abotapi-planity-salon-scraper",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "inputSchema": {
        "type": "object",
        "required": [
          "mode"
        ],
        "properties": {
          "mode": {
            "title": "Mode",
            "enum": [
              "search",
              "urls"
            ],
            "type": "string",
            "description": "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.",
            "default": "search"
          },
          "market": {
            "title": "Country",
            "enum": [
              "fr",
              "de",
              "be"
            ],
            "type": "string",
            "description": "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.",
            "default": "fr"
          },
          "cities": {
            "title": "Cities",
            "type": "array",
            "description": "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.",
            "items": {
              "type": "string"
            }
          },
          "service": {
            "title": "Service (optional)",
            "type": "string",
            "description": "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": {
            "title": "Keyword (optional)",
            "type": "string",
            "description": "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": {
            "title": "Sort results by",
            "enum": [
              "relevance",
              "rating",
              "price-asc",
              "price-desc"
            ],
            "type": "string",
            "description": "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.",
            "default": "relevance"
          },
          "minRating": {
            "title": "Min rating",
            "minimum": 0,
            "maximum": 5,
            "type": "number",
            "description": "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": {
            "title": "Links",
            "type": "array",
            "description": "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.",
            "default": [
              "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"
            ],
            "items": {
              "type": "string"
            }
          },
          "fetchDetails": {
            "title": "Fetch salon profiles",
            "type": "boolean",
            "description": "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.",
            "default": true
          },
          "fetchReviews": {
            "title": "Fetch client reviews",
            "type": "boolean",
            "description": "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.",
            "default": true
          },
          "maxReviews": {
            "title": "Max reviews per salon",
            "minimum": 0,
            "type": "integer",
            "description": "Upper bound on client reviews collected per salon (the pages this actor reads carry up to 20). 0 skips review collection entirely.",
            "default": 20
          },
          "maxItems": {
            "title": "Max items",
            "minimum": 1,
            "type": "integer",
            "description": "Hard cap on salon records for the whole run. The walk stops once it is reached.",
            "default": 20
          },
          "maxPages": {
            "title": "Max pages per city",
            "minimum": 0,
            "type": "integer",
            "description": "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.",
            "default": 0
          },
          "proxyConfiguration": {
            "title": "Proxy configuration",
            "type": "object",
            "description": "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.",
            "default": {
              "useApifyProxy": true,
              "apifyProxyGroups": [
                "RESIDENTIAL"
              ],
              "apifyProxyCountry": "FR"
            }
          },
          "resumeFromRunId": {
            "title": "Resume from run ID",
            "type": "string",
            "description": "A previous run's ID whose records are treated as already collected: they are seeded as seen and skipped this run."
          },
          "incrementalMode": {
            "title": "Incremental mode",
            "type": "boolean",
            "description": "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.",
            "default": false
          },
          "stateKey": {
            "title": "State key",
            "type": "string",
            "description": "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": {
            "title": "Emit unchanged records",
            "type": "boolean",
            "description": "Also push salons whose tracked fields did not change (changeType UNCHANGED) instead of suppressing them.",
            "default": false
          },
          "emitExpired": {
            "title": "Emit expired records",
            "type": "boolean",
            "description": "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.",
            "default": false
          },
          "ignoreFieldsForChanges": {
            "title": "Ignore fields when detecting changes (incremental mode only)",
            "type": "array",
            "description": "Extra record fields excluded from change detection beyond the built-in volatile set (booking availability, appointment counters, the nested review list).",
            "items": {
              "type": "string"
            }
          },
          "mcpConnectors": {
            "title": "MCP connectors",
            "type": "array",
            "description": "Connector IDs to export the collected records to after the run (Apify MCP). Leave empty to skip the export.",
            "items": {
              "type": "string"
            }
          },
          "notionParentPageUrl": {
            "title": "Notion parent page URL",
            "type": "string",
            "description": "Parent page URL when one of the connectors is Notion."
          },
          "maxNotifyListings": {
            "title": "Max records per export",
            "minimum": 1,
            "type": "integer",
            "description": "Upper bound on records handed to each connector.",
            "default": 50
          }
        }
      },
      "runsResponseSchema": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "actId": {
                "type": "string"
              },
              "userId": {
                "type": "string"
              },
              "startedAt": {
                "type": "string",
                "format": "date-time",
                "example": "2025-01-08T00:00:00.000Z"
              },
              "finishedAt": {
                "type": "string",
                "format": "date-time",
                "example": "2025-01-08T00:00:00.000Z"
              },
              "status": {
                "type": "string",
                "example": "READY"
              },
              "meta": {
                "type": "object",
                "properties": {
                  "origin": {
                    "type": "string",
                    "example": "API"
                  },
                  "userAgent": {
                    "type": "string"
                  }
                }
              },
              "stats": {
                "type": "object",
                "properties": {
                  "inputBodyLen": {
                    "type": "integer",
                    "example": 2000
                  },
                  "rebootCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "restartCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "resurrectCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "computeUnits": {
                    "type": "integer",
                    "example": 0
                  }
                }
              },
              "options": {
                "type": "object",
                "properties": {
                  "build": {
                    "type": "string",
                    "example": "latest"
                  },
                  "timeoutSecs": {
                    "type": "integer",
                    "example": 300
                  },
                  "memoryMbytes": {
                    "type": "integer",
                    "example": 1024
                  },
                  "diskMbytes": {
                    "type": "integer",
                    "example": 2048
                  }
                }
              },
              "buildId": {
                "type": "string"
              },
              "defaultKeyValueStoreId": {
                "type": "string"
              },
              "defaultDatasetId": {
                "type": "string"
              },
              "defaultRequestQueueId": {
                "type": "string"
              },
              "buildNumber": {
                "type": "string",
                "example": "1.0.0"
              },
              "containerUrl": {
                "type": "string"
              },
              "usage": {
                "type": "object",
                "properties": {
                  "ACTOR_COMPUTE_UNITS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_WRITES": {
                    "type": "integer",
                    "example": 1
                  },
                  "KEY_VALUE_STORE_LISTS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_INTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_EXTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_SERPS": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_UNBLOCKER_UNITS": {
                    "type": "integer",
                    "example": 0
                  }
                }
              },
              "usageTotalUsd": {
                "type": "number",
                "example": 0.00005
              },
              "usageUsd": {
                "type": "object",
                "properties": {
                  "ACTOR_COMPUTE_UNITS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_WRITES": {
                    "type": "number",
                    "example": 0.00005
                  },
                  "KEY_VALUE_STORE_LISTS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_INTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_EXTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_SERPS": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_UNBLOCKER_UNITS": {
                    "type": "integer",
                    "example": 0
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}