Quick start
No sign-up needed to try it. Every endpoint is a GET request that returns JSON.
curl "https://www.cincystairs.xyz/api/v1/stairs?neighborhood=Mount%20Adams&limit=5"Lists come back in the same envelope:
{
"data": [
{
"id": "004",
"name": "Ohio Avenue Steps",
"neighborhood": "Over-the-Rhine",
"connects": [
"Ohio Avenue, #1913",
"Clifton Avenue, #27 & 103"
],
"steps": null,
"stepCountSource": null,
"sections": 0,
"climbFeet": 39,
"difficulty": "Moderate",
"condition": "Excellent",
"features": [
"Handrails"
],
"description": null,
"bottom": { "lat": 39.1187376, "lng": -84.5185659 },
"top": { "lat": 39.119044, "lng": -84.5182789 },
"path": [
{ "lat": 39.1187376, "lng": -84.5185659 },
{ "lat": 39.119044, "lng": -84.5182789 }
],
"directionConfidence": "high",
"open": true,
"city": {
"status": "Open",
"rating": "Excellent",
"inspected": "2021-08-31"
},
"surveyedAt": null,
"verifiedFields": [],
"url": "https://www.cincystairs.xyz/stairs/004"
}
],
"meta": {
"total": 1,
"limit": 50,
"offset": 0,
"next": null
},
"license": {
"name": "CC BY 4.0",
"url": "https://creativecommons.org/licenses/by/4.0/",
"attribution": "Data: CincyStairs (www.cincystairs.xyz), CC BY 4.0"
}
}Keys and limits
| Without a key | 60 requests per hour per IP address |
| With a key | 5,000 requests per day per key, resetting at midnight UTC |
| GeoJSON download | Not counted |
Keys are free. Open CincyStairs, sign in, and go to Profile, Developer API. Name the key and copy it; it’s shown once. You can have up to five active keys, see how much each one is used, and revoke them there.
Send the key in a header:
curl -H "X-API-Key: cs_your_key_here" \
"https://www.cincystairs.xyz/api/v1/stairs?near=39.1187,-84.5186&radius=0.25"Authorization: Bearer cs_… works too. Every response includes X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (Unix time). Responses carry an ETag; send it back in If-None-Match and you’ll get a 304 with no body. The API allows requests from any website (CORS).
Endpoints
Base URL: https://www.cincystairs.xyz/api/v1
GET/stairs
Every stair, 50 at a time by default.
- neighborhood
- Exact neighborhood name, any case. Example: Mount Adams
- condition
- Excellent, Good, Fair, Poor or Not Rated
- difficulty
- Easy, Moderate, Hard or Very Hard
- open
- true for stairs the city lists as open, false for closed, abandoned or removed
- counted
- true for stairs with a step count, false for ones nobody has counted yet
- q
- Text in the name, or an exact stair ID
- near
- lat,lng. Sorts nearest first and adds distanceMiles
- radius
- Miles from near, default 0.5, up to 10
- bbox
- minLng,minLat,maxLng,maxLat
- limit, offset
- Paging. limit is 1 to 500
GET/stairs/{stepId}
One stair by its ID, such as 004 or 102A.
GET/stairs.geojson
The whole dataset as a GeoJSON FeatureCollection. Cached for an hour, not counted against your limit.
GET/routes
Curated walking routes.
- neighborhood
- Routes passing through this neighborhood
- stair
- Routes that include this stair ID
- limit, offset
- Paging
GET/routes/{routeId}
One route, with the IDs of the stairs on it.
GET/neighborhoods
Neighborhoods with how many stairs each has, and how many are open.
GET/openapi.json
OpenAPI 3.1 description of all of the above.
Stair fields
- id
- Stair ID, matching the city inventory where there is one
- name, neighborhood
- What it is called and where it is
- connects
- The streets or places at each end
- steps, stepCountSource
- Step count, or null if nobody has counted. Source is official, maintainer or crowd (three matching counts from people who were there)
- sections, climbFeet, difficulty
- Flights, estimated height climbed, and a difficulty based on both
- condition, features
- Condition rating and things like handrails or views
- description
- Written description, or null
- bottom, top, path
- Coordinates. path runs bottom to top
- directionConfidence
- How sure we are which end is the top: verified, high, medium or low
- open, city
- Whether the stair is walkable, and the status, rating and inspection date from the City of Cincinnati's inventory
- surveyedAt, verifiedFields
- When a CincyStairs maintainer last checked it in person, and which fields they confirmed
- url
- The stair’s page on CincyStairs
Photos and stair histories aren’t included. Some histories come from a published book and aren’t ours to license. Data that hasn’t been confirmed on site, like many elevations and directions, is an estimate.
Examples
JavaScript
const res = await fetch('https://www.cincystairs.xyz/api/v1/stairs?open=true&counted=true', {
headers: { 'X-API-Key': process.env.CINCYSTAIRS_KEY },
});
const { data, meta } = await res.json();
console.log(`${meta.total} open stairs with step counts`);Every stair on a Leaflet map
const res = await fetch('https://www.cincystairs.xyz/api/v1/stairs.geojson');
L.geoJSON(await res.json(), {
onEachFeature: (f, layer) => layer.bindPopup(f.properties.name),
}).addTo(map);Errors
Errors come back as { "error": { "code", "message", "docs" } }.
| 400 | invalid_parameter | A query parameter is wrong. The message says which. |
| 401 | invalid_key | The key is malformed, unknown or revoked. |
| 404 | not_found | No stair or route with that ID. |
| 429 | rate_limited | You hit your limit. Wait the number of seconds in Retry-After. |
| 500 | server_error | Something broke on our end. |
License and credit
The data is shared under Creative Commons Attribution 4.0. You can copy, change and use it for anything, including commercial work, as long as you credit CincyStairs where the data appears. For example:
Data: CincyStairs (www.cincystairs.xyz), CC BY 4.0
City status, ratings and inspection dates come from the City of Cincinnati’s public stairway inventory.
Terms
- The data is provided as is. Stairs close, crumble and get overgrown; don’t present it as a guarantee that a stair is safe or open.
- Stay within the limits. Don’t spread traffic across several keys or addresses to get around them. If you need more, get in touch.
- Cache what you can. The data changes slowly, and the GeoJSON download covers most needs.
- We may revoke keys that are abused, and we may change limits. Breaking changes will come as a new version (/api/v2), not as changes to v1.