-
Notifications
You must be signed in to change notification settings - Fork 89
stop
A rider's client application retrieves the full static record for a single transit stop — its location, name, and the routes that serve it — so the rider can confirm stop details or build a departure board.
OneBusAway REST API, endpoint GET /api/where/stop/{id}.json
User goal
Rider (accessing the system via a client application)
- Rider — wants accurate stop details (location, name, routes) to plan a journey or check whether a stop is wheelchair-accessible.
- The server has a loaded transit data bundle.
- The caller knows the combined stop ID in the form
{agencyId}_{entityId}(e.g.1_11870).
- If the request cannot be satisfied, the response body is
nullwith HTTP status 200. (This is a defect; see Suspected Defects.)
- The response contains the stop's geographic coordinates, human-readable name, stop code, direction, location type, wheelchair boarding annotation, the list of routes currently serving the stop, and — when the stop is part of a station — the combined ID of its parent station.
- All referenced routes and agencies appear in the references block.
- If the stop is a sub-platform of a parent station, the parent station's full record also appears in the references block.
Client sends GET /api/where/stop/{id}.json?key={key}.
- The client supplies a combined stop ID in the URL path (e.g.
/api/where/stop/1_11870.json). - The system parses the ID by splitting on the first underscore: everything before it is the agency ID; everything after is the stop's entity ID. (
AgencyAndIdLibrary.convertFromString) - The system looks up the stop in the transit graph. If the ID matches a consolidated stop alias, the canonical ID is substituted transparently. (
StopBeanServiceImpl.getStopForIdForServiceDate) - The system retrieves the stop's geographic coordinates (latitude and longitude) from the transit graph and its narrative attributes (name, code, direction, location type) from the narrative store. The code falls back to the raw entity portion of the combined ID if the GTFS feed supplies no
stop_code. (fillStopBean) - The system retrieves every route collection that serves this stop, drawing from the full set of service dates without filtering to any particular date. Routes are sorted by name in natural string order (treating embedded digit runs as numbers). (
fillRoutesForStopBeanwith null service interval) - The system resolves the static route list for the stop. If the narrative store holds a configured static route list, that list is used; otherwise the static route list is identical to the route list from step 5. (
fillTransfersForStopBean) - If the stop has a parent station, the system resolves the parent station's data (coordinates, narrative, routes) and adds the parent's combined ID to the entry's
parentfield. The full parent record is placed in thestopsarray of the references block. (BeanFactoryV2.getStopparent handling) - The system assembles the response: the entry for the stop, and — unless
includeReferences=falsewas requested — a references block containing the full records for all routes inrouteIdsand their owning agencies. - The system returns HTTP 200 with the assembled JSON response.
1a. The stop ID does not contain an underscore:
- Parsing fails with an internal error.
- The exception interceptor attempts to return HTTP 500, but due to a defect (see Suspected Defects) the actual HTTP response is 200 with body
null.
2a. No stop exists for the supplied ID (and it is not a consolidated alias):
- The service throws a not-found exception.
- The exception interceptor attempts to return HTTP 404, but due to a defect (see Suspected Defects) the actual HTTP response is 200 with body
null.
3a. includeReferences=false is supplied:
- The references block is present in the response but all arrays within it are empty. Route and agency records are omitted.
1. Error responses return HTTP 200 with a null body.
ExceptionInterceptor (lines 55–64) catches all unhandled exceptions (including not-found and internal errors) and constructs an error response bean with the appropriate status code (404 or 500). It sets that code on a DefaultHttpHeaders instance and passes both to the content-type handler. However, the status code set on the DefaultHttpHeaders is not applied to the actual HTTP response by this pathway — the HTTP response is committed as 200, and the serialised body is null rather than the expected JSON error structure.
Observable impact: Callers cannot detect stop-not-found or malformed-ID conditions by HTTP status code. The body null must be used as the error signal.
Likely intended behaviour: HTTP 404 with a JSON envelope containing "code": 404 and a descriptive text field for stop-not-found; HTTP 400 or 500 with "code": 400/500 for malformed IDs and unexpected errors. Maglev intentionally corrects this — see Implementation Decisions.
2. Error response version is hardcoded to 1.
When an exception is caught, ExceptionInterceptor.getExceptionAsResponseBean (line 72) constructs a ResponseBean with the version literal 1, regardless of the version parameter supplied by the caller. The version field in error responses is therefore always 1 rather than the requested version.
3. Dead null-check in StopAction.show().
StopAction.show() (line 61) checks if (stop == null) and calls setResourceNotFoundResponse(). This branch is unreachable: the service method (StopBeanServiceImpl.getStopForIdForServiceDate) either throws NoSuchStopServiceException or returns a non-null StopBean. The null-return path does not exist.
Unknown stop ID returns HTTP 404 and malformed ID returns HTTP 400 (deviates from legacy).
The legacy implementation returns HTTP 200 with a null body for both stop-not-found and malformed ID conditions (see Suspected Defects). Maglev intentionally corrects this: an unknown stop ID returns HTTP 404 with a standard error envelope, and a malformed ID (no underscore) returns HTTP 400, consistent with the treatment of other unknown and invalid entity IDs across the API.
{
"type": "object",
"required": ["id"],
"properties": {
"id": {
"type": "string",
"description": "Combined stop ID in the form {agencyId}_{entityId}, encoded in the URL path"
},
"key": {
"type": "string",
"description": "API key"
},
"version": {
"type": "integer",
"default": 2,
"description": "API version"
},
"includeReferences": {
"type": "boolean",
"default": true,
"description": "Whether to populate the references block in the response"
}
}
}id — Combined stop ID of the form {agencyId}_{entityId} (e.g. 1_11870). The system splits on the first underscore, so the entity ID portion may itself contain underscores. Supplied as a URL path segment: /api/where/stop/{id}.json.
key — API key for the requesting application. Passed as a query parameter.
version — Selects the API response format. Only version 2 is implemented in Maglev; version 1 is out of scope.
includeReferences — When false, the references block is present but all arrays are empty. Defaults to true.
{
"type": "object",
"properties": {
"code": { "type": "integer" },
"text": { "type": "string" },
"version": { "type": "integer" },
"currentTime": { "type": "integer", "description": "Unix ms" },
"data": { "type": "object" }
}
}code — HTTP status mirror: 200 on success.
text — Human-readable status string (e.g. "OK").
version — API version of the response (2).
currentTime — Server wall-clock time at the moment the response was generated, in Unix milliseconds.
data — Container for the entry and references.
{
"type": "object",
"properties": {
"id": { "type": "string" },
"lat": { "type": "number" },
"lon": { "type": "number" },
"name": { "type": "string" },
"code": { "type": "string" },
"direction": { "type": "string" },
"locationType": { "type": "integer" },
"wheelchairBoarding":{ "type": "string" },
"routeIds": { "type": "array", "items": { "type": "string" } },
"staticRouteIds": { "type": "array", "items": { "type": "string" } },
"parent": { "type": "string" }
}
}data.entry.id — Combined stop ID (agencyId_entityId).
data.entry.lat — WGS-84 latitude of the stop.
data.entry.lon — WGS-84 longitude of the stop.
data.entry.name — Human-readable stop name (e.g. "S Jackson St & 30th Ave S").
data.entry.code — Short public stop code shown on signage (e.g. "11870"). Taken from the GTFS stop_code field; falls back to the entity portion of the combined ID when the feed supplies no code.
data.entry.direction — Compass direction of travel at this stop (e.g. "N", "S", "NE"). Optional GTFS extension; absent when the feed does not supply it. Serialised as an empty string "" when absent.
data.entry.locationType — GTFS location type integer: 0 = boarding/alighting platform (default), 1 = station, 2 = station entrance or exit, 3 = generic pathway node, 4 = boarding area.
data.entry.wheelchairBoarding — Wheelchair accessibility annotation drawn from the GTFS feed. One of "UNKNOWN", "ACCESSIBLE", "NOT_ACCESSIBLE", or "PARTIALLY_ACCESSIBLE" (experimental). Absent when the feed provides no annotation.
data.entry.routeIds — Ordered array of combined route IDs (agencyId_entityId) for every route that serves this stop, across all service dates. Sorted by route short name in natural string order (numeric digit runs are compared as numbers, so "14" < "101" < "B"). When short name is absent, long name is used for sorting.
data.entry.staticRouteIds — Ordered array of combined route IDs representing the curated static route list for this stop. Used by some clients for display purposes (e.g. a stop sign showing which routes stop here regardless of real-time service). When no static list is configured for the stop, this field is identical to routeIds.
data.entry.parent — Combined ID of the enclosing station stop, if this stop is a sub-platform of a GTFS station. When no parent exists, this field is serialised as an empty string "". The full parent record is provided in data.references.stops[].
{
"type": "object",
"properties": {
"agencies": { "type": "array", "items": { "type": "object" } },
"routes": { "type": "array", "items": { "type": "object" } },
"stops": { "type": "array", "items": { "type": "object" } },
"trips": { "type": "array", "items": { "type": "object" } },
"situations": { "type": "array", "items": { "type": "object" } },
"stopTimes": { "type": "array", "items": { "type": "object" } }
}
}data.references.agencies[] — Full agency records for each agency that owns a route in routeIds. Empty when includeReferences=false.
data.references.routes[] — Full route records for each route in routeIds. Empty when includeReferences=false.
data.references.stops[] — Contains the full record of the parent station stop, if data.entry.parent is non-empty. Empty when the stop has no parent, or when includeReferences=false. The parent record follows the same structure as data.entry, with all fields populated from the database. Note: the GTFS spec permits a three-level hierarchy (Station → Platform → Boarding Area), but this spec assumes a maximum of two levels; boarding area stops are out of scope.
data.references.trips[] — Always empty for this endpoint.
data.references.situations[] — Always empty for this endpoint.
data.references.stopTimes[] — Always empty for this endpoint.