Skip to main content

coworkingview

PyPI Python Code: MIT Data: CC BY 4.0

Flexible-office prices and coworking-space search as tidy pandas DataFrames.

coworkingview is the Python client for the public API of CoworkingView. It gives you:

  • The CoworkingView Price Index: median, p25 and p75 prices by city for flex desks, fixed desks and private offices (per month), day passes (per day) and meeting rooms (per hour), with sample sizes. Open data under CC BY 4.0.
  • Catalogue search: coworking spaces, business centres, meeting rooms, event spaces and virtual offices, filtered by city, neighbourhood, operator, product, capacity and price, each with a link to its listing.

One call, one DataFrame. No API key, no account, one runtime dependency (pandas).

Install

pip install coworkingview

or with conda (once the conda-forge recipe is merged):

conda install -c conda-forge coworkingview

Requires Python 3.9 or newer.

60-second quickstart

>>> import coworkingview as cwv

>>> berlin = cwv.rates(city="berlin")
>>> berlin[["product", "unit", "median", "p25", "p75", "sample_size", "low_confidence"]]
          product   unit  median    p25    p75  sample_size  low_confidence
0       flex_desk  month   214.0  199.0  292.0           72           False
1        fix_desk  month   310.0  290.0  421.0           60           False
2  private_office  month   480.0  399.0  550.0           59           False
3        day_pass    day    35.0   <NA>   <NA>           59           False
4    meeting_room   hour    32.0   <NA>   <NA>           79           False

