# Pap.fr 🇫🇷 \[$1.5💰] French Private-Seller RE Scraper (`memo23/pap-scraper`) Actor

🔥 $1.5/1K · Pap.fr scraper for private French sellers — 30+ fields per row from rich JSON-LD: price · m² · rooms · DPE · lat-lng · seller · breadcrumb hierarchy (région/département/ville). Filter mode + URL mode + price-band split (lift the ~370-result cap). Apify Residential FR. No browser

- **URL**: https://apify.com/memo23/pap-scraper.md
- **Developed by:** [Muhamed Didovic](https://apify.com/memo23) (community)
- **Categories:** Real estate, Lead generation, Agents
- **Stats:** 19 total users, 18 monthly users, 100.0% runs succeeded, NaN bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $1.50 / 1,000 results

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are a software tools running on the Apify platform, for all kinds of web data extraction and automation use cases.
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.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp.md).

If your project is in a different language, use 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

## $1.5/1K ❤️ Pap.fr Scraper 🇫🇷 📞 Phones · Metro · DPE ⚡

#### How it works

![How Pap.fr Scraper works](https://raw.githubusercontent.com/muhamed-didovic/muhamed-didovic.github.io/main/assets/how-it-works-pap.png)
----

Pap.fr scraper for the French **private-seller** real-estate portal — no agencies, every listing is a direct buyer-to-seller deal. **30+ data points per property row**, fetched through Pap.fr's **iOS-app JSON API** (`api-ios.pap.fr`) — same backend the official Pap mobile app uses, so the row shape is rich, structured, and stable. Paste any mix of the **2 URL kinds** below and the actor auto-classifies each one:

| Input | Row(s) emitted |
|---|---|
| **Listing URL** — `/annonce/{transaction}-{rest}-g{geoId}[-{filter-slug}]` (e.g. `/annonce/vente-appartements-paris-7e-g37774-studio-a-partir-de-1-chambres-jusqu-a-150000-euros`). Filter slug supports `studio`, `a-partir-de-N-{pieces\|chambres\|m2\|euros}`, `jusqu-a-N-…`. | **N property rows** — one API call returns up to 300 cards, then we fan out one detail-fetch per card. |
| **Property detail URL** — `/annonces/{slug}-r{numericId}` (e.g. `/annonces/appartement-paris-15e-75015-r453301302`) | 1 property row: price, m², rooms, bedrooms, phones (when seller revealed), DPE/GES, lat/lng, metro stations, full photo URLs. |

Pap.fr is **Particulier à Particulier** — every seller is a private individual. The iOS API exposes phones natively when the seller has toggled "show phone" (typically 20–40 % of listings). When the URL has amenity filters (`avec-piscine`, `ascenseur`, `balcon-terrasse`, `dernier-etage`, …) the actor automatically enriches each candidate with the HTML page's `additionalProperty[]` list and **post-filters** the dataset so you only get rows that actually match.

> Pure HTTP. No Puppeteer, no Playwright, no headless Chromium, no third-party Cloudflare-bypass service.

#### Why this is fast

The Pap.fr iOS app talks to `api-ios.pap.fr` with just five static headers and no auth — no Cloudflare challenge from clean residential IPs. One search call returns **up to 300 property cards in one ~300 KB JSON response** (vs. nine 100-KB HTML pages with overlap-dedup overhead in the old pipeline). Each detail row is ~2–3 KB of JSON instead of ~70 KB of HTML — roughly a **95 % bandwidth saving**.

#### Fields you get per row

The iOS API surfaces fields that the HTML page doesn't expose structurally:

- **Property core** — propertyId, sku, transactionType (vente/location), title, description (full HTML-stripped), headline (`texte_accroche`), propertyType, livingArea (m²), rooms (`nb_pieces`), bedrooms (`nb_chambres_max`)
- **Pricing** — price (€ numeric), priceFormatted, currency, **priceValid** (`prix_valide` — is the listing's price still current?)
- **Location** — city, postalCode (inferred from Paris arrondissement title when not explicit), country, latitude, longitude
- **Energy** — dpeRating (A–G) + **dpeDescription** (full kWh/m² explanation), gesRating + **gesDescription**
- **Seller contact** — `phones[]` (native `telephones` from the iOS API), `hasEmail` (contactable via Pap.fr email gateway), `refusesDemarchage` (seller opted out of cold marketing), **appelVideo** (`["whatsapp", …]` when seller offers video call), **virtualTourUrl**, **sellerPersonalSite**
- **Transport** — `transports[]` with **station names + line slugs** (e.g. `{label: "Poissonnière", lines: ["metro-7"]}`)
- **Media** — `imageUrls[]` (full gallery, **high-res `-p1.jpg`** URLs), photosCount
- **Reference** — `reference_courte` (e.g. `F86/1075`), `listedDate` (e.g. `21 mai 2026`)
- **Raw** — `rawSource` (the full iOS `annonce` object, so you don't lose any field we didn't promote to a top-level column)

When the URL has amenity tokens (handled via HTML enrichment), rows additionally carry: **`address` (street-level)**, **`additionalProperties[]`** (Ascenseur, Dernier étage, Balcon, Terrasse, Parking, Piscine, …), **`breadcrumb[]`** (7-level geographic hierarchy), and the `source: "ios-api+html"` tag.

---

### Pipeline

Pap.fr's iOS API is the primary fetcher. Pure HTTP via impit (Rust+rustls TLS fingerprint). Each iOS call rotates through Apify Residential FR sessions for IP diversity.

1. **Search call** — `GET /app/annonces?produit={vente|location}&geo[ids][]=N[&typesbien[]=…&prix[min]=…&prix[max]=…&nb_pieces[min]=…&nb_chambres[min]=…&surface[min]=…]` returns up to 300 property cards in one call. Hard cap at 300; use `splitByPrice` to break past.
2. **Detail call** — `GET /app/annonces/detail?id={id}` returns the rich JSON for one property. 2–3 KB per row.
3. **Filter resolution** — when filter mode is used and a city isn't in our 30-entry cache (`papPlaces.ts`), we fall through to `GET /app/gis?q={city}` — Pap.fr's own geo autocomplete — so **every French commune resolves dynamically**.
4. **Amenity post-filter** — when the URL contains amenity slugs (`avec-piscine`, `ascenseur`, …) that the iOS API silently ignores, the handler fetches the HTML detail page, parses JSON-LD `additionalProperty[]`, and drops rows that don't satisfy ALL requested amenities. Adds 1 HTTP per candidate, only for amenity URLs.
5. **HTML escape hatch** — set `forceHtmlDetail: true` in input to route every row through the HTML JSON-LD parser instead of iOS (slower but provides amenity-rich additionalProperties + streetAddress + breadcrumb on every row). The iOS API contract has been stable through every tested run, but this is your fallback if Pap.fr ever rotates it.

The parser uses **JSON natively from the iOS API**. No DOM scraping, no fragile selectors. When HTML enrichment is needed, it parses schema.org `Product` JSON-LD — also structured, not DOM.

---

### Input

| Field | Type | Required | Notes |
|---|---|---|---|
| `startUrls` | `string[]` | no | Any mix of listing / detail URLs. Auto-classified. Filter slugs (`studio`, `a-partir-de-N-pieces`, etc.) are parsed into iOS API params. |
| `locations` | `string[]` | when `startUrls` empty | City names (`Paris`, `Lyon`, `Annecy`, …) or raw Pap.fr geo IDs (`g439`). Cities not in our cache resolve dynamically via `/app/gis`. |
| `distributionType` | enum | no | `Buy` (vente) / `Rent` (location). Default `Buy`. |
| `estateTypes` | enum[] | no | `Apartment` / `House`. Leave empty for both. |
| `priceMin` / `priceMax` | int | no | € bounds → `prix[min]`/`prix[max]`. |
| `roomsMin` | int | no | Minimum number of rooms (pièces) → `nb_pieces[min]`. |
| `splitByPrice` | boolean | no | When `true`, splits the search across 5 log-distributed price bands to multiply yield past the 300 cap. Default `false`. |
| `priceBands` | int 1–10 | no | Advanced override — exact band count. Implies `splitByPrice`. |
| `forceHtmlDetail` | boolean | no | Escape hatch — fetch every row via HTML JSON-LD instead of iOS API. Slower (~70 KB vs ~3 KB per row) but richer amenities/breadcrumb. Default `false`. |
| `maxItems` | int | no | Hard cap on rows. Default `1000`. Free-tier users capped at 100. |
| `maxConcurrency` | int | no | Default `10`. |
| `proxy` | object | no | Default Apify Residential FR. The iOS API works fine from any IP region we tested. |

#### Example input

```json
{
    "locations": ["Paris", "Lyon"],
    "distributionType": "Buy",
    "estateTypes": ["Apartment"],
    "priceMax": 500000,
    "splitByPrice": true,
    "maxItems": 500
}
````

That builds 10 iOS search tasks (2 locations × 5 price bands), each pulling up to 300 cards, with detail-fetches fanned out at `maxConcurrency`. Expected yield ~500 unique private-seller properties in one billable run.

***

### Output schema

One row per property. Example with phones revealed:

```jsonc
{
  "propertyId":     "458601075",
  "sku":            "vente-458601075",
  "transactionType":"vente",
  "title":          "Paris 9e",
  "headline":       "",
  "description":    "Rue de Maubeuge, dans un très bel immeuble pierre de taille…",
  "propertyType":   "Appartement",
  "livingArea":     7,
  "rooms":          1,
  "bedrooms":       1,
  "price":          66000,
  "priceFormatted": "66.000 €",
  "currency":       "EUR",
  "priceValid":     false,
  "city":           "Paris 9e",
  "postalCode":     "75009",
  "country":        "FR",
  "latitude":       48.878899,
  "longitude":      2.346797,
  "dpeRating":      "G",
  "dpeDescription": "Logement avec une consommation annuelle d'énergie supérieure ou égale à 421 kWh/m²/an.",
  "gesRating":      "D",
  "gesDescription": "Logement avec une émission de gaz à effet de serre comprise entre 31 à 50 kgéqCO2/m²/an.",
  "sellerName":     "Particulier",
  "sellerType":     "Person",
  "phones":         ["06 09 40 56 96", "06 12 77 36 34"],
  "hasEmail":       true,
  "refusesDemarchage": true,
  "appelVideo":     ["whatsapp"],
  "virtualTourUrl": null,
  "sellerPersonalSite": null,
  "reference":      "F86/1075",
  "listedDate":     "21 mai 2026",
  "transports": [
    { "label": "Poissonnière", "lines": ["metro-7"] },
    { "label": "Cadet",        "lines": ["metro-7"] },
    { "label": "Anvers",       "lines": ["metro-2"] }
  ],
  "imageUrls":      ["https://cdn.pap.fr/photos/pap/f1/1e/…/f-p1.jpg", "…"],
  "photosCount":    5,
  "additionalProperties": [],
  "breadcrumb":     [],
  "url":            "https://www.pap.fr/annonces/-r458601075",
  "rawSource":      { "id": 458601075, "produit": "vente", "telephones": [...], "transports": [...], "...": "..." },
  "source":         "ios-api",
  "scrapedAt":      "2026-05-25T15:18:42.331Z"
}
```

When the input URL contains amenity tokens (handled via HTML enrichment), the row gains `address`, `additionalProperties[]`, full `breadcrumb[]`, and `source: "ios-api+html"`.

***

### FAQ

**Q: Do you actually get phone numbers?**
Yes — when the seller has revealed their phone, the iOS API returns it natively in `telephones[]`. We pass that through as `phones[]`. About 20–40 % of Pap.fr listings have phones visible (it's seller-controlled — sellers who haven't toggled "show phone" simply don't appear with one, and there's no scrape path that bypasses the seller's choice).

**Q: How does this compare to the SeLoger scraper?**

- SeLoger covers **agency-listed** properties; Pap.fr covers **private-seller** properties. Different inventory, often complementary.
- SeLoger ships 75 fields per row (uses an undocumented React-app server-state blob). Pap.fr now ships 30+ structured fields including phones, station labels, DPE descriptions — directly from the iOS app's JSON API.
- Pap.fr is faster and cheaper to scrape (no Cloudflare challenge on the iOS endpoint, JSON not HTML).

**Q: Why does the actor stop after ~300 results per location?**
The iOS API hard-caps each search at 300 results. To break past, set `splitByPrice: true` (5 narrower searches × 300 = ~1,500 properties per location). For per-location yield beyond that, combine multiple filters (`priceMin`, `priceMax`, `roomsMin`, `estateTypes`).

**Q: I used `avec-piscine` in my URL but the count dropped a lot — why?**
The iOS API silently ignores `criteres[]` filters (amenities). When we detect amenity tokens in the URL, the handler fetches the HTML detail page for each candidate, parses the JSON-LD amenity flags, and drops rows that don't match. So your final dataset is **strictly filtered** — accuracy is preserved at the cost of one extra HTTP per candidate.

**Q: Will this break if Pap.fr changes their iOS app contract?**
The five static headers we use are stable across Pap iOS app versions (currently 4.17.6). If Pap.fr ever signs requests or rotates the contract, set `forceHtmlDetail: true` in input — the actor falls back to HTML JSON-LD per row (slower, but resilient to API churn). Each individual iOS detail failure also auto-falls-back to HTML, so single-property flakes are handled silently.

***

### ⚠️ Disclaimer

This scraper is provided for **lawful, ethical use**. Pap.fr is operated by SE Loger / Aviv Group and the listings are owned by the individual sellers who posted them. Before scraping:

1. Review Pap.fr's Terms of Service (`https://www.pap.fr/info/mentions-legales`) and your local laws (GDPR in EU, CCPA in California, etc.)
2. Use the data responsibly — don't republish raw listings, don't mass-contact sellers, don't compete unfairly
3. Rate-limit your runs and respect Pap.fr's infrastructure
4. The seller's personal data (name, address, phone if exposed by the seller) is protected under GDPR — process it lawfully
5. We are not affiliated with Pap.fr, SE Loger, or Aviv Group. We don't warrant that scraping Pap.fr is permitted under their Terms. By using this actor you accept full responsibility for compliance.

For paid commercial use of Pap.fr data, consider whether a direct data partnership with Pap.fr / SE Loger / Aviv is a better fit than scraping.

***

### SEO Keywords

pap.fr scraper, pap fr scraper, particulier a particulier scraper, french real estate scraper, paris properties scraper, lyon properties scraper, marseille properties scraper, french private seller scraper, immobilier scraper, vente appartement scraper, vente maison scraper, location appartement scraper, french property listings, paris immobilier scraper, pap.fr api, pap.fr ios api, scrape pap.fr, pap.fr extract listings, pap.fr property data, pap.fr phone numbers scraper, telephones pap.fr scraper, french real estate api alternative, private seller real estate france, pap.fr json scraper, pap.fr listings json, pap.fr breadcrumb hierarchy, pap.fr geo id scraper, pap.fr filter scraper, pap.fr price band scraper, ile-de-france real estate scraper, hauts-de-seine real estate scraper, vente immobiliere scraper, pap scraper apify

# Actor input Schema

## `startUrls` (type: `array`):

Accepted URL shapes:
• Listing — `/annonce/{transaction}-{rest}-g{geoId}` (e.g. `/annonce/vente-immobiliere-paris-75-g439`). Variants: `vente-appartements-…`, `vente-maisons-…`, `locations-appartement-…`. Pagination via `-pN` suffix.
• Property detail — `/annonces/{slug}-r{numericId}` (e.g. `/annonces/appartement-paris-15e-75015-r453301302`).

## `locations` (type: `array`):

Either a city name (e.g. `Paris`, `Lyon`, `Marseille`, `Nice`, `Bordeaux`, `Toulouse` — ~30 known) or a raw Pap.fr geo ID like `g439` (Paris) or `439`. Unknown cities are skipped with a warning.

## `distributionType` (type: `string`):

Buy = vente, Rent = location.

## `estateTypes` (type: `array`):

Filter by property type. Leave empty for both Apartment + House (Pap.fr default).

## `priceMin` (type: `integer`):

Lower price bound in euros.

## `priceMax` (type: `integer`):

Upper price bound in euros.

## `roomsMin` (type: `integer`):

Minimum number of rooms (pièces). 1 = studio, 2 = 1-bed, 3 = 2-bed, etc.

## `splitByPrice` (type: `boolean`):

The iOS API hard-caps each search at 300 results. When ON, the actor issues 5 narrower searches at log-distributed price cut-points instead of one — multiplying total yield ~5×. Default bands tuned for the French private-seller market: \[0–150K], \[150K–300K], \[300K–500K], \[500K–800K], \[800K+]. If you set both `priceMin` and `priceMax`, the range is split into 5 equal bands instead.

(Power users: `priceBands: N` can be passed via API to override the 5-band default. Not exposed in the UI to keep things simple.)

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

Hard cap on property rows emitted.

## `maxConcurrency` (type: `integer`):

Maximum number of property pages processed in parallel.

## `minConcurrency` (type: `integer`):

Minimum number of pages processed in parallel.

## `maxRequestRetries` (type: `integer`):

Per-URL retry budget. iOS API calls retry up to 4 times with proxy rotation; HTML enrichment retries the same.

## `forceHtmlDetail` (type: `boolean`):

Default: false (iOS API for every detail). When true, fetches every detail row via Pap.fr's HTML page (schema.org Product JSON-LD) instead of the iOS API. Slower (~70 KB vs ~3 KB per row) but provides street-level address, the `additionalProperties[]` amenity list, and the 7-level breadcrumb on every row. Useful if Pap.fr ever changes the iOS API contract.

## `proxy` (type: `object`):

Apify Residential FR is the default (Pap.fr is France-focused; direct works too but residential FR is most reliable). UK/DE/US residential proxies also work — Pap.fr's anti-bot is mild compared to SeLoger.

## Actor input object example

```json
{
  "startUrls": [
    "https://www.pap.fr/annonce/vente-immobiliere-paris-75-g439",
    "https://www.pap.fr/annonces/appartement-paris-15e-75015-r453301302"
  ],
  "locations": [
    "Paris"
  ],
  "distributionType": "Buy",
  "splitByPrice": false,
  "maxItems": 1000,
  "maxConcurrency": 10,
  "minConcurrency": 1,
  "maxRequestRetries": 5,
  "forceHtmlDetail": false,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "FR"
  }
}
```

# 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 = {
    "startUrls": [
        "https://www.pap.fr/annonce/vente-immobiliere-paris-75-g439",
        "https://www.pap.fr/annonces/appartement-paris-15e-75015-r453301302"
    ],
    "locations": [
        "Paris"
    ],
    "proxy": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "FR"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("memo23/pap-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 = {
    "startUrls": [
        "https://www.pap.fr/annonce/vente-immobiliere-paris-75-g439",
        "https://www.pap.fr/annonces/appartement-paris-15e-75015-r453301302",
    ],
    "locations": ["Paris"],
    "proxy": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "FR",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("memo23/pap-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "startUrls": [
    "https://www.pap.fr/annonce/vente-immobiliere-paris-75-g439",
    "https://www.pap.fr/annonces/appartement-paris-15e-75015-r453301302"
  ],
  "locations": [
    "Paris"
  ],
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "FR"
  }
}' |
apify call memo23/pap-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=memo23/pap-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Pap.fr 🇫🇷 [$1.5💰] French Private-Seller RE Scraper",
        "description": "🔥 $1.5/1K · Pap.fr scraper for private French sellers — 30+ fields per row from rich JSON-LD: price · m² · rooms · DPE · lat-lng · seller · breadcrumb hierarchy (région/département/ville). Filter mode + URL mode + price-band split (lift the ~370-result cap). Apify Residential FR. No browser",
        "version": "0.0",
        "x-build-id": "mGFPam21Z5A5HHbBY"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/memo23~pap-scraper/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-memo23-pap-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/memo23~pap-scraper/runs": {
            "post": {
                "operationId": "runs-sync-memo23-pap-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/memo23~pap-scraper/run-sync": {
            "post": {
                "operationId": "run-sync-memo23-pap-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": {
                    "startUrls": {
                        "title": "Pap.fr URLs",
                        "type": "array",
                        "description": "Accepted URL shapes:\n• Listing — `/annonce/{transaction}-{rest}-g{geoId}` (e.g. `/annonce/vente-immobiliere-paris-75-g439`). Variants: `vente-appartements-…`, `vente-maisons-…`, `locations-appartement-…`. Pagination via `-pN` suffix.\n• Property detail — `/annonces/{slug}-r{numericId}` (e.g. `/annonces/appartement-paris-15e-75015-r453301302`).",
                        "items": {
                            "type": "string"
                        }
                    },
                    "locations": {
                        "title": "Locations (city names or g{N} IDs)",
                        "uniqueItems": true,
                        "type": "array",
                        "description": "Either a city name (e.g. `Paris`, `Lyon`, `Marseille`, `Nice`, `Bordeaux`, `Toulouse` — ~30 known) or a raw Pap.fr geo ID like `g439` (Paris) or `439`. Unknown cities are skipped with a warning.",
                        "items": {
                            "type": "string"
                        }
                    },
                    "distributionType": {
                        "title": "Transaction type",
                        "enum": [
                            "Buy",
                            "Rent"
                        ],
                        "type": "string",
                        "description": "Buy = vente, Rent = location.",
                        "default": "Buy"
                    },
                    "estateTypes": {
                        "title": "Property types",
                        "uniqueItems": true,
                        "type": "array",
                        "description": "Filter by property type. Leave empty for both Apartment + House (Pap.fr default).",
                        "items": {
                            "type": "string",
                            "enum": [
                                "Apartment",
                                "House"
                            ]
                        }
                    },
                    "priceMin": {
                        "title": "Min price (€)",
                        "minimum": 0,
                        "type": "integer",
                        "description": "Lower price bound in euros."
                    },
                    "priceMax": {
                        "title": "Max price (€)",
                        "minimum": 0,
                        "type": "integer",
                        "description": "Upper price bound in euros."
                    },
                    "roomsMin": {
                        "title": "Min rooms (pièces)",
                        "minimum": 1,
                        "type": "integer",
                        "description": "Minimum number of rooms (pièces). 1 = studio, 2 = 1-bed, 3 = 2-bed, etc."
                    },
                    "splitByPrice": {
                        "title": "🚀 Split by price bands (break Pap.fr's 300-result cap)",
                        "type": "boolean",
                        "description": "The iOS API hard-caps each search at 300 results. When ON, the actor issues 5 narrower searches at log-distributed price cut-points instead of one — multiplying total yield ~5×. Default bands tuned for the French private-seller market: [0–150K], [150K–300K], [300K–500K], [500K–800K], [800K+]. If you set both `priceMin` and `priceMax`, the range is split into 5 equal bands instead.\n\n(Power users: `priceBands: N` can be passed via API to override the 5-band default. Not exposed in the UI to keep things simple.)",
                        "default": false
                    },
                    "maxItems": {
                        "title": "Maximum items to scrape",
                        "minimum": 1,
                        "type": "integer",
                        "description": "Hard cap on property rows emitted.",
                        "default": 1000
                    },
                    "maxConcurrency": {
                        "title": "Max concurrency",
                        "minimum": 1,
                        "type": "integer",
                        "description": "Maximum number of property pages processed in parallel.",
                        "default": 10
                    },
                    "minConcurrency": {
                        "title": "Min concurrency",
                        "minimum": 1,
                        "type": "integer",
                        "description": "Minimum number of pages processed in parallel.",
                        "default": 1
                    },
                    "maxRequestRetries": {
                        "title": "Max request retries",
                        "minimum": 0,
                        "type": "integer",
                        "description": "Per-URL retry budget. iOS API calls retry up to 4 times with proxy rotation; HTML enrichment retries the same.",
                        "default": 5
                    },
                    "forceHtmlDetail": {
                        "title": "Force HTML for detail rows (escape hatch)",
                        "type": "boolean",
                        "description": "Default: false (iOS API for every detail). When true, fetches every detail row via Pap.fr's HTML page (schema.org Product JSON-LD) instead of the iOS API. Slower (~70 KB vs ~3 KB per row) but provides street-level address, the `additionalProperties[]` amenity list, and the 7-level breadcrumb on every row. Useful if Pap.fr ever changes the iOS API contract.",
                        "default": false
                    },
                    "proxy": {
                        "title": "Proxy configuration",
                        "type": "object",
                        "description": "Apify Residential FR is the default (Pap.fr is France-focused; direct works too but residential FR is most reliable). UK/DE/US residential proxies also work — Pap.fr's anti-bot is mild compared to SeLoger.",
                        "default": {
                            "useApifyProxy": true,
                            "apifyProxyGroups": [
                                "RESIDENTIAL"
                            ],
                            "apifyProxyCountry": "FR"
                        }
                    }
                }
            },
            "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
                                    }
                                }
                            },
                            "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
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
