# Real-Time Bike Availability: How to Display It in an Application?

> Display bike availability with the ROOTE API: timestamping, freshness, unknown values, refreshing, and expired data within your application.

Source: https://www.roote.ai/en/guides/real-time-bike-availability-display/
Language: en
Author: ROOTE

To display real-time bike availability in an application, associate the returned number with its freshness and vehicle pickup possibilities. An observation describes what the source knew at a moment in time; it does not guarantee that a bike will still be there upon arrival.

The ROOTE mobility contract distinguishes stations, individual vehicles, and their states. The interface must reflect these differences without confusing an unknown value, an empty station, and a temporarily unavailable source.

## Separating a station from an individual vehicle

A station can expose counters for bikes and return docks. An individual vehicle has an availability state and possibly other information. Avoid counting a station as a bike or summing counters representing the same inventory.

In the ROOTE mobility DTO, availability.bikes and availability.docks can be unknown. Propulsion or battery fields should only be displayed if they exist and are interpreted according to the contract.

[ROOTE OpenAPI contract](https://api.roote.ai/openapi.json)

## Read freshness and timestamps

| Field | Interpretation |
| --- | --- |
| freshness.state | Reported state: fresh, stale, unknown, or static |
| freshness.source_updated_at | Source update date, if known |
| freshness.received_at | Reception date as indicated by the contract |
| freshness.expires_at | Validity expiration date indicated, if known |
| availability.bikes | Known quantity or unknown value |
| pickup.enabled and pickup.state | Information about picking up a bike at the station |

The time of your call is not automatically the observation time. A result received at 10:00 may contain a source updated at 9:45. Do not display “updated now” based solely on your interface's reception time.

## Plan for distinct display states

| Data received | Display to expect |
| --- | --- |
| Known quantity and fresh data | Observed quantity and temporal indication |
| Quantity equal to zero | No bike observed, with its temporal context |
| Quantity null | Unknown availability |
| Stale state or expired validity | Old data; suggest a refresh |
| pickup.enabled=false | Pickup unavailable even if a counter is positive |
| Search error | Temporary availability unavailable, without converting to zero |

Do not classify an unknown or static state as fresh. Station information may be stable while counters evolve quickly. Also retain warnings and attributions required by the response.

## An example of normalization before rendering

The following function produces a presentation state from a station already validated against the ROOTE schema. It is not a complete response validator. Visible labels should come from your interface’s translation keys.

```
function availabilityView(station, now = Date.now()) {
  const freshness = station.freshness;
  const expiresAt = freshness.expires_at
    ? Date.parse(freshness.expires_at) : null;
  const expired = expiresAt !== null &&
    Number.isFinite(expiresAt) && expiresAt <= now;
  if (station.pickup.enabled === false ||
      station.pickup.state === 'unavailable_now') {
    return { state: 'pickup_unavailable', count: null };
  }
  if (expired || freshness.state === 'stale') {
    return { state: 'stale', count: null };
  }
  const count = station.availability.bikes;
  if (freshness.state !== 'fresh' || count === null ||
      !Number.isFinite(count) || count < 0) {
    return { state: 'unknown', count: null };
  }
  return {
    state: count === 0 ? 'empty' : 'observed', count,
    sourceUpdatedAt: freshness.source_updated_at,
    receivedAt: freshness.received_at,
    pickupState: station.pickup.state
  };
}
```

Even with an observed state, do not transform pickupState=unknown into confirmed pickup. The counter remains an observation. Display pickup context if your product helps the user choose a station.

## Refresh without unnecessarily multiplying calls

Adapt refreshing to the published expiration, service conditions, and user behavior. Group identical requests, avoid background calls on an inactive page, and cancel those from a replaced search.

A local cache delay does not prove source freshness. After an error, you may keep a last dated observation if your interface explicitly presents it as old. Do not erase this distinction at the first successful retry if the source remains stale.

## Understanding the link with GBFS

GBFS describes shared mobility services and their published states. A direct integration must interpret files, version, and feed timestamps. With a standardized API, use the API contract; do not add an assumed GBFS field missing from its response.

[Choosing between GTFS, GTFS Realtime, and GBFS](https://www.roote.ai/en/guides/gtfs-gtfs-realtime-and-gbfs-differences/)

## Testing scenarios misleading the reader

Test a real zero, an unknown value, an expired validity, a disabled pickup, and an error after a valid result. Also verify display time zones. A positive counter must never produce 'bike reserved' and an unavailability must never produce an invented zero.

[Handling empty responses and errors](https://www.roote.ai/en/guides/no-result-or-api-error-how-to-tell-the-difference/)

[Applying these rules in an AI assistant](https://www.roote.ai/en/guides/how-to-create-an-assistant-that-finds-mobility-near-an-address/)

[User guide to finding a bike](https://www.roote.ai/en/guides/find-bike-sharing-near-you/)

## Frequently asked questions

### Does a positive quantity guarantee a bike when I arrive?

No. It describes an observation, which may change between the search and your arrival.

### Can null be replaced by zero?

No. null indicates an unknown value; zero is a known quantity and carries a different meaning.

### Should I refresh every few seconds?

Use validity indications, service limits, and your interface’s needs. An arbitrary frequency does not guarantee a fresher source.
