# FINN.no Real Estate Scraper & New-Listing Monitor - Norway (`neverempty/finn-real-estate`) Actor

Turn monitoring on and every later run returns only the listings it has not returned before, so you are never charged for the same property twice. Every FINN.no listing as clean JSON: asking price, total price, monthly shared costs, area, bedrooms, ownership type, agency. Unofficial.

- **URL**: https://apify.com/neverempty/finn-real-estate.md
- **Developed by:** [NeverEmpty](https://apify.com/neverempty) (community)
- **Categories:** Real estate, Lead generation, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.46 / 1,000 listing returneds

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?

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

## FINN.no Real Estate Scraper - Norway Homes for Sale & Rent

Pull property listings from **FINN.no** — Norway's largest marketplace — as clean JSON: the asking price, the total price with the share of cooperative debt, the monthly shared costs, area, plot, bedrooms, ownership type, address with coordinates, the agency and the publish time, for sale (homes) or to rent (lettings), for any Norwegian county or district.

*Unofficial. This Actor is not affiliated with, endorsed by, or sponsored by FINN.no. All product names are trademarks of their respective owners.*

No API key. Reads the same public pages a visitor sees.

***

### Run it tomorrow and you only get what is new

Schedule this Actor with `monitoringMode: true` and each run returns **only the listings it has not returned before**. Not "here is everything again, please de-duplicate it yourself" — the Actor remembers what it already gave you, so a daily feed of a 4,000-listing search costs you the handful of new homes, not 4,000 rows every morning. **You are never charged for the same property twice.**

Listings your filters removed are remembered too, so widening a filter later does not resurrect them as false "new" listings. Listings that did not fit under your limit are **not** remembered, so they are still waiting on the next run instead of being silently lost.

***

### Why this one is different

#### Three prices, each with its real meaning

A Norwegian sale listing carries three numbers that are easy to confuse. FINN publishes the **asking price** (*prisantydning*), the **total price** (*totalpris* — the asking price plus the purchase costs such as the 2.5% document tax and, for a cooperative share, the buyer's share of the cooperative's debt) and the **monthly shared costs** (*felleskostnader*). In the 100 Oslo sale listings measured their medians were **NOK 6,950,000**, **NOK 7,398,901** and **NOK 4,741** — mixing them up changes a price by hundreds of thousands of kroner. This Actor keeps them apart as `price` (`priceType: "asking-price"`), `totalPrice` and `sharedCostPerMonth`. In the 64 freehold (*Selveier*) listings measured the total sat 2.5% above the asking price — that is the purchase cost, not debt; in the 31 cooperative (*Andel*) listings the gap ran from 0.1% to 69%, which is the debt. A new-build project with no single price comes back with `priceType: "sale-range"`, `price: null` and `priceFrom` / `priceTo` as FINN shows them ("fra 1 120 000"). A rental comes back as `price` with `priceType: "monthly-rent"` (median in the 50 Oslo rentals: **NOK 19,000**).

#### Area is a value, or honestly a range

FINN publishes the internal area as a range on some listings. This Actor returns `areaSqm` only when the range is a single value, and `areaSqmFrom` / `areaSqmTo` always — it never picks one end of a range and calls it the area. Area filters keep a ranged listing whenever any part of the range fits.

#### A private seller or landlord is never named

Listings from private advertisers (49 of the 150 measured, mostly rentals) carry no agency name on FINN. This Actor returns `advertiserType: "private"` with `agencyName: null` for them and the agency name only for agency listings. Norwegian mobile numbers and e-mail addresses written into the heading or address are removed. Listing IDs and prices, which can look like phone numbers, are left intact.

#### FINN's page is read, never executed

FINN ships its data as a React Router stream — one flat array with index references. This Actor reconstructs it in plain code; it never evaluates FINN's JavaScript.

#### Places are FINN's own codes, checked before anything is requested

FINN identifies places by code (`0.20061` is Oslo). This Actor accepts the sixteen county names as FINN lists them, or any code copied from a FINN address (`1.20061.20528` is the Bjerke district). An unknown word is refused up front; a code FINN does not know comes back as a free `no-such-search` row, because FINN answers such codes — and any page beyond its last — with HTTP 500 rather than an empty list. `searchedLocation` on every row carries the place FINN actually searched.

#### Pages overlap, and that is handled

FINN's pages can overlap: in one measurement the second page started at the 29th listing (21 of its 50 cards were already on page one); in another, taken a few hours later, the two pages did not overlap at all. Repeats are dropped before delivery, so **you are never charged twice for the same listing**, and the page budget is sized for the worst case of 29 new listings per page. FINN serves at most 50 pages per search.

#### Filters run here

Every filter — price, total price, shared costs, bedrooms, area, property type, ownership, advertiser, coming-for-sale, promoted, keywords, agency, publish date — runs on the rows already fetched. Only the location is sent to FINN. Nothing extra is requested and nothing extra is charged. A listing is never dropped for a value FINN did not publish.

***

### Input

Either paste a search URL, or build one from the fields below.

| Field | Type | Default | Description |
|---|---|---|---|
| `searchUrl` | string | — | e.g. `https://www.finn.no/realestate/homes/search.html?location=0.20061`. Only `location` is kept |
| `channel` | string | `sale` | `sale` (homes) or `rent` (lettings) |
| `location` | string | `oslo` | A county (`oslo`, `akershus`, `vestland`, `rogaland`, `trondelag`, `innlandet`, `agder`, `buskerud`, `vestfold`, `ostfold`, `telemark`, `nordland`, `troms`, `finnmark`, `more-og-romsdal`, `svalbard`) or a FINN code. Empty = all of Norway |
| `maxListings` | integer | 20 | 1-1000. One listing = one row = one charged event. FINN serves at most 50 pages (up to 2,500 listings) per search |
| `maxPages` | integer | 0 | Hard cap on requests; 0 = decide from `maxListings`. 50 at most |
| `useProxy` | boolean | `false` | Off by default — FINN answers Apify's network directly. Switches on by itself if blocked |
| `keywords` / `keywordMatch` / `excludeKeywords` | array / string / array | — | Match on heading, type, ownership, address, area name, agency and furnishing |
| `agencyNames` | array | — | Keep only these agencies (partial match) |
| `minPrice` / `maxPrice` | integer | 0 | NOK. Asking price, or monthly rent. A project with only a range is kept whenever any part of the range fits. **A listing with no price is kept** |
| `maxTotalPrice` | integer | 0 | NOK. FINN's total price — asking price plus purchase costs and any share of cooperative debt (sale only) |
| `maxSharedCost` | integer | 0 | NOK per month |
| `requirePrice` | boolean | `false` | Drop listings with no published price |
| `minBedrooms` / `maxBedrooms` | integer | -1 | -1 = off. FINN counts a studio as 0 bedrooms. **An unpublished count is kept** |
| `minAreaSqm` / `maxAreaSqm` | integer | 0 | m². A ranged listing is kept if any part of the range fits |
| `propertyTypes` | array | — | Partial match on FINN's words: `Leilighet`, `Enebolig`, `Rekkehus`, `Tomannsbolig`, `Gårdsbruk` … |
| `ownershipTypes` | array | — | Sale only: `Selveier`, `Andel`, `Aksje`. **An unpublished type is kept** |
| `advertiserType` | string | `any` | `agency` or `private` |
| `excludeComingForSale` | boolean | `false` | Drop listings flagged as coming for sale |
| `excludePromoted` | boolean | `false` | Drop paid highlight placements (102 of the 150 measured carry one) |
| `publishedWithinDays` | integer | 0 | By FINN's publish timestamp. 0 = off |
| `monitoringMode` | boolean | `false` | Return only listings not returned on a previous run |
| `resetMonitoringState` | boolean | `false` | One-shot: forget what was already returned |

```json
{ "channel": "sale", "location": "oslo", "maxListings": 200,
  "minBedrooms": 2, "maxTotalPrice": 8000000, "maxSharedCost": 5000, "ownershipTypes": ["Selveier"], "excludeComingForSale": true }
```

#### Monitoring mode turns this into a daily feed

FINN lists newest first. Schedule this Actor and set `monitoringMode: true`: each run returns **only the listings it has not returned before**, reading on past the listings it already knows — up to FINN's 50 pages, or `maxPages` if you set it — until it has `maxListings` new ones (in Oslo one page of 50 covers about three hours of new sale listings, so a daily feed of 200 is a few pages). Listings removed by your filters are remembered too, so changing a filter later does not resurrect them as false "new" listings — and listings that simply **did not fit under `maxListings`, or were cut off by the run's charge limit, are not remembered**, so they are still waiting for you on the next run rather than being silently lost.

Measured on 2026-09-05: **5,249** homes for sale in Oslo; **2,466** to rent in Oslo; **41,311** for sale across Norway.

***

### Output

One row per listing:

```json
{
  "source": "finn.no",
  "status": "ok",
  "listingId": "475590706",
  "title": "Arealeffektiv og delikat 1 roms i 7. etasje. Felles takterrasse. Varmt vann og internett inkl. (fiber). Nært \"alt\".",
  "url": "https://www.finn.no/realestate/homes/ad.html?finnkode=475590706",
  "searchedLocation": "Oslo",
  "transactionType": "sale",
  "propertyType": "Leilighet",
  "ownershipType": "Andel",
  "isComingForSale": false,
  "isPromoted": true,
  "price": 2980000,
  "priceFrom": null,
  "priceTo": null,
  "priceCurrency": "NOK",
  "priceType": "asking-price",
  "totalPrice": 3167081,
  "sharedCostPerMonth": 3837,
  "areaSqm": 18,
  "areaSqmFrom": 18,
  "areaSqmTo": 18,
  "plotSqm": 2368,
  "bedrooms": 0,
  "furnishedState": null,
  "address": "Stavangergata 46 A, Oslo",
  "localAreaName": "Bjølsen - Perfekt førstegangskjøp.",
  "latitude": 59.94441,
  "longitude": 10.75882,
  "advertiserType": "agency",
  "agencyName": "OBOS eiendomsmeglere - Kalbakken",
  "viewingTimes": ["2026-09-13T12:00:00.000Z"],
  "mainImageUrl": "https://images.finncdn.no/dynamic/default/2026/9/vertical-2/05/6/475/590/706_….jpg",
  "publishedAt": "2026-09-05T08:00:27.000Z",
  "scrapedAt": "2026-09-05T09:30:32.423Z"
}
```

Measured across 150 live listings (129 unique) on 2026-09-05 — 100 for sale and 50 to rent in Oslo: id, heading, url, type, address, advertiser and publish time on **150 of 150**; bedrooms on **149**; coordinates on **141** (nine rentals carried 0,0, which is returned as null, not as a point in the Gulf of Guinea); a price on **147** — the three without one are a coming-for-sale home listed twice, with no price yet, and a project that carries a range instead (`priceFrom` / `priceTo`); an area on **147** (146 as a single value); a plot area above zero on **90** (for a flat this is the whole estate's plot, not the flat's); a total price on **97** and monthly shared costs on **84 of the 100 sales** (a zero on FINN is returned as null, not as a free home); an ownership type on **100 of the 100 sales**; a local area name on **137**; viewing times on **91**. The card carries at most three preview images, so no image count is returned — only `mainImageUrl`. `localAreaName` is the agent's own line and sometimes reads as a slogan rather than a place.

#### Rows that are never charged

| `status` | when |
|---|---|
| `no-results` | FINN reports zero listings for this search. That is its answer, not a failure |
| `no-such-search` | FINN answered HTTP 500 — what it does for a location code it does not know, and for a page beyond its last. **Not** a claim that there is nothing there |
| `no-filter-match` | Listings were read, but your filters removed all of them. The row says how many were read |
| `no-new-listings` | Monitoring mode: nothing new since the previous run |
| `unreadable` | The page could not be read, its stream was missing, or it claimed a total above zero while yielding nothing |

***

### Pricing

Pay per listing returned. Rows that report an empty search, an unknown location, a filter that matched nothing, or a page that could not be read are **not** charged. A listing that appears twice across pages is dropped before delivery, so it is never charged twice.

***

### Notes

- `robots.txt` was read in full on 2026-09-05. The `User-agent: *` section disallows messaging, profile, contact, map (`/map`, `/map?`), gallery, report and a few legacy pages. This Actor requests only `/realestate/homes/search.html` or `/realestate/lettings/search.html` with `location` and `page`.
- Only public pages are read. No login, no API key.

# Actor input Schema

## `searchUrl` (type: `string`):

Paste a FINN property search page, for example https://www.finn.no/realestate/homes/search.html?location=0.20061 or https://www.finn.no/realestate/lettings/search.html?location=1.20061.20528, and the two fields below are ignored. Only the location parameter is kept; every other filter runs here on the rows already fetched.

## `channel` (type: `string`):

sale = homes (bolig til salgs), rent = lettings (bolig til leie).

## `location` (type: `string`):

A county name as FINN lists it (oslo, akershus, vestland, rogaland, trondelag, innlandet, agder, buskerud, vestfold, ostfold, telemark, nordland, troms, finnmark, more-og-romsdal, svalbard), or a FINN location code copied from a FINN address (0.20061 is Oslo, 1.20061.20528 is the Bjerke district). Leave empty for the whole of Norway. Any other word is refused before a request is made.

## `maxListings` (type: `integer`):

How many listings to return. You are charged for the rows you actually receive. FINN serves at most 50 pages (up to 2,500 listings; pages may overlap and repeats are dropped) per search; narrow the location to see more.

## `maxPages` (type: `integer`):

A hard stop on how many pages are requested, whatever the filters do. 0 lets the run work it out. FINN has 50 pages at most.

## `useProxy` (type: `boolean`):

Off by default because FINN answers Apify's own network directly. If FINN ever starts blocking, the run switches to a proxy on its own.

## `keywords` (type: `array`):

Keep only listings whose heading, type, ownership, address, area name or agency contain these words. No extra requests are made.

## `keywordMatch` (type: `string`):

any: at least one keyword. all: every keyword.

## `excludeKeywords` (type: `array`):

Drop listings containing any of these words.

## `agencyNames` (type: `array`):

Keep only listings marketed by these agencies. Partial names work.

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

For sale this is the asking price (prisantydning); to rent it is the monthly rent, the way FINN publishes them. 0 means no minimum.

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

0 means no maximum. A listing with no published price is kept.

## `maxTotalPrice` (type: `integer`):

FINN's totalpris = asking price plus purchase costs (the 2.5% document tax and fees) and, for a cooperative share, the buyer's share of the cooperative's debt. 0 = off. A listing without a total price is kept.

## `maxSharedCost` (type: `integer`):

FINN's felleskostnader per month. 0 means no maximum.

## `requirePrice` (type: `boolean`):

Off by default: a listing without a price is not treated as a cheap one, it is simply kept.

## `minBedrooms` (type: `integer`):

-1 turns the filter off. FINN counts a studio as 0 bedrooms. A listing without a count is kept.

## `maxBedrooms` (type: `integer`):

-1 turns the filter off.

## `minAreaSqm` (type: `integer`):

FINN's internal area. A listing that publishes a range is kept if the top of the range fits. 0 means no minimum.

## `maxAreaSqm` (type: `integer`):

0 means no maximum.

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

FINN's Norwegian words, partial match: Leilighet (flat), Enebolig (detached house), Rekkehus (terraced), Tomannsbolig (semi-detached), Gårdsbruk, Garasje …

## `ownershipTypes` (type: `array`):

FINN's words, partial match: Selveier (freehold), Andel (housing cooperative share), Aksje (housing stock company). A listing without one is kept.

## `advertiserType` (type: `string`):

Agency listings, or listings from private sellers and landlords. Private advertisers are never named.

## `excludeComingForSale` (type: `boolean`):

Drop listings FINN flags as coming for sale (not yet on the market).

## `excludePromoted` (type: `boolean`):

Drop listings FINN flags as paid highlight placements. Note that most agency listings carry such a flag (102 of the 150 measured).

## `publishedWithinDays` (type: `integer`):

By FINN's publish timestamp. 0 means no date filter. A listing whose date cannot be read is kept.

## `monitoringMode` (type: `boolean`):

Turn this on and schedule the Actor: each run returns only the listings it has never returned before, reading past the ones it already knows (up to FINN's 50 pages, or maxPages) until it has maxListings new ones. Listings removed by your filters are remembered too. Listings that did not fit under the limit are not remembered, so they are still waiting for you on the next run.

## `resetMonitoringState` (type: `boolean`):

Clears the memory for this search, so the next monitoring run starts from scratch. Works with or without monitoring mode on.

## Actor input object example

```json
{
  "channel": "sale",
  "location": "oslo",
  "maxListings": 20,
  "maxPages": 0,
  "useProxy": false,
  "keywords": [],
  "keywordMatch": "any",
  "excludeKeywords": [],
  "agencyNames": [],
  "minPrice": 0,
  "maxPrice": 0,
  "maxTotalPrice": 0,
  "maxSharedCost": 0,
  "requirePrice": false,
  "minBedrooms": -1,
  "maxBedrooms": -1,
  "minAreaSqm": 0,
  "maxAreaSqm": 0,
  "propertyTypes": [],
  "ownershipTypes": [],
  "advertiserType": "any",
  "excludeComingForSale": false,
  "excludePromoted": false,
  "publishedWithinDays": 0,
  "monitoringMode": false,
  "resetMonitoringState": false
}
```

# Actor output Schema

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

One row per listing: heading, the asking price (or the monthly rent) with its currency and what it means, FINN's total price (asking price plus purchase costs and any share of cooperative debt), a price range for projects, the monthly shared costs in their own column, internal area (as a value or a range), plot area, bedrooms, property and ownership type, furnishing (rentals), address and local area name with coordinates, whether the advertiser is an agency or a private person (never named), the agency name, viewing times, the main image, and the publish time. Every row also names the place FINN actually searched. Searches with no results, location codes FINN does not know, filters that matched nothing, and pages that could not be read come back as their own rows and are not charged.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/finn-real-estate").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("neverempty/finn-real-estate").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 '{}' |
apify call neverempty/finn-real-estate --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,neverempty/finn-real-estate"
        }
    }
}

```

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/h7Q2DgFrULnnIKEVL/builds/6A6VDa9LF3o8eDbgt/openapi.json
