# Hall Portugal Scraper - Real Estate Listings (`studio-amba/hall-pt-scraper`) Actor

Scrape property listings from Hall Rede Imobiliária (hall.pt), Portugal. Price, typology, useful, gross and land area, photos, energy certificate, coordinates, district, municipality, parish and agent details. Sale, rent and business transfer. No login.

- **URL**: https://apify.com/studio-amba/hall-pt-scraper.md
- **Developed by:** [Studio Amba](https://apify.com/studio-amba) (community)
- **Categories:** Real estate
- **Features:** Limited permissions
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $1.20 / 1,000 result scrapeds

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

## Hall Portugal Scraper

Scrape every property listing from Hall Rede Imobiliária (hall.pt), a Portuguese real estate agency network with offices in Setúbal, Barreiro, Amora, Beja, Évora and Montemor-o-Novo.

### Why use this actor?

Hall publishes its own stock on hall.pt, and part of it never reaches the big portals. If you build a real estate CRM, track agency stock in the Setúbal peninsula and the Alentejo, or compare asking prices per parish, this actor gives you every listing as structured rows: price, typology, the three area types, photos, energy certificate, coordinates and the agent who holds the mandate.

### How to scrape Hall data

1. Leave every filter empty to get the full hall.pt catalogue (a few hundred listings, sale, rent and business transfer).
2. Or narrow it down with **Transaction type** (sale, rent, transfer), **Property type**, **District**, **Municipality** or **Parish**. Accents are optional: `Setubal` and `Setúbal` both work.
3. Optionally add **Typology / bedrooms**, **Minimum price** and **Maximum price**.
4. Set **Max items** and run. The default of 1000 covers the whole catalogue.
5. Download the dataset as JSON, CSV or Excel, or read it through the Apify API.

The actor uses the site's own search, so the filters behave like the filters on hall.pt. Every result is then checked against your filters again before it is saved. If you ask for a district or municipality where hall.pt has no listings, the run stops and lists the places that do have listings.

No login, cookies or browser are needed.

### Input

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `transactionTypes` | Array | No | `sale` (Compra), `rent` (Arrendamento), `transfer` (Trespasse). Empty = all. |
| `propertyTypes` | Array | No | `apartment`, `house`, `house-floor`, `farm`, `garage`, `building`, `office`, `warehouse`, `shop`, `business`, `land`, `multipurpose-space`, `laundry`, `restaurant`. Empty = all. |
| `district` | String | No | Distrito, e.g. `Setúbal`, `Beja`, `Évora`, `Lisboa` |
| `municipality` | String | No | Concelho, e.g. `Palmela`, `Barreiro`, `Montijo` |
| `parish` | String | No | Freguesia, e.g. `Pinhal Novo` |
| `bedrooms` | Array of integers | No | e.g. `[2, 3]` for T2 and T3. 9 means T9 or more. |
| `minPrice` / `maxPrice` | Integer | No | Price range in EUR (monthly rent for rentals). Listings without a published price are kept. |
| `sortBy` | String | No | `newest` (default), `price_asc`, `price_desc` |
| `startUrls` | Array | No | hall.pt search URLs or listing URLs. Max 50. |
| `maxItems` | Integer | No | Maximum listings to save. Default 1000, hard cap 10,000. |
| `proxyConfiguration` | Object | No | Apify automatic proxy is enough. |

Every filter is off by default. An empty filter never narrows the results.

### Output

Each listing is one row. Fields the site does not show for a listing are `null`, never invented.

| Field | Type | Example |
|-------|------|---------|
| `listingId` | Integer | `1291840` |
| `reference` | String | `"012.012.206"` |
| `title` | String | `"Arrendamento Apartamento T3 - Beja, Cidade"` |
| `url` | String | Listing URL on hall.pt |
| `transactionType` | String | `"sale"`, `"rent"` or `"transfer"` |
| `transactionTypeLocal` | String | `"Arrendamento"` |
| `propertyType` | String | `"apartment"` |
| `propertyTypeLocal` | String | `"Apartamento"` |
| `typology` | String | `"T3"` |
| `bedrooms` / `bathrooms` | Number | `3` / `2` |
| `price` | Number or null | `900` |
| `previousPrice` | Number or null | Price before the latest reduction |
| `priceOnRequest` | Boolean | `true` when no price is published |
| `currency` | String or null | `"EUR"`, always next to `price` |
| `areaM2` | Number | Useful area, else gross, else land |
| `livingAreaM2` | Number | Área útil |
| `grossAreaM2` | Number | Área bruta |
| `landAreaM2` | Number | Área do terreno |
| `footprintAreaM2` | Number | Área de implantação |
| `condition` | String | `"Renovado"` |
| `constructionYear` | Integer | `1975` |
| `energyLabel` | String | `"C"` |
| `usageLicense` | String | Licença de utilização, when shown |
| `condominiumFeeMonthly` | Number | Condomínio mensal, when shown |
| `description` | String | Full description with line breaks |
| `district` / `municipality` / `parish` | String | `"Setúbal"` / `"Palmela"` / `"Pinhal Novo"` |
| `locationLabel` | String | Location line under the title (often includes the zone) |
| `locationFull` | String | `"Pinhal Novo, Palmela, Setúbal"` |
| `latitude` / `longitude` | Number or null | `38.63512`, `-8.91523` |
| `imageUrl` / `imageUrls` / `imageCount` | String / Array / Integer | Full-size photos |
| `hasGeneratedImages` | Boolean | The site marks a photo as a generated image |
| `virtualTourUrl` / `photos360Urls` | String / Array | When present |
| `divisions` | Array | Rooms with area and finishes (Divisões) |
| `characteristics` | Object | Every label and value from the Características block |
| `agentName` / `agencyName` | String | Agent and office |
| `agencyLicenseAmi` | String | AMI licence number |
| `agencyEmail` / `agencyPhone` / `agencyAddress` | String | Office contact |
| `scrapedAt` | String | ISO timestamp |

### Example output

```json
{
    "listingId": 1294935,
    "reference": "007.754.012",
    "title": "T2 Pinhal Novo",
    "url": "https://www.hall.pt/pt-PT/Imovel/Compra/Apartamento/Setubal/Palmela/Pinhal-Novo/1294935",
    "transactionType": "sale",
    "transactionTypeLocal": "Compra",
    "propertyType": "apartment",
    "propertyTypeLocal": "Apartamento",
    "typology": "T2",
    "bedrooms": 2,
    "bathrooms": 1,
    "price": null,
    "previousPrice": null,
    "priceOnRequest": true,
    "currency": null,
    "areaM2": 85,
    "livingAreaM2": 85,
    "grossAreaM2": 88,
    "landAreaM2": null,
    "footprintAreaM2": null,
    "constructionYear": 1975,
    "energyLabel": "D",
    "country": "PT",
    "district": "Setúbal",
    "municipality": "Palmela",
    "parish": "Pinhal Novo",
    "locationLabel": "Palmela, Pinhal Novo",
    "locationFull": "Pinhal Novo, Palmela, Setúbal",
    "latitude": null,
    "longitude": null,
    "imageUrl": "https://imgs.centralimo.pt/28594/properties/66374052-0b88-47a0-8d7e-cf4d2dfc081a.jpg",
    "imageCount": 1,
    "agentName": "Odete Marques",
    "agencyName": "Horizonte Inesgotável",
    "agencyLicenseAmi": "19280",
    "agencyEmail": "odete.marques@hall.pt",
    "agencyPhone": "+351 963 374 001",
    "scrapedAt": "2026-11-08T03:35:31.971Z"
}
```

### Cost estimate

The actor charges **$0.002 per saved listing** plus **$0.005 per run start** (lower per-listing prices on higher Apify plans). The full hall.pt catalogue of about 320 listings costs about $0.65 and finishes in about 4 minutes. A run of 20 listings costs about $0.045.

The run's usage cost only settles once the run reports SUCCEEDED. A dataset read during a run, or from a run that later failed, shows a cost far below the real total.

### What hall.pt does not publish

- **Listing date and last-update date.** hall.pt does not show them on any page, so the dataset has no date fields. Use `scrapedAt` and compare runs to see new and removed listings.
- **Property street address.** Only the office address is shown.
- **Coordinates** for listings where the agent hides the map (roughly 4 in 10). `latitude` and `longitude` are `null` there.
- **Price** for listings marked "Preço sob consulta" or left blank (roughly 1 in 8). `price` is `null` and `priceOnRequest` is `true`.

### Typology

The typology comes from the listing title (`Apartamento T3`). When the title has none and the listing is a home (apartment, house, house floor or farm), the actor uses the Portuguese convention T + number of bedrooms. Land, shops, garages and other non-residential listings have no typology.

### Limitations

- hall.pt shows 18 listings per results page and one detail page per listing, so a run makes one request per listing. The whole catalogue still runs in minutes.
- Location filters only accept places where hall.pt currently has listings. The run fails with the list of available places otherwise.
- Data is taken from the public website and can change without notice.

### Related Scrapers

- [RE/MAX Portugal Scraper](https://apify.com/studio-amba/remax-pt-scraper) - listings from remax.pt
- [Imovirtual Scraper](https://apify.com/studio-amba/imovirtual-scraper) - Portugal's large property portal
- [Idealista Scraper](https://apify.com/studio-amba/idealista-scraper) - Spain, Portugal and Italy
- [Supercasa Scraper](https://apify.com/studio-amba/supercasa-scraper) - Portuguese property portal
- [Casa Sapo Scraper](https://apify.com/studio-amba/casa-sapo-pt-scraper) - Portuguese property portal

### Maintained by Studio Amba

We run 700+ scrapers for European websites, and the priority is keeping
every one of them working. Automated runs test this actor against the live
site and verify the output is complete. When the website changes, the
scraper usually gets repaired the same day, without you having to report
anything. Questions and issues go straight to the people who built it, and
most get an answer the same day.

If this actor works for you, a review on its Store page helps other people
find it.

# Actor input Schema

## `transactionTypes` (type: `array`):

Sale (Compra), rent (Arrendamento) or business transfer (Trespasse). Leave empty for all three.

## `propertyTypes` (type: `array`):

Only these property types. Leave empty for all types.

## `district` (type: `string`):

Portuguese district, e.g. Setúbal, Beja, Évora, Lisboa. Accents optional. The run fails with the list of available districts if hall.pt has no listings there.

## `municipality` (type: `string`):

Municipality, e.g. Palmela, Barreiro, Montijo, Montemor-o-Novo. Accents optional.

## `parish` (type: `string`):

Parish, e.g. Pinhal Novo. Accents optional. Setting a municipality too makes the lookup faster.

## `bedrooms` (type: `array`):

Number of bedrooms as a JSON list, e.g. \[2, 3] (T0 = 0, T1 = 1 ... 9 means T9 or more). Leave empty for any.

## `minPrice` (type: `integer`):

Minimum asking price or monthly rent in EUR. Listings without a published price are kept.

## `maxPrice` (type: `integer`):

Maximum asking price or monthly rent in EUR. Listings without a published price are kept.

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

Order in which listings are collected. Matters when Max items is lower than the number of matching listings.

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

Optional hall.pt search result URLs (https://www.hall.pt/pt-PT/Imoveis/...) or listing URLs (https://www.hall.pt/pt-PT/Imovel/...). When set, the filters above are only used to check the results. Max 50.

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

Maximum number of listings to save. hall.pt has a few hundred listings in total, so 1000 covers the full catalogue.

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

hall.pt has no anti-bot wall (see recon), so Apify's automatic proxy is enough.

## Actor input object example

```json
{
  "transactionTypes": [
    "sale"
  ],
  "sortBy": "newest",
  "maxItems": 20,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `results` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

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

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "transactionTypes": [
        "sale"
    ],
    "maxItems": 20,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("studio-amba/hall-pt-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 = {
    "transactionTypes": ["sale"],
    "maxItems": 20,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("studio-amba/hall-pt-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 '{
  "transactionTypes": [
    "sale"
  ],
  "maxItems": 20,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call studio-amba/hall-pt-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,studio-amba/hall-pt-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/5klhNVUCCzd4JZpvM/builds/U2ghirDehUCzUOVE6/openapi.json
