# Ejendomstorvet Scraper - Danish Commercial Property (`studio-amba/ejendomstorvet-scraper`) Actor

Scrape commercial property listings from Ejendomstorvet.dk, Denmark's largest portal for erhvervslokaler: offices, retail, warehouses and commercial land for rent or sale. Prices, areas, rent per m2, broker contacts.

- **URL**: https://apify.com/studio-amba/ejendomstorvet-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

## Ejendomstorvet Scraper

Scrape commercial property listings from [Ejendomstorvet.dk](https://www.ejendomstorvet.dk), Denmark's largest portal for erhvervslokaler and erhvervsejendomme. The portal carries around 11,000 active listings from professional brokers across the whole country: offices (kontorlokaler), retail (butikslokaler), warehouses (lagerlokaler), clinics, hotels, restaurants and commercial land (erhvervsgrunde), for rent and for sale.

Each result comes straight from the portal's own data feed, so you get clean structured numbers instead of parsed page text: yearly rent, rent per m2 per year (the standard Danish commercial quote), sale prices, floor and plot areas, energy labels, coordinates, and the listing broker's name and phone number.

### Why use it

- **Market research** — track asking rents and sale prices per municipality or segment across Denmark.
- **Lead generation** — every listing includes the broker's name and phone number, plus a link to the broker's own site.
- **Site selection** — filter offices, retail or warehouse space by city and compare rent per m2 across locations.
- **Investment screening** — sale listings include asking price, areas and the advertised return percentage where brokers publish one.
- **Monitoring** — run it on a schedule and diff against yesterday's dataset to catch new listings the day they appear.

### How to scrape Ejendomstorvet data

1. Set **Location** to a Danish city or municipality — `København`, `Aarhus`, `Odense`, `Aalborg`, `Esbjerg`. ASCII spellings work too (`Kobenhavn`). Leave it empty to scrape the entire country.
2. Pick a **Listing Type**: rent (leje), sale (salg) or both.
3. Pick a **Property Segment**: office (kontor), retail (butik), warehouse (lager), commercial land (grund), clinic, hotel & restaurant, or all segments.
4. Set **Max Results** and run. Results land in the dataset as one flat JSON object per listing.

Example: all office space for rent in København:

```json
{
    "searchQuery": "København",
    "listingType": "rent",
    "segment": "kontor",
    "maxResults": 200
}
```

Example: warehouse and production space around Aarhus:

```json
{
    "searchQuery": "Aarhus",
    "segment": "lager",
    "maxResults": 100
}
```

Example: commercial land for sale, whole country, largest plots first:

```json
{
    "listingType": "sale",
    "segment": "grund",
    "sort": "area_desc",
    "maxResults": 500
}
```

An empty input `{}` works as well — it returns listings from all of Denmark up to the default cap of 100.

### Input options

| Field | Type | Description |
|-------|------|-------------|
| `searchQuery` | string | City or municipality (e.g. `København`, `Aarhus`). Empty = all of Denmark. |
| `listingType` | string | `all` (default), `rent` (leje) or `sale` (salg). |
| `segment` | string | `all` (default), `kontor`, `butik`, `lager`, `grund`, `klinik`, `restaurant`, `andre`. |
| `sort` | string | `created_desc` (newest first), `salesprice_asc/desc`, `area_asc/desc`, `return_asc/desc`, or empty for the site default. |
| `maxResults` | integer | Cap on returned listings. Default 100, hard maximum 12,000. |
| `proxyConfiguration` | object | Apify proxy settings. Danish residential proxy is the default. |

### Output fields

| Field | Example | Description |
|-------|---------|-------------|
| `title` | `"Attraktivt kontorlejemål i Indre By"` | Listing headline |
| `listingType` | `"rent"` | `rent` or `sale` |
| `segment` | `"Kontor"` | Commercial segment |
| `usageProposals` | `["Kontor", "Klinik"]` | All proposed uses |
| `price` | `918272` | Sale: total price. Rent: yearly rent. Null when on request |
| `currency` | `"DKK"` | Always DKK, never converted |
| `priceUnit` | `"DKK/year"` | `DKK total` for sales, `DKK/year` for rentals |
| `rentPerM2Year` | `2100` | Rent in kr. per m2 per year (rentals only) |
| `yearlyOperatingCostDkk` | `72000` | Yearly operating cost |
| `returnPercent` | `7.25` | Advertised return (investment sales) |
| `surface` | `544` | Floor area in m2 |
| `groundSurface` | `3749` | Plot area in m2 |
| `address` | `"Humletorvet 27, 4., 1799 København V"` | Full address |
| `postalCode` | `"1799"` | Danish postal code |
| `city` | `"København V"` | Postal district |
| `municipality` | `"København"` | Municipality |
| `latitude` / `longitude` | `55.67` / `12.58` | WGS84 coordinates |
| `energyLabels` | `["D"]` | Energy label(s) |
| `newListing` | `true` | Portal's "new" flag |
| `promoted` | `false` | Paid placement flag |
| `brokerName` | `"Nordicals Sjælland P/S"` | Listing broker |
| `brokerPhone` | `"+45 59 50 12 12"` | Broker phone |
| `imageUrl` | `"https://img.ejendomstorvet.dk/..."` | Primary photo |
| `url` | `"https://www.ejendomstorvet.dk/leje/..."` | Listing on the portal |
| `externalListingUrl` | `"https://nordicals.dk/..."` | Listing on the broker's site |
| `id` | `"7ed2a06e-..."` | Portal listing UUID |
| `source` | `"ejendomstorvet.dk"` | Data source |
| `scrapedAt` | `"2026-08-18T10:00:00.000Z"` | Scrape timestamp |

### Example output

```json
{
    "title": "1.129 m² nyopført og funktionel højloftet lagerejendom og kontorfaciliteter",
    "listingType": "rent",
    "segment": "Lager & produktion",
    "usageProposals": ["Lager", "Lager & produktion"],
    "price": 784655,
    "currency": "DKK",
    "priceUnit": "DKK/year",
    "rentPerM2Year": 758,
    "yearlyOperatingCostDkk": 72000,
    "returnPercent": null,
    "surface": 1129,
    "groundSurface": 17681,
    "address": "Aa.Louis-Hansens Alle 16A, 3060 Espergærde",
    "streetName": "Aa.Louis-Hansens Alle",
    "postalCode": "3060",
    "city": "Espergærde",
    "municipality": "Helsingør",
    "latitude": 55.9962,
    "longitude": 12.5423,
    "energyLabels": ["Ikke krav om energimærke"],
    "newListing": true,
    "promoted": false,
    "brokerName": "EDC Erhverv Poul Erik Bech",
    "brokerPhone": "+45 58 58 88 88",
    "imageUrl": "https://img.ejendomstorvet.dk/...",
    "url": "https://www.ejendomstorvet.dk/leje/nordsjaelland/helsingoer/lager-produktion/03e670be-938e-44f1-b41a-c9b522b1ec65",
    "externalListingUrl": "https://www.edc.dk/erhverv/...",
    "id": "03e670be-938e-44f1-b41a-c9b522b1ec65",
    "source": "ejendomstorvet.dk",
    "scrapedAt": "2026-08-18T10:00:00.000Z"
}
```

### Pricing and cost estimate

The actor charges a small start fee plus a fee per result. On top of that, standard Apify platform usage applies (compute and proxy). A typical run is light: the actor talks to the portal's JSON feed directly, about 20 listings per request, no browser.

As a working number, expect roughly **$0.005-0.006 per listing all-in** at the default settings. A 500-listing run is around $3, and a full-portal snapshot (~11,000 listings) stays in the tens of dollars, mostly per-result fees.

Note on cost timing: a run's usage cost only settles after the run reports SUCCEEDED. Reading the cost of a run that is still in progress shows a number far below the final one — don't size budgets off a partial run.

### Limitations

- The portal's own detail API requires an API key, so fields that only exist on the detail page (full description text, all photos, floor plans) are not included. The search feed already carries prices, areas, addresses, coordinates and broker contacts.
- `price` is null when a broker lists "price on request" — this is the portal's data, not a scrape failure.
- Multi-unit rental groups are returned as individual listings, matching what the portal shows with grouping off.
- Rent figures are yearly, as quoted on the portal. Divide by 12 for monthly rent.
- Free-text location fallback matches literally (addresses, titles), so a misspelled city name can return few or zero results. Municipality names are the reliable filter.

### Fair use

The actor scrapes only publicly visible listing data at a polite request rate through the portal's public search feed. Respect Ejendomstorvet's terms of service and Danish law when using the data, especially broker contact details.

# Actor input Schema

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

City or municipality to search (e.g. 'København', 'Aarhus', 'Odense', 'Aalborg'). Danish spelling and ASCII forms both work ('København' or 'Kobenhavn'). Leave empty to search all of Denmark.

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

Rent (leje), sale (salg/køb), or both.

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

Commercial property segment (anvendelse). 'All segments' returns every listing type on the portal.

## `sort` (type: `string`):

How the portal orders results. Empty uses the site default.

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

Maximum number of listings to return. The whole portal holds roughly 11,000 listings.

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

Proxy settings. The site sits behind Cloudflare (passive on the search API, see docs/site-recon/ejendomstorvet.md) — Danish residential proxy is the reliable default.

## Actor input object example

```json
{
  "searchQuery": "København",
  "listingType": "all",
  "segment": "all",
  "sort": "created_desc",
  "maxResults": 20,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "DK"
  }
}
```

# 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": "København",
    "sort": "created_desc",
    "maxResults": 20,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "DK"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("studio-amba/ejendomstorvet-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": "København",
    "sort": "created_desc",
    "maxResults": 20,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "DK",
    },
}

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

```

## MCP server setup

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