Cost of Living Scraper - Baskets, Indices & Official Floor avatar

Cost of Living Scraper - Baskets, Indices & Official Floor

Pricing

$4.00 / 1,000 city priceds

Go to Apify Store
Cost of Living Scraper - Baskets, Indices & Official Floor

Cost of Living Scraper - Baskets, Indices & Official Floor

One row per city: cost of living baskets for one person and for a family of four, cost-of-living indices, household estimates, nomad scores and the official country floor. Every figure is labelled city or country grain. For relocation teams, HR pay bands and market sizing.

Pricing

$4.00 / 1,000 city priceds

Rating

0.0

(0)

Developer

SR

SR

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

17 days ago

Last modified

Categories

Share

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

This cost of living scraper 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. It is built for relocation platforms pricing a move, HR compensation teams testing an offer, and market sizing across a list of cities: give it a list of city names and get one structured row each, with every figure labelled city or country grain.

What this cost of living scraper returns

  • 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

FieldRequiredWhat it does
citiesYesCities to price. Write Lisbon, Lisbon, Portugal to pin the country, or paste a full city page address. Up to 50 per run.
currencyNoDisplay currency for every money figure. One of USD, EUR, GBP, CHF, SEK, NOK, DKK, PLN, CZK, JPY, CAD, AUD, BRL, INR. Default USD.
include_basketsNoCost baskets and basket lines. On by default.
include_indicesNoIndices, household estimates and versus-city percentages. On by default.
include_scoresNoNomad score block. On by default.
include_floorNoOfficial country floor. On by default.
max_citiesNoUpper bound on cities returned per run, 1 to 50. Default 20.
cursorNoContinuation token from an earlier run's next_cursor. Send it with the same input and the run picks up where the last one stopped. API field, not on the run form.

Output

{
"city": "Lisbon",
"country": "Portugal",
"currency": "EUR",
"as_of": "2026-10-07",
"baskets": {
"grain": "city",
"one_person": {
"total_with_rent": 1758.96,
"without_rent": 632.55,
"rent_utilities": 1126.41,
"food": 399.03,
"transport": 94.84,
"salary_after_tax": 1435.98
},
"family_of_four": {
"total_with_rent": 4007.32
},
"basket_lines": {
"lunch_menu": 13.15,
"apartment_1bd_centre": 1257.93,
"utilities_one": 105.57,
"internet": 30.42
},
"world_rank": 2123,
"country_rank": 1,
"status": "ok"
},
"indices": {
"grain": "city",
"cost_of_living_index": 54.1,
"rent_index": 34.5,
"cost_of_living_plus_rent_index": 45,
"groceries_index": 49.1,
"restaurant_price_index": 56.8,
"local_purchasing_power_index": 66.8,
"family_of_four_excl_rent": 2743.8,
"single_person_excl_rent": 760.4,
"pct_cheaper_than": {
"vs_city": "Amsterdam",
"cost_of_living_pct": 30.8,
"direction": "cheaper",
"rent_pct": 34.7
},
"status": "ok"
},
"scores": {
"grain": "city",
"nomad_score": 3.79,
"nomad_rank": 70,
"cost_of_living_nomad": 4279.3,
"cost_of_living_expat": 2968.58,
"cost_of_living_family": 5712.6,
"cost_of_living_local": 1631.91,
"lifestyle_band": "Very rich",
"lifestyle_band_monthly": 2867.48,
"internet_mbps": 101,
"safety": "Good",
"status": "ok"
},
"floor": {
"grain": "country",
"country": "Portugal",
"reference_year": "2024",
"hicp_annual_average_index": 122.15,
"hicp_label": "harmonised consumer price index, annual average, 2015 = 100",
"hicp_period": "2024",
"ppp_conversion_factor": 0.515993,
"ppp_label": "purchasing power parity conversion factor, local currency units per international dollar",
"error": null
},
"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.89469,
"rate_date": "2026-10-07"
}
}
}

This is one item out of a live run on 7 October 2026 (examples/live-lisbon.json, input cities: ["Lisbon, Portugal"], currency: "EUR"), trimmed to the blocks that matter; the row also carries currency_note per block, quality_of_life and population on the basket, headline_total_with_rent for the same figure the source prints at the top, and fetched_at (the ISO 8601 moment the row was fetched) beside the date-only as_of. Note that world_rank and country_rank sit on baskets, not inside one_person. Figures move as the sources update.

