# Toimitilat Scraper - Finnish Commercial Property Data (`studio-amba/toimitilat-fi-scraper`) Actor

Scrape commercial property listings from Toimitilat.fi, Finland's largest commercial-space portal with ~37,000 active listings. Filter by location, deal type and segment (offices, retail, warehouses, plots). Extract prices with units, surface, address, coordinates and full descriptions.

- **URL**: https://apify.com/studio-amba/toimitilat-fi-scraper.md
- **Developed by:** [Studio Amba](https://apify.com/studio-amba) (community)
- **Categories:** Real estate
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — 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

## Toimitilat Scraper

Extract commercial property listings from [Toimitilat.fi](https://www.toimitilat.fi), Finland's largest commercial-space portal with around 37,000 active listings: offices (toimistotilat), retail spaces (liiketilat), warehouses (varastotilat), production facilities (tuotantotilat), plots (tontit) and more.

### Why use it

Toimitilat.fi is where Finnish commercial real estate gets listed. Owners, brokers and Alma Media's network publish rental and sale listings there across every municipality in Finland. This actor turns those listings into structured data you can load into a spreadsheet, database or BI tool.

Typical uses:

- **Market research**: track supply of office or warehouse space in a city over time
- **Pricing analysis**: compare quoted rents per m² across districts
- **Lead generation**: find newly listed properties in your target segment
- **Portfolio monitoring**: watch competing space near your own properties
- **Site selection**: pull every available retail space in a region with size and location filters

No login, no cookies, no browser needed.

### Input options

| Field | Type | Description |
|-------|------|-------------|
| `searchQuery` | string | Finnish region, municipality or district, e.g. `Helsinki`, `Espoo`, `Tampere`, `Turku`, `Pääkaupunkiseutu`, `Lauttasaari`. Leave empty to scrape all of Finland. If the name doesn't match a location known to the portal, the run fails and lists valid examples instead of silently returning wrong data. |
| `listingType` | select | `any` (default), `rent` (vuokrataan), `sale` (myydään), `investment` (sijoitus) |
| `segment` | select | `all` (default), `office`, `office-hotel`, `retail`, `showroom`, `production`, `warehouse`, `warehouse-hotel`, `workspace`, `garage-hall`, `care`, `plot`, `other` |
| `minSize` / `maxSize` | integer | Floor area bounds in m² |
| `maxResults` | integer | Maximum listings to scrape (default 100, hard cap 40,000) |
| `proxyConfiguration` | proxy | Finnish residential proxies are prefilled and recommended |

### Output fields

| Field | Example | Notes |
|-------|---------|-------|
| `id` | `"10858452"` | Toimitilat.fi object number (kohdenumero) |
| `listingTitle` | `"Vuokrataan toimistotilat - Helsinki"` | |
| `dealType` | `"rent"` | `rent`, `sale`, `rent-or-sale`, `investment` |
| `segment` | `"office"` | Normalized from the Finnish space type |
| `propertyTypes` | `["Toimistotilaa"]` | All space types as published, in Finnish |
| `price` | `14` | Numeric price. Read together with `priceUnit`. Null when the listing quotes no price. |
| `priceMax` | `735` | Upper bound when a range is quoted |
| `currency` | `"EUR"` | Present whenever `price` is present, never converted |
| `priceUnit` | `"€/m²/kk"` | `€/m²/kk` (per m² per month), `€/kk` (per month), `€ (sale price)` or `€` |
| `priceRaw` | `"14 € / m2 / kk"` | The price text exactly as published |
| `surface` | `215` | Floor area in m² (lower bound of a range) |
| `surfaceMax` | `5500` | Upper bound of the floor-area range, if given |
| `address` | `"Itälahdenkatu 15-17, Lauttasaari, 00210 Helsinki"` | |
| `street`, `district`, `postalCode`, `city` | | Parsed address components |
| `floor` | `"2."` | Kerros |
| `availableFrom` | `"Heti vapaa"` | Vapautuu |
| `energyClass` | `"E"` | Energy-efficiency class when stated |
| `propertyName` | `"Helsingin Itälahdenkatu 15-17"` | Building name (kiinteistö) |
| `latitude`, `longitude` | `60.15082`, `24.88157` | WGS84 coordinates |
| `listingDescription` | | Full listing text in Finnish |
| `imageUrl`, `imageUrls` | | Listing photos |
| `url` | `https://www.toimitilat.fi/toimitila/10858452-...` | Canonical listing URL |
| `source`, `scrapedAt` | `"toimitilat.fi"`, ISO 8601 | |

#### A note on prices

Finnish commercial listings often publish no price at all. The market convention is "neuvoteltavissa" (negotiable) or "kysy" (ask), especially for rentals. In a typical Helsinki office search, roughly a quarter to a third of listings quote a concrete rent; sale listings quote prices more often. When a listing has no price, `price`, `currency` and `priceUnit` are null and `priceRaw` preserves whatever the listing said. This reflects the source, not a scraping gap. Rents are quoted either per square meter per month (`€/m²/kk`) or as a flat monthly rent (`€/kk`); the `priceUnit` field tells you which one you are looking at.

### Example output

```json
{
    "id": "10858452",
    "listingTitle": "Vuokrataan toimistotilat - Helsinki",
    "dealType": "rent",
    "segment": "office",
    "propertyTypes": ["Toimistotilaa"],
    "price": 14,
    "priceMax": null,
    "currency": "EUR",
    "priceUnit": "€/m²/kk",
    "priceRaw": "14 € / m2 / kk",
    "surface": 215,
    "surfaceMax": null,
    "address": "Itälahdenkatu 15-17, Lauttasaari, 00210 Helsinki",
    "street": "Itälahdenkatu 15-17",
    "district": "Lauttasaari",
    "postalCode": "00210",
    "city": "Helsinki",
    "floor": "2.",
    "availableFrom": "Heti vapaa",
    "energyClass": "E",
    "latitude": 60.15082,
    "longitude": 24.88157,
    "url": "https://www.toimitilat.fi/toimitila/10858452-toimistotilat-helsinki-italahdenkatu-15-17",
    "source": "toimitilat.fi",
    "scrapedAt": "2026-08-18T09:00:00.000Z"
}
```

### How to scrape Toimitilat data

1. Open the actor and set a location in `searchQuery`, for example `Helsinki`, `Tampere` or `Oulu`. Leave it empty to cover all of Finland.
2. Pick a `listingType` (`rent` or `sale`) and a `segment` (`office`, `retail`, `warehouse`, ...) if you want a subset.
3. Set `maxResults` to the number of listings you need.
4. Run the actor. Results land in the default dataset, one record per listing.
5. Export as JSON, CSV or Excel from the dataset tab, or read it through the Apify API.

Example searches that work well:

- `searchQuery: "Helsinki"`, `segment: "office"`, `listingType: "rent"` (vapaat toimistotilat Helsinki)
- `searchQuery: "Tampere"`, `listingType: "sale"` (myytävät toimitilat Tampere)
- `searchQuery: "Vantaa"`, `segment: "warehouse"` (varastotilat Vantaa)
- `segment: "plot"` with no location (tontit, all of Finland)

### Cost estimate

Each listing costs one detail-page request, plus one search request per 200 listings. On the default settings, expect roughly $0.002 per result plus a small run-start fee, so about 500 listings per $1. A full national scrape (~37,000 listings) is a long run; set `maxResults` accordingly and prefer filtered runs when you only need one city or segment.

Note that a run's usage cost only settles after the run reports SUCCEEDED. Reading the dataset mid-run shows a partial cost that can be far below the final figure, so don't estimate spend from an in-progress run.

### Limitations

- Listings grouped under one address on the portal are expanded automatically; this adds two extra requests per address group, which is included in the run cost.
- Price coverage follows the source: many rentals publish no rent (see the price note above).
- The portal is Finnish-language; descriptions and some raw fields come back in Finnish.
- Historical data is not available, only currently active listings.

# Actor input Schema

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

Finnish region, municipality or district name, e.g. 'Helsinki', 'Espoo', 'Tampere', 'Pääkaupunkiseutu', 'Lauttasaari'. Must match a location known to Toimitilat.fi (the run fails with suggestions if it doesn't). Leave empty to scrape all of Finland.

## `listingType` (type: `string`):

Filter by deal type: rent (vuokrataan), sale (myydään) or investment (sijoitus). Default: all deal types.

## `segment` (type: `string`):

Filter by space type. Default: all segments.

## `minSize` (type: `integer`):

Minimum floor area in square meters.

## `maxSize` (type: `integer`):

Maximum floor area in square meters.

## `maxResults` (type: `integer`):

Maximum number of listings to scrape. Each listing costs one detail-page request. The portal carries ~37,000 active listings.

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

Proxy settings. Finnish residential proxies recommended: the site itself has no anti-bot wall (see docs/site-recon/toimitilat-fi.md), but Nordic sites routinely reject non-EU datacenter IPs and this account has no FI datacenter pool.

## Actor input object example

```json
{
  "searchQuery": "Helsinki",
  "listingType": "any",
  "segment": "all",
  "maxResults": 20,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "FI"
  }
}
```

# 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 = {
    "searchQuery": "Helsinki",
    "maxResults": 20,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "FI"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("studio-amba/toimitilat-fi-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 = {
    "searchQuery": "Helsinki",
    "maxResults": 20,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "FI",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("studio-amba/toimitilat-fi-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 '{
  "searchQuery": "Helsinki",
  "maxResults": 20,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "FI"
  }
}' |
apify call studio-amba/toimitilat-fi-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,studio-amba/toimitilat-fi-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/AqEra7S3l1CkE1UxK/builds/HgLdQDu6S5aXemAq6/openapi.json
