API reference

Client

class closecity.Client(api_key: str | None = None, *, base_url: str = 'https://api.close.city', timeout: float = 30.0, output: str = 'spatial', http_client: httpx.Client | None = None, transport: httpx.BaseTransport | None = None)[source]

Client for the Close API.

api_key is optional (the catalog and health routes are free), but every data route needs one (a ck_live_ key), created at https://account.close.city. Usable as a context manager.

output sets how results come back, and defaults to "spatial":

  • "spatial" returns a geopandas.GeoDataFrame for routes with geometry (points, isochrone and block polygons) and a plain pandas.DataFrame for the rest;

  • "tabular" returns a plain pandas.DataFrame for every route and never downloads block boundaries (the cheap path when you only want the numbers);

  • "raw" returns the underlying Reply / Paginator.

Set it on the client (close.output = "raw") or per call (close.blocks_query(..., output = "tabular")).

health() Reply[source]

Liveness check (free). Touches no database. Always a raw Reply.

last_updated() Reply[source]

Publication timestamp of the newest published data (free). Always a raw Reply.

modes(*, output: str | None = None)[source]

The travel modes and their numeric ids (free). A DataFrame of mode_id, mode, description, or a raw Reply when output="raw".

destination_types(*, output: str | None = None)[source]

The destination-type taxonomy with leaf expansions (free). A DataFrame of dest_type_id, name, label, is_leaf, leaf_ids, or a raw Reply when output="raw". Filtering by a parent type expands to its leaf_ids.

vintage(*, output: str | None = None)[source]

The active version of each dataset component (free). A DataFrame of component, version, effective_date, or a raw Reply.

places(q: str, *, limit: int | None = None, output: str | None = None)[source]

Search census places by name (free). Each match carries the place’s GEOID and WGS84 centroid. Feed a centre into blocks_query(center=...), or a GEOID into place_blocks(...). q is a name substring; limit caps the matches (1 to 20). A points GeoDataFrame (or a plain DataFrame under output="tabular", a raw Reply under output="raw").

block_summary(geoid: str, *, mode=None, type=None, if_none_match: str | None = None, output: str | None = None)[source]

Fastest travel time to each destination category from a census block, by mode. geoid is a 15-digit block GEOID; mode/type are optional filters (scalar or list). Supports if_none_match (ETag/304). A DataFrame (the origin GEOID broadcast to a geoid column), or a raw Reply.

block_pois(geoid: str, *, mode=None, type=None, dest_id=None, max_minutes=None, limit=None, output: str | None = None)[source]

Every nearby POI and its travel time from a block, one row per (POI, mode). A GeoDataFrame of points (a plain DataFrame under output="tabular", a Paginator under output="raw").

point_summary(lat: float, lon: float, *, mode=None, type=None, if_none_match: str | None = None, output: str | None = None)[source]

Like block_summary, but from the census block containing a lat/lon. The resolved block GEOID is echoed as resolved_block and broadcast to a geoid column. A DataFrame, or a raw Reply.

point_pois(lat: float, lon: float, *, mode=None, type=None, dest_id=None, max_minutes=None, limit=None, output: str | None = None)[source]

Like block_pois, but from the block containing a lat/lon. A GeoDataFrame of points (a plain DataFrame under output="tabular", a Paginator under output="raw").

Search POIs by bounding box (bbox) or radius (lat + lon + radius_m). A GeoDataFrame of points (a plain DataFrame under output="tabular", a Paginator under output="raw").

poi(dest_id: int, *, if_none_match: str | None = None, output: str | None = None)[source]

Name, location, address, types, and whitelisted attributes for one POI. A one-row points GeoDataFrame (a plain DataFrame under output="tabular", a Reply under output="raw").

poi_catchment(dest_id: int, *, mode=None, block=None, max_minutes=None, limit=None, output: str | None = None)[source]

Every census block that can reach a POI, one row per (block, mode). A GeoDataFrame of block polygons (a plain DataFrame under output="tabular", a Paginator under output="raw").

blocks_query(*, polygon: dict | None = None, center: dict | None = None, radius_m: float | None = None, type: Sequence[int] | None = None, mode: Sequence[str] | None = None, include_population: bool | None = None, limit: int | None = None, output: str | None = None)[source]

Blocks within a GeoJSON polygon or a center + radius_m. Rows carry the numeric mode_id (join modes() to label it). A GeoDataFrame of block polygons (a plain DataFrame under output="tabular", a Paginator under output="raw").

place_blocks(geoid: str, *, mode=None, type=None, include_population=None, limit=None, output: str | None = None)[source]

Per-block travel times for every census block in a place (city or town), by place GEOID. Rows carry the numeric mode_id (join modes() to label it). A GeoDataFrame of block polygons (a plain DataFrame under output="tabular", a Paginator under output="raw").

isochrone(*, block=None, lon=None, lat=None, mode=None, direction=None, minutes=None, contours=None, format=None, v=None, if_none_match: str | None = None, output: str | None = None)[source]

Travel-time contours from a block (GEOID) or lon + lat. Pass minutes (single) or contours (up to 4 ascending levels, a list or a comma string). In spatial mode format="geojson" gives contour polygons and format="blocks" gives block polygons; tabular gives the matching rows; raw gives a Reply.

isochrone_meta(*, if_none_match: str | None = None) Reply[source]

The active isochrone store version, available directions/modes, and the routing assumptions (free, keyless). Always a raw Reply.

Replies and pagination