The floor block keeps its full key set on every run: hicp_label and hicp_period sit next to the harmonised index, ppp_label next to the conversion factor, national_cpi_index, national_cpi_annual_change_pct, national_cpi_period and national_cpi_label are filled for the Netherlands and stay null elsewhere, and a figure the source does not publish for that country comes back as null instead of a missing key.

Every block also carries an error field: null on the rows above, and a short reason such as city not found, upstream unavailable, no official figure returned or country not in the official floor when that block came back without figures, so a block that missed is labelled rather than missing. Each row carries coverage_count (how many of the four blocks answered, 0 to 4 — the count the pricing section charges on) and country_code. The run as a whole reports partial and next_cursor: a run that collected everything in one go has partial: false and next_cursor: null. partial: true with a next_cursor means more cities are still to collect; partial: true with next_cursor: null means the list is complete but some rows carry a soft error (no_data, conversion_unavailable) — see errors and the per-row error fields.

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 actorlogiover/numbeo-cost-of-living-scrapeautomation-lab/numbeo-scraperlulzasaur/nomadlist-scraper
Sources in one rowBaskets, indices, nomad scores, official floorOne price tableOne price tableOne score page
Per 1k rows, free plan$4.00$3.50$5.00$10.00
Per 1k rows, Store "from" price$4.00$1.99$3.25$10.00
Run-start eventNone$0.00005 per GB of run memory$0.01$0.00005 per GB of run memory
Official statistics floorYes, with grain labelsNoNoNo
Grain labels per blockYesNoNoNo
Versus-city percentagesYesNoNoNo
Household estimates excluding rentYesNoNoNo
Nomad score blockYesNoNoYes
Currency conversion recordedYesNoNoNo

The two price rows are what a buyer actually pays: the per-event rate each competitor lists for a free Apify plan, and the "from" price the Store shows for that actor. logiover charges $0.0035 per result on a free plan, $0.003 on BRONZE and $0.00199 from GOLD up; automation-lab $0.005 per city plus a $0.01 run start ($0.00325 from); lulzasaur $0.01 per result at every tier. All three also bill a run-start event on top of that: logiover and lulzasaur $0.00005 per GB of run memory, automation-lab a flat $0.01. This actor bills no run-start event — the row price is the only charge it adds. Compute usage of the run itself is billed by Apify on every plan, for their runs as for this one.

The closest competitor by audience is logiover/numbeo-cost-of-living-scrape at 59 users (10 of them in the last 30 days), and its row costs $0.0035 against this actor's $0.004 on a free plan, then $0.003 (BRONZE) down to $0.00199 (GOLD and above) against this actor's flat $0.004 once the plan is paid. 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: the cheaper row on paid plans, and a single source that never disagrees with itself.

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, so you only pay for results you receive. There is no actor-start fee and no other event on this actor: the row price is the whole price this actor adds. Compute usage of the run itself is billed by Apify at its usual rate, exactly as it is for any other actor. The run record reports billing.cities, the number of rows charged for that run, so an invoice line can be checked against the run that produced it.

Free-plan Apify accounts receive up to 10 city rows per run, and those same 10 rows are the most a free run can be charged. Larger city lists run from a paid plan, or split across runs.

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 long list can spend its collection budget partway through. The run follows the continuation token by itself while there is time left, so most lists come back complete in one run. When it does run out, it stops in the open instead of quietly handing you half a city: partial goes to true, next_cursor carries a continuation token and the run warns
    Request budget reached; continue with next_cursor
    . Send that token as cursor with the same input and the rest is collected. The rows you already hold are delivered and charged for; the continuation is charged only for the city rows it adds. partial with a cursor only means that deferral; on a finished list (next_cursor: null) it flags rows that carry a soft error, not work that is still owed.
  • 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.

What if a run stops partway through my city list? It says so rather than hiding it: partial is true, next_cursor holds a continuation token and a warning tells you to continue. Pass that token as cursor with the input you already used and the remaining cities come back as normal rows. Cities you already received are not collected or charged twice.

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.

What if a city is not in the sources at all? The run still succeeds. The row comes back with error: "city not found" in the blocks that could not answer, the run record carries no figures for <city> in errors, and the row is delivered without being charged. Add the country (Lisbon, Portugal) if the name resolved to the wrong place.