# Cost of Living: city baskets, indices and official floor (`s-r/cost-of-living`) Actor

One row per city with cost of living baskets for one person and for a family of four, cost-of-living indices and household estimates, nomad scores where that door answers, and the official country-grain price floor. Every figure is labelled city or country grain.

- **URL**: https://apify.com/s-r/cost-of-living.md
- **Developed by:** [SR](https://apify.com/s-r) (community)
- **Categories:** Business, Other
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$4.00 / 1,000 city priceds

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/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

## Cost of Living Comparison Actor: one row per city with baskets, indices and an official floor

This cost of living comparison actor returns one row per city with the cost of
living baskets for one person and for a family of four (total with rent,
without rent, rent and utilities, food, transport, salary after tax), twelve
basket lines from lunch to a one-bedroom flat, six cost-of-living indices with
the household estimates excluding rent, the versus-city percentages, the nomad
score block where that door still answers, and the official country-grain
price floor next to all of it. Give it a list of city names and get one
structured row each, with every figure labelled city or country grain.

### What you get

- One dataset row per city, so a 40-city market sizing is billable only for
  the rows you actually received.
- Cost baskets for one person and for a family of four: `total_with_rent`,
  `without_rent`, `rent_utilities`, `food`, `transport` and
  `salary_after_tax`, plus the world rank and the rank inside the country.
- Twelve basket lines covering the things a relocation quote has to price:
  lunch menu, dinner for two, a fast-food meal, beer, cappuccino, a soft
  drink, one-bedroom flat in the centre, a cheaper one-bedroom flat, a
  three-bedroom flat, utilities for one person and for a family, and an
  internet plan.
- Six cost-of-living indices: cost of living, rent, cost of living plus rent,
  groceries, restaurant prices and local purchasing power.
- Household estimates excluding rent for a family of four and for a single
  person, and the versus-reference-city percentages for cost of living and
  for rent, so "how much cheaper is Lisbon than Amsterdam" is a field rather
  than a separate request.
- The nomad score block: nomad score and rank, the monthly cost for a nomad,
  an expat, a family and a local, the lifestyle band with its monthly figure,
  average internet speed, the quality of life, family and community bands,
  and the safety ratings.
- The official country-grain price floor: the harmonised consumer price index
  (annual average, 2015 = 100), the purchasing-power parity conversion factor
  in local currency units per international dollar, and for the Netherlands
  the national consumer price index with its annual change.
- A `field_grain` map on every row saying which blocks are city grain and
  which are country grain, and a `data_coverage` count block saying which
  blocks returned figures.
- Money in the currency you ask for, with the conversion rate and its date
  recorded on the row so any number can be traced back to what the source
  page showed.

### Why do a cost of living comparison

Nomad and relocation platforms, HR compensation teams and private equity
market sizing all have the same question: what does life in this city cost,
and does the salary on the table survive it. The crowd-sourced price tables
answer the first half well and the official statistics answer the second half
differently, and until now a buyer had to pick one or the other and hope the
grain matched.

That gap matters because the numbers disagree in ways that change a decision.
A crowd-sourced basket for one city is a real monthly figure. A national
consumer price index is a percentage change against a base year. A
purchasing-power parity factor is local currency units per international
dollar. Placing them in the same row without labels is how a relocation model
ends up comparing a Lisbon flat to a Portuguese inflation rate.

The salary arbitrage angle is the reason the Dutch market cares. A Bay Area
package tested against Lisbon costs, or a Dutch offer tested against a
cheaper market, is one subtraction once `salary_after_tax` and the basket sit
in the same currency in the same row. Doing that by hand means opening three
different sites, reconciling three page shapes, guessing at the grain of an
index, and re-doing it every time the numbers move.

### Input

| Field | Required | What it does |
|---|---|---|
| `cities` | Yes | Cities to price. Write `Lisbon`, `Lisbon, Portugal` to pin the country, or paste a full city page address. Up to 50 per run. |
| `currency` | No | Display currency for every money figure. Default `USD`. |
| `include_baskets` | No | Cost baskets and basket lines. On by default. |
| `include_indices` | No | Indices, household estimates and versus-city percentages. On by default. |
| `include_scores` | No | Nomad score block. On by default. |
| `include_floor` | No | Official country floor. On by default. |
| `max_cities` | No | Upper bound on cities returned per run, 1 to 50. Default 20. |

### Output

```json
{
  "city": "Lisbon",
  "country": "Portugal",
  "currency": "EUR",
  "as_of": "2026-09-25",
  "baskets": {
    "grain": "city",
    "one_person": {
      "total_with_rent": 1729.57,
      "without_rent": 621.98,
      "rent_utilities": 1107.59,
      "food": 392.36,
      "transport": 93.25,
      "salary_after_tax": 1411.98,
      "world_rank": 2123,
      "country_rank": 1
    },
    "family_of_four": { "total_with_rent": 3940.36 },
    "basket_lines": {
      "lunch_menu": 12.93,
      "apartment_1bd_centre": 1236.72,
      "utilities_one": 103.81,
      "internet": 29.91
    }
  },
  "indices": {
    "grain": "city",
    "cost_of_living_index": 55.4,
    "rent_index": 34.8,
    "restaurant_price_index": 56.6,
    "local_purchasing_power_index": 69.5,
    "family_of_four_excl_rent": 2745.5,
    "single_person_excl_rent": 760.9,
    "pct_cheaper_than": {
      "vs_city": "Amsterdam",
      "cost_of_living_pct": 30.7,
      "rent_pct": 34.1
    }
  },
  "scores": {
    "grain": "city",
    "nomad_score": 3.79,
    "nomad_rank": 69,
    "cost_of_living_nomad": 4338.88,
    "lifestyle_band": "Medium",
    "internet_mbps": 100,
    "safety": "Good",
    "status": "ok"
  },
  "floor": {
    "grain": "country",
    "country": "Portugal",
    "hicp_annual_average_index": 122.15,
    "ppp_conversion_factor": 0.515993,
    "reference_year": "2024"
  },
  "field_grain": {
    "baskets": "city",
    "indices": "city",
    "scores": "city",
    "floor": "country"
  },
  "data_coverage": { "baskets": 1, "indices": 1, "scores": 1, "floor": 1 },
  "missing_blocks": [],
  "conversion": {
    "USD": { "to": "EUR", "rate": 0.87974, "rate_date": "2026-09-24" }
  }
}
```

Figures shown are from a September 2026 run and move as the sources update.

### Use cases

**Relocation platform pricing a move.** A user in Amsterdam asks what Lisbon
costs. One call returns both cities in one currency: the basket for one
person, the basket for a family of four, the rent and utilities line, and
the versus-city percentage that the platform can put straight into its copy.
The floor block lets the platform show a national price index next to the
city basket without mixing the two.

**HR compensation team testing a package.** A Bay Area offer is on the table
and the candidate is looking at Lisbon. `salary_after_tax` in the row is the
local benchmark; the basket is what the package has to cover. The
purchasing-power parity factor converts a local figure into an international
dollar for a multi-country band, which is what a global comp spreadsheet
actually needs.

**Private equity market sizing.** Forty cities in one run, one row each, with
the food and transport basket as the cheapest way to size a consumer market
and the local purchasing power index as the correction. `max_cities` bounds
the run and the per-row charge means a pilot of five cities costs a pilot's
money.

**Salary arbitrage research.** The Dutch angle is net salary minus the
basket, computed per city pair. `salary_after_tax` and `total_with_rent` are
the two fields that matter, and putting them in the same currency in the same
row is what makes the subtraction possible without a second tool.

### How it compares

| | This actor | logiover/numbeo-cost-of-living-scrape | automation-lab/numbeo-scraper | lulzasaur/nomadlist-scraper |
|---|---|---|---|---|
| Sources in one row | Baskets, indices, nomad scores, official floor | One price table | One price table | One score page |
| Per 1k rows | $4.00 | Free (compute only) | Free (compute only) | Free (compute only) |
| Official statistics floor | Yes, with grain labels | No | No | No |
| Grain labels per block | Yes | No | No | No |
| Versus-city percentages | Yes | No | No | No |
| Household estimates excluding rent | Yes | No | No | No |
| Nomad score block | Yes | No | No | Yes |
| Currency conversion recorded | Yes | No | No | No |

The closest competitor by audience is `logiover/numbeo-cost-of-living-scrape`
at 53 users, and like the rest of the category it is free: what a buyer pays
is compute. What those actors return is one source's page as JSON. This actor
folds four blocks into one schema, labels the grain of each, and puts the
official country floor next to the crowd numbers so a model can tell a city
basket from a national index. What they have that we do not: a free run for a
one-source lookup, and nothing to configure.

### Pricing

All pricing is pay-per-event at $0.004 per `city`, one event per returned
city row. $4.00 per 1,000 rows. Rows that carry no figures at all are
delivered but never charged. All pricing is pay-per-event, you only pay for
results you receive. No actor-start fee, no per-compute-unit charges.

### Limits and gotchas

- The scoring door answers a limited number of cities per run and then stops.
  The actor paces itself and stops as soon as that happens, and every
  remaining city comes back with `scores.status` set to `blocked` rather than
  a zero score. `include_scores=false` skips that block entirely and makes a
  long city list cheaper.
- The price tables and the index pages also answer a limited number of
  requests before they refuse. The actor pauses between cities. `max_cities`
  is the real lever on a big list: raise it only when the run is worth the
  extra minutes.
- Money figures arrive in whatever unit each source publishes in. The actor
  reads the unit off the page, converts to your `currency`, and records the
  rate and its date under `conversion`. If no rate can be published for a
  unit, those figures stay in their original unit and the row says so.
- The official floor is country grain. `field_grain` marks it as such. Do not
  read a national consumer price index as a city number; it is there as a
  floor, not as a basket.
- The purchasing-power parity factor is local currency units per
  international dollar. It is not a price level and not a cost.
- City resolution uses the source's own city index, so a bare `Lisbon`
  resolves to the right country. An ambiguous name with no country resolves
  to the largest match and the row reports the country it found.
- A run of 20 cities takes a few minutes because of the pacing. That is the
  cost of staying inside the sources' budgets.

### FAQ

**How do I run a cost of living comparison of cities?**
Put both cities in `cities` (`Lisbon`, `Amsterdam`) and read the two rows.
`pct_cheaper_than` on each row gives the versus-city percentage the index
source publishes, and both rows are converted to the same currency so the
baskets subtract cleanly.

**What does a cost of living comparison between cities actually return?**
One row per city with four blocks: the cost baskets, the indices and
household estimates, the nomad score block, and the official country floor.
Each block carries a grain label and the row carries a coverage count, so
you can see which blocks returned figures.

**Can I get cost of living for Lisbon, Portugal specifically?**
Yes. Write `Lisbon, Portugal` to pin the country, or `Lisbon` alone and let
the city index resolve it. The row comes back with `country` filled in
either way.

**How do I compare minimum wage against cost of living?**
The row carries `salary_after_tax` alongside the baskets, which is the
monthly net figure the comparison needs. Bring your own minimum wage figure
for the market you are testing and subtract `total_with_rent` for the
standard of living you have in mind.

**Does the nomad score block always come back?**
No. That door answers a limited number of cities per run. When it closes,
the remaining rows carry `scores.status` set to `blocked`. Turn
`include_scores` off if you only want baskets and indices.

**Which figures are city grain and which are country grain?**
`field_grain` says so on every row. Baskets, indices and scores are city
grain. The floor block is country grain and carries the country name with it.

### Related Actors

- [Google Flights Scraper](https://apify.com/s-r/google-flights-scraper) for
  the travel cost side of a move alongside the monthly basket.
- [Zillow Scraper](https://apify.com/s-r/zillow-scraper) for US rental
  listings next to the rent and utilities line.
- [LinkedIn Jobs Scraper](https://apify.com/s-r/linkedin-jobs-scraper) for
  the salary side of the arbitrage question.

# Actor input Schema

## `cities` (type: `array`):

Cities to price. One per line or as a list. Write `Lisbon`, `Lisbon, Portugal` to pin the country, or paste a full city page address. Up to 50 per run.

## `currency` (type: `string`):

Display currency for every money figure in the row. Upstream figures arrive in USD and are converted once at the published reference rate, recorded on the row.

## `include_baskets` (type: `boolean`):

Cost baskets for one person and for a family of four, plus the basket lines. On by default.

## `include_indices` (type: `boolean`):

Cost-of-living indices, household estimates excluding rent, and the versus-reference-city percentages. On by default.

## `include_scores` (type: `boolean`):

Nomad score, cost bands, lifestyle band, internet speed and safety ratings. That door answers a limited number of cities per run and then closes; the rest come back with status blocked. On by default.

## `include_floor` (type: `boolean`):

Country-grain price floor from official statistics: the harmonised consumer price index, the purchasing-power parity conversion factor, and the Dutch consumer price index for the Netherlands. On by default.

## `max_cities` (type: `number`):

Upper bound on cities returned per run, 1 to 50. Default 20.

## Actor input object example

```json
{
  "cities": [
    "Lisbon",
    "Amsterdam, Netherlands"
  ],
  "currency": "USD",
  "include_baskets": true,
  "include_indices": true,
  "include_scores": true,
  "include_floor": true,
  "max_cities": 20
}
```

# Actor output Schema

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

One row per city, with baskets, indices, scores, the official floor, grain labels and a coverage count block.

## `output` (type: `string`):

OUTPUT record with the run's counts, coverage totals and status flags.

## `errors` (type: `string`):

Failures with a code and a redacted message. Absent when the run had none.

# 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 = {
    "cities": [
        "Lisbon"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("s-r/cost-of-living").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 = { "cities": ["Lisbon"] }

# Run the Actor and wait for it to finish
run = client.actor("s-r/cost-of-living").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 '{
  "cities": [
    "Lisbon"
  ]
}' |
apify call s-r/cost-of-living --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,s-r/cost-of-living"
        }
    }
}
```

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/9ubEZ2WM02a4bp1vh/builds/AYanrNmM2jcIQOTtS/openapi.json