class closecity.Reply(data: Any, status: int, tokens_charged: int | None = None, tokens_remaining: int | None = None, etag: str | None = None, request_id: str | None = None, not_modified: bool = False)[source]

One API response, with the metering + caching metadata exposed.

data is the parsed JSON body (a dict for every route). For a 304 revalidation not_modified is True and data is None. tokens_charged / tokens_remaining are None on free and member-unmetered responses.

property results: list[dict]

The results array of a list endpoint (empty if absent).

to_geopandas(**kwargs)[source]

Convert this reply to a geopandas.GeoDataFrame.

Points for POI replies, polygons for isochrone format=geojson. Block replies need a block_geometry= GeoDataFrame or fetch=True (which downloads TIGER blocks with pygris). See closecity.spatial.to_geopandas().

to_pandas(**kwargs)[source]

Convert this reply to a pandas.DataFrame (no geometry). See closecity.tabular.to_pandas().

class closecity.Paginator(_client: Client, _method: str, _path: str, _params: dict[str, ~typing.Any]=<factory>, _json: dict[str, ~typing.Any] | None=None, _cursor_in: str = 'params')[source]

Lazily follows next_cursor across pages.

Iterate it directly to get every record; use pages() for per-page Reply objects (each carrying its own token/ETag metadata); use page() to fetch a single page.

to_geopandas(**kwargs)[source]

Collect every page and convert the rows to a geopandas.GeoDataFrame. See closecity.spatial.to_geopandas().

to_pandas(**kwargs)[source]

Collect every page and convert the rows to a pandas.DataFrame. See closecity.tabular.to_pandas().

Output conversion

closecity.to_pandas(source: Any, *, key: str | None = None) DataFrame[source]

Convert a Close API reply into a pandas.DataFrame.

source is a Reply, a Paginator (all pages are collected), or a raw response body. key names the record array explicitly; when omitted it is detected from the body. Metering and envelope metadata land on df.attrs.

closecity.to_geopandas(source: Any, *, block_geometry: Any = None, geoid_col: str = 'GEOID20', crs: Any = 'EPSG:4326', fetch: bool = False)[source]

Convert a Close API reply into a geopandas.GeoDataFrame.

source is a Reply, a Paginator (all pages are collected), or a raw response body. The geometry kind is detected from the payload; see the module docstring. block_geometry / geoid_col / fetch only apply to block replies. Metering and envelope metadata land on .attrs.

Errors

Exceptions mapped from the API’s RFC 9457 problem+json responses.

Every error the API returns has a stable type URI whose final path segment is the slug (e.g. tokens-exhausted). We map on that slug, falling back to the HTTP status, so callers can catch a precise class or the CloseAPIError base.

exception closecity.errors.CloseError[source]

Bases: Exception

Base class for every error raised by this client.

exception closecity.errors.CloseAPIError(*, status: int, slug: str, title: str, detail: str | None = None, request_id: str | None = None, retry_after: float | None = None, extras: dict[str, Any] | None = None)[source]

Bases: CloseError

An error response from the API (a problem+json body).

Attributes mirror the problem document: slug (the stable machine key), title, status, detail, plus request_id (from X-Request-Id, quote it in support requests), retry_after when present, and extras for any additional problem members (e.g. validation errors).

exception closecity.errors.BadRequestError(*, status: int, slug: str, title: str, detail: str | None = None, request_id: str | None = None, retry_after: float | None = None, extras: dict[str, Any] | None = None)[source]

Bases: CloseAPIError

400: invalid parameters, cursor, geometry, bbox, etc.

exception closecity.errors.AuthenticationError(*, status: int, slug: str, title: str, detail: str | None = None, request_id: str | None = None, retry_after: float | None = None, extras: dict[str, Any] | None = None)[source]

Bases: CloseAPIError

401: missing or invalid API key.

exception closecity.errors.PermissionDeniedError(*, status: int, slug: str, title: str, detail: str | None = None, request_id: str | None = None, retry_after: float | None = None, extras: dict[str, Any] | None = None)[source]

Bases: CloseAPIError

403: the key’s account is disabled (or membership is required).

exception closecity.errors.NotFoundError(*, status: int, slug: str, title: str, detail: str | None = None, request_id: str | None = None, retry_after: float | None = None, extras: dict[str, Any] | None = None)[source]

Bases: CloseAPIError

404: the block, POI, place, or point is not in the published data.

exception closecity.errors.RateLimitedError(*, status: int, slug: str, title: str, detail: str | None = None, request_id: str | None = None, retry_after: float | None = None, extras: dict[str, Any] | None = None)[source]

Bases: CloseAPIError

429 rate-limited: too many requests; see retry_after.

exception closecity.errors.TokensExhaustedError(*, status: int, slug: str, title: str, detail: str | None = None, request_id: str | None = None, retry_after: float | None = None, extras: dict[str, Any] | None = None)[source]

Bases: CloseAPIError

429 tokens-exhausted: the account’s token balance is zero.

exception closecity.errors.ServiceUnavailableError(*, status: int, slug: str, title: str, detail: str | None = None, request_id: str | None = None, retry_after: float | None = None, extras: dict[str, Any] | None = None)[source]

Bases: CloseAPIError

503: a backend is briefly unavailable; the request was not charged.

closecity.errors.error_from_problem(status: int, body: dict[str, Any], request_id: str | None, retry_after: float | None) CloseAPIError[source]

Build the most specific exception for a problem+json body.