>>> print(berlin.attrs["attribution"])
Source: CoworkingView Price Index (https://coworkingview.com/en/tools/price-index), as of October 2026. Licensed under CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/). Aggregated figures only; no endorsement by CoworkingView or any operator is implied.

>>> cwv.cities()[["city_slug", "currency", "published", "property_count", "entry_median"]]
      city_slug currency  published  property_count  entry_median
0     abu-dhabi      AED       True               2        1453.0
1        berlin      EUR       True              85         265.0
2        madrid      EUR       True              10         325.0
...

>>> rooms = cwv.spaces(city="berlin", product="meeting_room", limit=20)
>>> rooms[["title", "zone_slug", "operator_name", "market_verdict"]].head(3)
                                                      title                   zone_slug operator_name market_verdict
0  St. Oberholz Coworking Berlin Mitte – Rosenthaler Straße                       mitte  St. Oberholz         around
1                      smartspaces Coworking Berlin-Spandau                     spandau   smartspaces         around
2                                      Regus Charlottenburg  charlottenburg-wilmersdorf         Regus           <NA>

>>> s = cwv.space("sankt-oberholz-rosenthaler-berlin")
>>> s.pricing[["product", "title", "price", "currency", "billing_period", "price_on_request"]]
          product                             title  price currency billing_period  price_on_request
0       flex_desk  Coworking Membership – Flex Desk  209.0      EUR          month             False
1        fix_desk                        Fixed Desk   <NA>     <NA>          month              True
2  private_office                 Private Team Room   <NA>     <NA>          month              True
3    meeting_room           Meeting & Workshop Room   <NA>     <NA>           once              True
4  virtual_office   Virtual Office with St.Dropscan   69.0      EUR          month             False

(Figures are illustrative and change as the index is recomputed; as_of tells you when.)

Every column has a nullable pandas dtype (string, Float64, Int64, boolean) and timestamps are timezone-aware UTC, so missing values are <NA>, never a silent NaN in an integer column.

Reference

Accepted values, validated before any request is sent (a typo raises ValueError listing the valid values):

parameter values
locale "en" (default), "es", "de"
currency "EUR", "GBP", "USD", "CHF", "AED", "VES" (case-insensitive; the API converts at an approximate rate and answers 400 where it has none)
product "flex_desk", "fix_desk", "private_office", "exclusive_bereich", "day_pass", "meeting_room", "event_space", "virtual_office", "membership"
sort "random" (default), "price-asc", "date-desc", "date-asc"

rates(city=None, country=None, currency=None, locale="en")

The Price Index: one row per published city and product.

parameter type description
city str City slug, e.g. "berlin", "madrid", "abu-dhabi"
country str ISO 3166-1 alpha-2 code, e.g. "DE"
currency str Present prices in this currency (default: each city's own)
locale str Language of city names
column dtype description
city_slug, city_name string City identifier and display name
country_code string ISO 3166-1 alpha-2
currency string Currency of the figures
product string flex_desk, fix_desk, private_office, day_pass, meeting_room, ...
unit string month, day (day pass) or hour (meeting and event rooms)
median, p25, p75 Float64 Price statistics (p25/p75 are <NA> when not published)
sample_size Int64 Number of prices behind the figure
property_count, operator_count Int64 Spaces and operators behind the figure
low_confidence boolean True when sample_size < 10; treat as indicative
as_of datetime64[ns, UTC] When the index was computed

Rows are sorted by city, then product (flex desk, fixed desk, private office, ...). Aggregates the API adds later (say, event spaces) appear as new rows without a package release. df.attrs holds attribution, license (licence URL) and as_of.

cities(city=None, country=None, currency=None, locale="en")

One row per city, same parameters as rates().

column dtype description
city_slug, city_name, country_code, currency string As in rates()
published boolean False for cities with too few prices for an index yet
property_count, operator_count Int64 Spaces and operators in the city
entry_median, entry_min, entry_max Float64 Cheapest monthly option per space: median, min, max
p25, p75 Float64 Quartiles of those entry prices
sample_size Int64 Number of spaces with a monthly price (<NA> if unpublished)
as_of datetime64[ns, UTC] When the index was computed

Published cities come first, then by slug. Same df.attrs as rates().

spaces(city=None, country=None, zone=None, operator=None, product=None, min_capacity=None, min_price=None, max_price=None, currency=None, sort="random", limit=50, locale="en")

Search the catalogue, one row per space.

parameter type description
city, country str City slug, ISO country code
zone str Neighbourhood slug, e.g. "mitte"
operator str Operator slug, e.g. "regus"
product str Only spaces offering this product (see accepted values)
min_capacity int Options for at least this many people
min_price, max_price float Price bounds (in currency when given)
currency str Currency of the price bounds
sort str "random" is stable across the pages of one call
limit int 1 to 200 (default 50); see limits
locale str Language of titles and of url
column dtype description
slug, title string Identifier and name of the space
city_slug, zone_slug string City and neighbourhood
operator_slug, operator_name string Operator
country_code string ISO 3166-1 alpha-2
lat, lng Float64 WGS 84 coordinates
featured boolean Featured listing
market_verdict string below, around or above the city median
market_median, market_sample_size Float64, Int64 The city median it compares against
url string Listing page on coworkingview.com
updated_at datetime64[ns, UTC] Last update of the listing

df.attrs holds attribution (the citation), notice (the catalogue notice) and total (how many spaces matched, which can be more than limit).

space(slug, locale="en")

One space, by its slug (letters, digits, ., _, -; anything else raises ValueError before a request is made), returned as a frozen dataclass Space with the spaces() fields plus address, capacity and amenities (a tuple of codes such as "wifi"), and:

attribute type description
pricing DataFrame One row per pricing option (below)
attribution str Citation to show next to the data
notice str The catalogue notice
pricing column dtype description
product string Product type
title string The operator's name for the option
price, currency Float64, string <NA> when the price is on request
billing_period string month, day, hour, once, ...
price_on_request boolean No public price
min_capacity, max_capacity Int64 People, when published

Client(base_url=None, timeout=20, user_agent=None)

The functions above use a shared default client. Create your own to change the configuration; it has the same four methods.

from coworkingview import Client

client = Client(timeout=5, user_agent="my-research-project/1.0 (me@example.org)")
client.rates(country="ES", currency="EUR")

base_url defaults to the COWORKINGVIEW_API_URL environment variable, else https://api.coworkingview.com. Nothing touches the network at import time.

Errors

exception raised when
ValueError An argument is invalid (before any request)
coworkingview.APIError The API answered with an error, or with a body that is not JSON (code == "BAD_RESPONSE")
coworkingview.CoworkingViewError Base class; raised directly when the API cannot be reached

APIError carries the API's problem document: status, code, title, detail and errors (per-field messages).

try:
    cwv.rates(city="berlin", currency="VES")
except cwv.APIError as err:
    print(err.status, err.code, err.detail)
# 400 VALIDATION_FAILED currency "VES" has no approximate rate from "EUR" ...

On 429 Too Many Requests or a 5xx, the client waits (Retry-After, else RateLimit-Reset, else 2 s; never more than 10 s) and retries once.

Limits and good citizenship

  • The API allows 120 requests per minute per IP and answers are cacheable for 10 minutes. Cache what you reuse.
  • spaces() returns at most 200 spaces per call, and asking for more raises a ValueError. That is deliberate: the catalogue may be searched and linked to, but not bulk-copied. Please do not loop over cities or operators to rebuild it.
  • No descriptions, photos or logos are returned. They belong to the operators.

Attribution and licence

  • Code: MIT, see LICENSE.
  • Price Index data (rates(), cities()): CC BY 4.0. You may share and adapt it, including commercially, if you credit the source. Every result carries the credit line in df.attrs["attribution"]; publish it next to any chart, table or article that uses the figures. Methodology and the interactive index: CoworkingView Price Index.
  • Catalogue content (spaces(), space()): names, descriptions, images, logos and prices of individual spaces belong to their operators and are not licensed for reuse. You may search it and link to the listing url; df.attrs["notice"] carries the exact terms.

Development

uv sync                                   # create .venv with dev tools
uv run ruff format --check . && uv run ruff check .
uv run mypy                               # --strict
uv run pytest --cov                       # offline; fails under 90 % coverage
COWORKINGVIEW_LIVE_TESTS=1 uv run pytest -m live   # 3 real requests, opt-in

The parsing is tested row for row against the shared contract in spec/, which the R and Rust packages implement too.

Metadata

Release files for coworkingview 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for coworkingview 0.1.0
File Size Uploaded Provenance Actions
coworkingview-0.1.0.tar.gz 39.6 kB Without provenance Details for coworkingview-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for coworkingview 0.1.0
File Interpreter ABI Platform Actions
coworkingview-0.1.0-py3-none-any.whl Python 3 none any Details for coworkingview-0.1.0-py3-none-any.whl

Total release size: 64.5 kB

Release files / coworkingview-0.1.0.tar.gz

Download URL coworkingview-0.1.0.tar.gz
Size 39.6 kB
Tags Source
SHA-256 checksum
How to use checksums
479947f65341958a7d0406a1868958868599c38bc986f84cad34eefde7bfd522
BLAKE2b-256 checksum
How to use checksums
f943fae7e95c5fb853a966560bdc8fe193ab5c2483c275db636fc35e0b748f6c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.15

Release files / coworkingview-0.1.0-py3-none-any.whl

Download URL coworkingview-0.1.0-py3-none-any.whl
Size 24.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
508f58b937326e2d8834f4b98b6cbc7b57decb75d227a3a4d3e996f088cab7b0
BLAKE2b-256 checksum
How to use checksums
c37db1b93ff3e2ac2839baf52af8efdd61a92dccbe767aaf77dee638cc91c621
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.15

Security for coworkingview 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page