# Bike Share Systems & Live Stations Scraper (GBFS) (`ninhothedev/bike-share-gbfs-scraper`) Actor

$0.5/1K 🔥 Bike share scraper! Official GBFS feeds with live station availability, pricing & vehicle types. No key. JSON, CSV, Excel or API in seconds. Power mobility dashboards ⚡

- **URL**: https://apify.com/ninhothedev/bike-share-gbfs-scraper.md
- **Developed by:** [ninhothedev](https://apify.com/ninhothedev) (community)
- **Categories:** Travel, Automation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.50 / 1,000 results

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

## Bike Share Systems & Live Stations Scraper (GBFS)

Scrape **official GBFS feeds** from **1,500+ bike-share and micromobility systems worldwide** — the system catalogue, **real-time station availability** (bikes, docks, occupancy, vehicle types) and **pricing plans**.

This actor reads the **General Bikeshare Feed Specification (GBFS)** feeds that operators publish themselves — the industry standard behind Citi Bike, Capital Bikeshare, Bicing, Vélib', nextbike, Call a Bike, BIXI, Oslo Bysykkel, Divvy, Bay Wheels and hundreds more. No API key. No login. No aggregator sitting in the middle.

***

### Why this actor (and how it differs from the CityBikes scraper)

There are two ways to get bike-share data, and they are **not** the same thing.

| | **This actor — `bike-share-gbfs-scraper`** | [`citybikes-scraper`](https://apify.com/ninhothedev/citybikes-scraper) |
|---|---|---|
| **Data source** | The **official GBFS feeds published by the operator** (auto-discovery `gbfs.json` → `station_status`, `system_pricing_plans`, …) | The **CityBikes aggregator API**, a third-party community project that re-publishes a normalised subset |
| **Coverage** | 1,500+ systems in the MobilityData catalogue, including every system that ships a GBFS feed | Only what CityBikes has onboarded and keeps in sync |
| **Freshness** | Straight from the operator, `ttl` as low as 10 s | As fresh as the aggregator's own polling |
| **Pricing plans** | ✅ `system_pricing_plans` (currency, price, taxable, description) | ❌ not exposed |
| **Vehicle types** | ✅ per-station e-bike / cargo / classic breakdown | ❌ only a total bike count |
| **Operator metadata** | ✅ operator, timezone, phone, e-mail, purchase URL, licence, start date, published feed list | Limited |
| **Spec fidelity** | GBFS v1, v2 **and** v3 (incl. the v3 field renames) | Aggregator's own flat schema |

**Rule of thumb:** use `citybikes-scraper` when you want one quick, uniform bike-count feed. Use **this** actor when you need the authoritative operator data — the systems CityBikes misses, plus vehicle types, pricing and full real-time dock status. Many teams run both and reconcile.

***

### What you get

#### Mode `systems` — the catalogue

Every bike-share system in the MobilityData GBFS registry with operator metadata and a live station count.

```json
{
  "type": "system",
  "system_id": "bike_barcelona",
  "name": "Bike Barcelona",
  "operator": null,
  "country_code": "ES",
  "location": "Barcelona",
  "timezone": "Europe/Madrid",
  "url": "https://www.bicing.barcelona/",
  "email": "mobility-data-client@lyft.com",
  "gbfs_version": "3.0",
  "auto_discovery_url": "https://barcelona.publicbikesystem.net/customer/gbfs/v3.0/gbfs.json",
  "feed_names": ["station_information", "station_status", "system_information", "system_pricing_plans", "vehicle_types", "..."],
  "station_count": 542,
  "source": "gbfs",
  "scraped_at": "2026-07-28T18:11:52+00:00"
}
```

#### Mode `stations` — live availability

`station_information` **joined with** `station_status` on `station_id`, so every row carries both the static location and the real-time counts.

```json
{
  "type": "station",
  "system_id": "oslobysykkel",
  "system_name": "Oslo Bysykkel",
  "station_id": "YOS:Station:6026",
  "name": "Østbanehallen",
  "address": "Jernbanetorget 1",
  "latitude": 59.9104991,
  "longitude": 10.7505055,
  "capacity": 35,
  "is_virtual_station": false,
  "bikes_available": 7,
  "bikes_disabled": null,
  "docks_available": 22,
  "docks_disabled": null,
  "is_installed": true,
  "is_renting": true,
  "is_returning": true,
  "occupancy_pct": 20.0,
  "vehicle_types_available": [
    { "vehicle_type_id": "YOS:VehicleType:bike", "count": 7 },
    { "vehicle_type_id": "YOS:VehicleType:ebike", "count": 0 }
  ],
  "last_reported": "2026-07-28T18:03:48+00:00",
  "source": "gbfs",
  "scraped_at": "2026-07-28T18:12:34+00:00"
}
```

#### Mode `pricing` — pricing plans

```json
{
  "type": "pricing_plan",
  "system_id": "nextbike_wr",
  "system_name": "WienMobil Rad",
  "plan_id": "Rate0571",
  "name": "Pay as you go: € 0,75 / 30 min",
  "currency": "EUR",
  "price": 0,
  "is_taxable": false,
  "description": "EUR 0.75/30min, EUR 19.00/24h",
  "source": "gbfs",
  "scraped_at": "2026-07-28T18:13:39+00:00"
}
```

***

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `mode` | select | `systems` | `systems` (catalogue + metadata), `stations` (live availability), `pricing` (pricing plans) |
| `systemIds` | array | 5 verified IDs | GBFS system IDs exactly as in `systems.csv`. **Leave empty to sweep the whole 1,500+ catalogue.** |
| `countryFilter` | string | – | Optional 2-letter ISO code, e.g. `DE`, `US`, `FR` |
| `maxItems` | integer | `500` (max `5000`) | Row cap. In `stations`/`pricing` the budget is split **fairly** across the selected systems |

The five default system IDs are all verified live: `bike_barcelona` (GBFS 3.0), `lyft_nyc` (Citi Bike, 2.3), `cabi` (Capital Bikeshare, 2.3), `nextbike_wr` (WienMobil Rad, 2.3), `oslobysykkel` (3.0).

```json
{
  "mode": "stations",
  "systemIds": ["bike_barcelona", "lyft_nyc", "cabi", "nextbike_wr", "oslobysykkel"],
  "maxItems": 1000
}
```

Sweep an entire country instead:

```json
{ "mode": "systems", "systemIds": [], "countryFilter": "DE", "maxItems": 300 }
```

***

### Use cases

- **Mobility research** — build longitudinal datasets on shared-micromobility supply, fleet mix (e-bike vs classic) and system growth across cities and countries.
- **City dashboards** — power a live map of every dock in your city with bikes, free docks and occupancy percentage, refreshed as often as the operator's `ttl` allows.
- **Travel apps** — show travellers whether there are bikes at the station next to their hotel, in any of 1,500+ systems, without integrating 1,500 different APIs.
- **Rebalancing analytics** — poll `stations` on a schedule and detect the docks that run empty or jam full, the classic input for fleet-rebalancing and demand models.

Also good for: transport-planning consultancies, urban-mobility startups doing competitive coverage analysis, academic transport labs, insurance and fleet pricing, and open-data portals.

***

### Pricing

Pay-per-result: **~$0.5 per 1,000 results**. A default `stations` run over the five verified systems (~5,300 stations) lands around **$2.65**. A `systems` sweep of one country is usually well under a dollar.

***

### How it works

1. Download the MobilityData catalogue `systems.csv` (verified header: `Country Code, Name, Location, System ID, URL, Auto-Discovery URL, Supported Versions, Authentication Info URL, Authentication Type, Authentication Parameter Name`).
2. Filter by `systemIds` / `countryFilter`; systems whose feeds require an API key are flagged and skipped.
3. For each system, fetch the auto-discovery `gbfs.json` and resolve the feed list with a **tolerant resolver** that handles both real-world shapes: `data.feeds` directly (GBFS v3) and `data.<lang>.feeds` where the language key may be `en`, `de`, `fr`, `nb`, … (GBFS v1/v2).
4. Fetch the feeds the mode needs, normalise and push in batches of 50.

**Versions handled in the wild:** GBFS `1.1`, `2.2`, `2.3`, `3.0`, `3.1-RC3` and a handful of v1.0 producers that omit the `version` field entirely (Vélib' Métropole). The actor normalises the v3 renames — `num_vehicles_available` → `bikes_available`, `num_vehicles_disabled` → `bikes_disabled` — and flattens the v3 localised `name` / `short_name` / `operator` / `description` lists (`[{"text": …, "language": …}]`) back into plain strings.

**Resilience.** Feeds are hosted by the operators themselves, so some are always down. A failing system produces a **warning and is skipped** — the run continues. The actor only raises if **zero** items were produced across **all** selected systems.

**Nulls are honest.** A station whose `station_status` feed is missing gets `null` availability, never a misleading `0`. `occupancy_pct` is `null` when capacity is `0` or unknown.

***

### Related actors

- [CityBikes Bike Sharing Scraper](https://apify.com/ninhothedev/citybikes-scraper) — the aggregator-based counterpart described above
- [Public Transit Scraper](https://apify.com/ninhothedev/public-transit-scraper) — stops, routes and departures for public transport networks
- [EV Charging Stations Scraper](https://apify.com/ninhothedev/ev-charging-scraper) — charge points, connectors and live status
- [Airports & Airlines Scraper](https://apify.com/ninhothedev/airports-airlines-scraper) — airport, airline and route reference data

***

### Legal

GBFS feeds are published by operators as **open data**, usually under CC0 or a comparable open licence, precisely so that third parties can build on them. This actor reads those public endpoints, sends no credentials and collects **no personal data** — only infrastructure and fleet information. Check the individual system's `license_url` before redistributing its data commercially.

***

### Support

Found a system that will not resolve, or want another GBFS feed (`free_bike_status`/`vehicle_status`, `geofencing_zones`, `system_alerts`) added? Open an issue on the actor page.

# Actor input Schema

## `mode` (type: `string`):

What to scrape. 'systems' returns the GBFS catalogue with per-system metadata (operator, timezone, contact, published feeds, station count). 'stations' returns live station data - station\_information joined with station\_status for real-time bikes/docks availability. 'pricing' returns the system\_pricing\_plans of each selected system.

## `systemIds` (type: `array`):

GBFS system IDs to scrape, exactly as listed in the MobilityData catalogue (systems.csv), e.g. 'lyft\_nyc' for Citi Bike or 'bike\_barcelona' for Bicing. The defaults are five systems verified to be live and cover both GBFS v2 and v3. Leave the list EMPTY to process every system in the catalogue (1500+, use maxItems and countryFilter to keep runs small).

## `countryFilter` (type: `string`):

Optional two-letter ISO country code to restrict the catalogue to one country, e.g. 'DE' for Germany, 'US' for the United States, 'FR' for France. Combine with an empty System IDs list to sweep a whole country.

## `maxItems` (type: `integer`):

Maximum number of dataset rows to push before the run stops. One row is one system, one station or one pricing plan depending on the mode.

## Actor input object example

```json
{
  "mode": "systems",
  "systemIds": [
    "bike_barcelona",
    "lyft_nyc",
    "cabi",
    "nextbike_wr",
    "oslobysykkel"
  ],
  "countryFilter": "DE",
  "maxItems": 500
}
```

# 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 = {
    "systemIds": [
        "bike_barcelona",
        "lyft_nyc",
        "cabi",
        "nextbike_wr",
        "oslobysykkel"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("ninhothedev/bike-share-gbfs-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 = { "systemIds": [
        "bike_barcelona",
        "lyft_nyc",
        "cabi",
        "nextbike_wr",
        "oslobysykkel",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("ninhothedev/bike-share-gbfs-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "systemIds": [
    "bike_barcelona",
    "lyft_nyc",
    "cabi",
    "nextbike_wr",
    "oslobysykkel"
  ]
}' |
apify call ninhothedev/bike-share-gbfs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=ninhothedev/bike-share-gbfs-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/8ic5jzPUNs0TbeoAA/builds/hJajKITA6FrPYTZ2X/openapi.json
