{
  "openapi": "3.0.1",
  "info": {
    "title": "PropertyGuru SG Scraper - Property Listings, Prices & Agents",
    "description": "From $0.80/1K. Scrape PropertyGuru.com.sg sale and rental listings with 30+ structured fields, including price, PSF, floor area, tenure, nearby MRT stations, property details, agent information and GPS coordinates. Fast and reliable at scale.",
    "version": "0.3",
    "x-build-id": "DPB6tEq7KZkXsr4pd"
  },
  "servers": [
    {
      "url": "https://api.apify.com/v2"
    }
  ],
  "paths": {
    "/acts/abotapi~propertyguru-sg-scraper/run-sync-get-dataset-items": {
      "post": {
        "operationId": "run-sync-get-dataset-items-abotapi-propertyguru-sg-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~propertyguru-sg-scraper/runs": {
      "post": {
        "operationId": "runs-sync-abotapi-propertyguru-sg-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~propertyguru-sg-scraper/run-sync": {
      "post": {
        "operationId": "run-sync-abotapi-propertyguru-sg-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",
        "properties": {
          "mode": {
            "title": "1. Search mode",
            "enum": [
              "search",
              "url"
            ],
            "type": "string",
            "description": "Search mode = build URLs from district/search/filters below. URL mode = paste any propertyguru.com.sg search URL already refined in a browser (recommended for complex filters).",
            "default": "search"
          },
          "urls": {
            "title": "Search URLs (URL mode)",
            "type": "array",
            "description": "URLs to scrape. Provide propertyguru.com.sg search, single-listing, or project URLs. Tip: open propertyguru.com.sg in your browser, apply filters, then paste the resulting URL here. In URL mode, all the filter fields below are ignored. Search URLs (any district/freetext/price/sort filter, any property type path, paged /2, /3..., or the popular-location /in-<area>-d<NN> links) are paginated like a normal search. A single listing URL (/listing/for-sale-... or /listing/for-rent-...) is scraped directly as one record. A project/new-launch page (/project/...) is followed to that project's own for-sale/for-rent listings. Agent profile pages and a `market=commercial` filter are not supported (commercial listings live on the separate commercialguru.com.sg site) and are skipped with a warning in the run log rather than returning unrelated or mislabeled data.",
            "items": {
              "type": "string"
            }
          },
          "listing_type": {
            "title": "Listing Type (Search mode)",
            "enum": [
              "sale",
              "rent"
            ],
            "type": "string",
            "description": "Type of listing to search for. Search mode only.",
            "default": "sale"
          },
          "property_type": {
            "title": "Property Type",
            "enum": [
              "condo",
              "hdb",
              "landed"
            ],
            "type": "string",
            "description": "Filter by property type. Note: commercial properties are on commercialguru.com.sg, not supported here."
          },
          "search": {
            "title": "Location Search",
            "type": "string",
            "description": "Free-text location search (e.g. 'Orchard', 'Jurong', 'Tampines', 'Sentosa Cove'). Use this OR district code, not both."
          },
          "district": {
            "title": "District",
            "type": "string",
            "description": "A Singapore district: a plain area name (e.g. 'Orchard', 'Bukit Timah', 'Tampines') or a D-code (D01-D28, e.g. D09). Bare numbers are also accepted for back-compat (e.g. '9' = D09). Use this OR freetext search, not both. An unrecognized value fails the run with an error rather than silently searching all of Singapore."
          },
          "min_price": {
            "title": "Min Price (SGD)",
            "type": "integer",
            "description": "Minimum price in Singapore Dollars"
          },
          "max_price": {
            "title": "Max Price (SGD)",
            "type": "integer",
            "description": "Maximum price in Singapore Dollars"
          },
          "bedrooms": {
            "title": "Bedrooms",
            "minimum": 0,
            "maximum": 10,
            "type": "integer",
            "description": "Number of bedrooms (0=Studio)"
          },
          "sort": {
            "title": "Sort By",
            "enum": [
              "date",
              "price",
              "psf"
            ],
            "type": "string",
            "description": "Sort results by date (newest), price, or price per sqft",
            "default": "date"
          },
          "sort_order": {
            "title": "Sort Order",
            "enum": [
              "asc",
              "desc"
            ],
            "type": "string",
            "description": "Sort direction",
            "default": "desc"
          },
          "enable_detail_pages": {
            "title": "Fetch Detail Pages",
            "type": "boolean",
            "description": "Fetch each listing's detail page for richer attributes: latitude/longitude, full description, project name, TOP year, total units, facilities, floor plans, nearby schools, and virtual tours. Recommended. Applies regardless of search mode (Search or URL).",
            "default": true
          },
          "fetch_reviews": {
            "title": "Fetch Reviews",
            "type": "boolean",
            "description": "Opt-in: fetch the full review list from each listing's linked project page and agent profile page (reviewer, rating, date, text, replies where the site has them). Adds one `reviews` array to the listing row and bills a review-enrichment surcharge per 10 reviews collected -- never charged unless this is on.",
            "default": false
          },
          "max_reviews_per_listing": {
            "title": "Max Reviews Per Listing",
            "minimum": 1,
            "maximum": 500,
            "type": "integer",
            "description": "Cap on reviews collected per listing (combined project + agent reviews), when Fetch Reviews is on.",
            "default": 20
          },
          "max_properties": {
            "title": "Max Properties",
            "minimum": 0,
            "type": "integer",
            "description": "Maximum number of properties to scrape (0=unlimited)",
            "default": 10
          },
          "max_pages": {
            "title": "Max Pages",
            "minimum": 1,
            "type": "integer",
            "description": "Maximum number of search result pages to scrape (20 listings per page)",
            "default": 20
          },
          "proxy": {
            "title": "Proxy configuration",
            "type": "object",
            "description": "Apify Residential in Singapore is required - PropertyGuru is geo-restricted to SG IPs. IP rotation is handled automatically. Free-tier accounts automatically use a more conservative path that disables detail-page enrichment for reliability; paid plans get the full feature set.",
            "default": {
              "useApifyProxy": true,
              "apifyProxyGroups": [
                "RESIDENTIAL"
              ],
              "apifyProxyCountry": "SG"
            }
          },
          "session_ttl_seconds": {
            "title": "Session TTL (seconds)",
            "minimum": 120,
            "maximum": 1800,
            "type": "integer",
            "description": "Internal: how often the scraper refreshes its connection state. Hidden from the form because picking a safe value is the scraper's job, not the user's; still accepted from the API so existing saved inputs and schedules keep working unchanged.",
            "default": 600
          },
          "reuse_project_cache": {
            "title": "Reuse Project Cache",
            "type": "boolean",
            "description": "Internal: when enabled, the scraper caches project-level fields (project_name, TOP year, total_units, facilities, nearby_schools, coordinates, developer, tenure) from the first listing in each condo project and reuses them as a fallback if a later same-project listing's detail page fails to load. Every listing is still fetched individually, so per-listing fields such as price_label, description and unit details are always captured accurately. Hidden from the form; still accepted from the API so existing saved inputs and schedules keep working unchanged.",
            "default": true
          },
          "dataset_name": {
            "title": "Dataset Name",
            "type": "string",
            "description": "Internal: custom dataset name (empty = the default dataset). Hidden from the form; still accepted from the API so existing saved inputs and schedules keep working unchanged."
          },
          "clear_dataset": {
            "title": "Clear Dataset",
            "type": "boolean",
            "description": "Internal: clear the dataset before scraping. Hidden from the form; still accepted from the API so existing saved inputs and schedules keep working unchanged.",
            "default": false
          },
          "detail_concurrency": {
            "title": "Detail Page Concurrency",
            "minimum": 1,
            "maximum": 5,
            "type": "integer",
            "description": "Internal: number of parallel detail-page fetches. Hidden from the form because picking a safe rate is the scraper's job, not the user's; still accepted from the API so existing saved inputs and schedules keep working unchanged.",
            "default": 1
          },
          "resumeFromRunId": {
            "title": "Resume from a previous run",
            "type": "string",
            "description": "Paste a previous run ID or dataset ID to continue a large crawl of listings without returning or charging for listings already collected there. Use this after an interrupted run, or when continuing a search pull in another run. For recurring daily monitoring of the same search, use Incremental mode below instead."
          },
          "incrementalMode": {
            "title": "Incremental changes for scheduled runs",
            "type": "boolean",
            "description": "Turn this on for daily or recurring monitoring. The first run returns all matching listings as NEW. Later runs normally return only NEW, UPDATED, and REAPPEARED listings. Turn on \"Emit unchanged\" or \"Emit expired\" only when you also want those listings returned (and billed). State is kept separately for each search/filter/URL and detail-page setup; use State key when you want to name or deliberately share a monitoring campaign. To continue one specific interrupted run instead, use Resume from a previous run above.",
            "default": false
          },
          "stateKey": {
            "title": "State key (optional, incremental mode only)",
            "type": "string",
            "description": "Optional. Name this monitoring campaign to keep its state stable, or to deliberately share state across differently-configured runs. Leave empty to let the actor derive a key automatically from the search/filter/URL and detail-page settings — different searches then never mix state with each other."
          },
          "emitUnchanged": {
            "title": "Emit unchanged listings (incremental mode only)",
            "type": "boolean",
            "description": "Off by default. Turn on to also return listings that have not changed since the last run, marked UNCHANGED. This returns — and bills — extra rows you already have, so leave it off unless you specifically want the full snapshot every run.",
            "default": false
          },
          "emitExpired": {
            "title": "Emit expired listings (incremental mode only)",
            "type": "boolean",
            "description": "Off by default. Turn on to also return listings that were present in a previous run but are no longer found, marked EXPIRED. Only produced once a run has fully scanned the tracked search — not when Max pages/properties capped it, a page was blocked, or Resume was used. This returns — and bills — extra synthetic rows, so leave it off unless you need expiry tracking.",
            "default": false
          },
          "ignoreFieldsForChanges": {
            "title": "Ignore fields when detecting changes (incremental mode only)",
            "type": "array",
            "description": "Output field names that should NOT make a listing count as UPDATED. recency is always ignored, because it is a relative-age render (\"37m ago\") that ticks over on every re-scrape even though nothing about the listing changed. Add nested field paths with a dot (for example agent.phone) to ignore only that exact nested field, or a bare name (for example description) to ignore it anywhere it appears. Ignored fields are still returned in the output.",
            "items": {
              "type": "string"
            }
          },
          "mcpConnectors": {
            "title": "Pipe results into your apps (optional)",
            "type": "array",
            "description": "Optionally send the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize a connector once under Apify → Settings → Integrations, then select it here. The connector receives a condensed, human-readable summary per item (title + key fields), not the full JSON — the complete record stays in the dataset. Leave empty to skip. Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com)."
          },
          "notionParentPageUrl": {
            "title": "Notion parent page (Notion connector only)",
            "type": "string",
            "description": "URL (or id) of the Notion page under which item pages are created. Required to enable the Notion export; ignored by other connectors."
          },
          "maxNotifyListings": {
            "title": "Max items to export per connector",
            "minimum": 1,
            "maximum": 1000,
            "type": "integer",
            "description": "Cap on items written to each connector per run. Does not affect the dataset.",
            "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
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}