Project Sidewalk

Welcome back

Project Sidewalk

Join Project Sidewalk

Map sidewalks. Help make your city accessible to everyone.

3–30 characters — letters, numbers, hyphens, and underscores

Pick a nickname — you don't have to use your real name. It's shown publicly on the leaderboard and your profile, and you can change it anytime.

  • At least 8 characters
  • One uppercase letter
  • One lowercase letter
  • One number
Passwords match

AccessScore: Streets API

The AccessScore: Streets API returns an accessibility score in the range (0, 1) for each street — higher is more accessible. A street's score combines three things: its own segment, scored from the obstacles, surface problems, and missing sidewalks along it, and the intersections at its two ends, scored from the curb ramps, crosswalks, and signals pooled at each corner (see the AccessScore: Intersections API). Each piece weights every clustered feature by its type, its severity/quality rating, and its tags, then squashes the total with a logistic function.

AccessScore extends the method introduced in Li et al., A Pilot Study of Sidewalk Equity in Seattle (Urban Access 2022). It is experimental: the weights are deliberately simple and subject to change. For full control you can compute your own index from the Label Clusters API.

AccessScore: Streets Preview

Below is a live preview of street AccessScores from a sample region in Washington, DC, retrieved directly from the API. Streets are colored from red (low accessibility) to green (high); gray streets have not yet been audited. Hover or click a street to see its score and the accessibility features that produced it.

One method, not the method. There is no single correct way to measure street-level accessibility—researchers and practitioners use many different approaches. AccessScore implements just one such algorithm, and it is experimental: the weighting is deliberately simple and subject to change. Treat these scores as one lens rather than a definitive measure, and compute your own index from the Label Clusters API if a different method suits your needs better.

Endpoint

Returns a street segment and its AccessScore for each street in the queried area, optionally filtered by the Query Parameters below.

GET /v3/api/accessScoreStreets

Examples

/v3/api/accessScoreStreets?filetype=geojson Get AccessScores for all streets in GeoJSON (default)

/v3/api/accessScoreStreets?filetype=geojson&inline=true Same, but opened in the browser

/v3/api/accessScoreStreets?filetype=csv Get AccessScores for all streets in CSV

/v3/api/accessScoreStreets?regionId=8 Get scores for streets in a single region

How the score is computed

Project Sidewalk clusters proximal labels of the same type, and scores two kinds of unit from them: a street's segment and an intersection. Four of the seven scored label types are corner features — CurbRamp, NoCurbRamp, Crosswalk, Signal — so each of their clusters is attributed to the nearest intersection within 25 m (on whichever street it sits) and scores that intersection, pooled with the corner features of every other street meeting there. The rest of the street's clusters score its segment: the along-length types (Obstacle, SurfaceProblem, NoSidewalk), plus any corner-type cluster too far from an intersection to belong to one (a mid-block crosswalk, a driveway ramp), which counts as an ordinary point feature.

A street's headline score is the plain mean of its segment_score and the scores of its start and end intersections, over whichever of the three exist — a trip along a street includes getting on and off it. An end with no intersection (a dead end, a street split at a region boundary) or a grade-separated one contributes nothing, and an unaudited street between two scored crossings still gets a headline from them. All three components are reported, so you can recombine them your own way.

Scoring a segment. Each scored label type contributes a signed term; the segment's score is the logistic (sigmoid) of the sum of those terms, mapped to (0, 1). An audited street with nothing on it scores the neutral 0.5.

Most types are scored per cluster. Each cluster contributes base(type) × ratingMultiplier + Σ tagAdjustments, and the type's term is the sum over its clusters on the street:

  • Positive, quality-rated (CurbRamp, Crosswalk): rated Good / Okay / Bad → multiplier +1.0 / +0.5 / −1.0. A Bad feature therefore lowers the score.
  • Negative, severity-rated (NoCurbRamp, Obstacle, SurfaceProblem): rated Low / Medium / High → magnitude 0.33 / 0.67 / 1.0 of the (negative) base.
  • Presence-only (Signal): no rating; each cluster adds the base weight as-is.
  • Tags adjust a cluster's weight when present on at least half of its labels — e.g. a Signal tagged hard to reach buttons counts negatively, while APS (accessible signal) counts positively.
  • Length normalization (Obstacle, SurfaceProblem): these describe problems along a street, so their terms are scaled to a density per 100 m — term × 100 / max(length_meters, 25). Three obstacles on a 300 m street score as one on a 100 m street; a long street is not penalized for having more room for problems. The 25 m floor keeps a stub of a street from multiplying one problem without bound, so the factor never exceeds 4.

NoSidewalk is scored once per street, as a street condition, rather than per cluster. A missing sidewalk is a stretch, not a point: labelers pin it repeatedly along the street and it is clustered at 10 m, so a per-cluster weight would measure how many pins were dropped instead of whether the street has a sidewalk. All of a street's NoSidewalk clusters pool into a single term, which is what score_no_sidewalk reports:

score_no_sidewalk = base × min(1, n / 3) + Σ tagAdjustments
  • n is the street's NoSidewalk cluster count (n_no_sidewalk). The extent factor min(1, n / 3) saturates at 3 clusters: one stray pin counts 1 / 3 of the base weight, and 3 or more count the full weight however many more there are.
  • Known limitation. A cluster count is a proxy for coverage, and NoSidewalk clusters at 10 m, so how readily a street reaches 3 clusters depends on how long it is. A very short street without a sidewalk can be structurally unable to saturate, and so scores somewhat higher than a long street in the same condition. Pooling removes this effect above saturation but not below it.
  • Street-wide tags are judged over the street's pooled NoSidewalk labels: a tag adjusts the term when at least half of those labels carry it. street has no sidewalks and street has a sidewalk (the other side of the street has one) are mutually exclusive, so at most one is active unless the labels split exactly in half, in which case they cancel.
  • ends abruptly marks the point where a sidewalk stops, so it is active when any single cluster carries it on at least half of that cluster's labels. It aggravates the score: a pedestrian on the sidewalk is stranded in the roadway where it ends.
  • Worked example (current weights, before the street's other features): a street with eight NoSidewalk clusters and no tags scores sigmoid(-2.00) ≈ 0.12; tagged street has no sidewalks it drops to sigmoid(-3.00) ≈ 0.05; tagged street has a sidewalk it rises to sigmoid(-1.00) ≈ 0.27. A single NoSidewalk cluster with no tags is sigmoid(-0.67) ≈ 0.34.

The base weights and tag adjustments below are read directly from the scoring engine, so they always match what the API computes:

Label typeBase weightRatingTag adjustments
CurbRamp +0.75 Quality (Good / Okay / Bad)
missing tactile warning -0.25
narrow -0.25
points into traffic -0.25
steep -0.25
NoCurbRamp -1.00 Severity (Low / Medium / High)
alternate route present +0.25
no alternate route -0.50
Obstacle -1.00 Severity (Low / Medium / High)
SurfaceProblem -1.00 Severity (Low / Medium / High)
Crosswalk +0.75 Quality (Good / Okay / Bad)
level with sidewalk +0.25
no pedestrian priority -0.25
paint fading -0.25
Signal +0.50 Presence only, per cluster
APS +0.25
button waist height +0.15
hard to reach buttons -0.25
NoSidewalk -2.00 Street condition: one pooled term per street
covered walkway +0.50
ends abruptly -1.00
gravel/dirt road -0.25
pedestrian lane marking +0.50
shared pedestrian/car space +0.25
street has a sidewalk +1.00
street has no sidewalks -1.00

The per-type cluster_counts and sub_scores in each response let you see exactly how a street's segment was composed: the sub_scores sum to logit(segment_score). A corner type appearing there is a mid-block cluster; the ones at the corners are on the intersections. Other label types (Occlusion, Other) do not affect the score.

Note: Region scores (see the AccessScore: Regions API) are the length-weighted mean of their streets' headline scores, alongside a plain mean of their intersections.

Recomputing the score under your own weights

Every street carries the inputs to its score, so you can re-weight it yourself. GET /v3/api/accessScoreConfig returns the engine's configuration — the scored types in order, each type's base weight and scoring mode, the rating multipliers per bucket, the tag adjustments, the street-condition saturation count, and a few named weight presets — as JSON:

GET /v3/api/accessScoreConfig

Then, per street, each type's segment term is rebuilt from severity_counts, tag_adjustments, and length_meters:

  • Per-cluster types (positive_quality, negative_severity, presence_only): term = weight × Σbucket severity_counts[type][bucket] × multiplier(bucket) + tag_adjustments[type], where the multiplier comes from quality_multiplier or severity_multiplier by scoring mode, and is 1 for presence_only.
  • Street-condition types (street_condition): term = weight × min(1, n / street_condition_saturation_count) + tag_adjustments[type], with n the type's total cluster count.
  • Length-normalized types (length_normalized is true): multiply the whole term by length_normalization.per_meters / max(length_meters, length_normalization.min_length_meters).
  • segment_score = sigmoid(Σtype term). With the engine's own base_weight for every weight, the terms equal sub_scores and the result equals segment_score.
  • An intersection is rebuilt the same way from its own severity_counts and tag_adjustments, with no length factor; the street's headline score is then the mean of segment_score and the scores of start_intersection_id and end_intersection_id, over those that are not null.

The response also carries clusters_updated_at: when the nightly clustering run that every score is computed from last finished (ISO 8601, or null before the first run). Labels added after that moment are not in any cluster yet and cannot have changed a score until the next run.

The presets object holds named weight vectors (magnitudes; a type's sign is fixed by its base weight): default is the engine's own weights, barriers weighs problem types half again as much, infrastructure does the same for feature types, and missing_ramps doubles missing curb ramps. They are named by what they change, not by whom they are for — a weighting that claims to represent a disability community would need that community's involvement and the literature behind it — and every value is published, so a lens hides nothing.

Quick Download

Download street AccessScore data directly in your preferred format:

Generating your download...
This may take anywhere from a few seconds to a few minutes depending on the data size.

Note: This downloads scores for all streets. For filtered data, use the API Query Parameters described below.

Query Parameters

All parameters are optional.

Note: When multiple location filters are provided (bbox, regionId, and regionName), bbox takes precedence over region filters, and regionId takes precedence over regionName. A region filter is resolved to the region's bounding box, and the streets are then trimmed back to that region.

ParameterTypeDescription
bbox string Filter by bounding box, as minLongitude,minLatitude,maxLongitude,maxLatitude (e.g., -74.04,40.88,-74.00,40.91), WGS84 (EPSG:4326). If omitted, the city's default bounding box is used.
regionId integer Score only streets within the region with this id. Takes precedence over regionName; bbox takes precedence over both.
regionName string Score only streets within the region with this name. Used only when bbox and regionId are absent.
filetype string Output format. Options: geojson (default), csv, shapefile, geopackage.
inline boolean Whether to display the file inline rather than as an attachment. Default: false.

Responses

Success Response (200 OK)

On success, the API returns 200 OK and the requested data in the specified filetype format.

GeoJSON Format (Default)

Returns a GeoJSON FeatureCollection where each feature is a street segment (LineString, WGS84 / EPSG:4326).

{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "geometry": {
        "type": "LineString",
        "coordinates": [[-74.0243, 40.8839], [-74.0245, 40.8836]]
      },
      "properties": {
        "street_edge_id": 766,
        "osm_way_id": 11584845,
        "region_id": 8,
        "score": 0.6849,
        "segment_score": 0.3846,
        "start_intersection_id": 412,
        "end_intersection_id": 415,
        "start_intersection_score": 0.8176,
        "end_intersection_score": 0.8525,
        "audit_count": 2,
        "length_meters": 142.7,
        "label_count": 4,
        "cluster_counts": {
          "CurbRamp": 0, "NoCurbRamp": 0, "Obstacle": 0, "SurfaceProblem": 1,
          "Crosswalk": 1, "Signal": 0, "NoSidewalk": 0
        },
        "sub_scores": {
          "CurbRamp": 0.0, "NoCurbRamp": 0.0, "Obstacle": 0.0, "SurfaceProblem": -0.4695,
          "Crosswalk": 0.0, "Signal": 0.0, "NoSidewalk": 0.0
        },
        "severity_counts": {
          "CurbRamp": {"1": 0, "2": 0, "3": 0, "null": 0},
          "NoCurbRamp": {"1": 0, "2": 0, "3": 0, "null": 0},
          "Obstacle": {"1": 0, "2": 0, "3": 0, "null": 0},
          "SurfaceProblem": {"1": 0, "2": 1, "3": 0, "null": 0},
          "Crosswalk": {"1": 0, "2": 0, "3": 1, "null": 0},
          "Signal": {"1": 0, "2": 0, "3": 0, "null": 0},
          "NoSidewalk": {"1": 0, "2": 0, "3": 0, "null": 0}
        },
        "tag_adjustments": {
          "CurbRamp": 0.0, "NoCurbRamp": 0.0, "Obstacle": 0.0, "SurfaceProblem": 0.0,
          "Crosswalk": 0.0, "Signal": 0.0, "NoSidewalk": 0.0
        }
      }
    },
    ...
  ]
}
GeoJSON Field Descriptions
Field PathTypeDescription
geometry.coordinatesarray[number]Coordinate pairs forming the street's path, in [longitude, latitude] (WGS84 / EPSG:4326).
properties.street_edge_idintegerProject Sidewalk's unique identifier for this street segment.
properties.osm_way_idintegerOpenStreetMap Way ID for the street, if available.
properties.region_idintegerIdentifier for the region the street belongs to.
properties.scorenumberHeadline AccessScore in (0, 1); higher is more accessible. The mean of segment_score, start_intersection_score, and end_intersection_score over those that are not null; null when all three are.
properties.segment_scorenumberThe street's own segment score in (0, 1), from the clusters in cluster_counts. null when the street has not been audited.
properties.start_intersection_idintegerThe intersection at the start of the street's geometry (see the Intersections API); null when that end is a dead end or a mere way split.
properties.end_intersection_idintegerThe intersection at the end of the street's geometry; null likewise.
properties.start_intersection_scorenumberThat intersection's score; null when there is none, it is unscored, or it is grade-separated.
properties.end_intersection_scorenumberThat intersection's score; null likewise.
properties.audit_countintegerNumber of completed (high-quality) audits of this street.
properties.length_metersnumberStreet length in meters (geodesic). Length-weights region scores and normalizes the along-length terms.
properties.label_countintegerNumber of labels contributing to this street's segment clusters.
properties.cluster_countsobjectNumber of clusters of each label type scoring the segment, keyed by label-type name: the along-length types, plus any corner-type cluster not attributed to an intersection.
properties.sub_scoresobjectContribution of each label type to the segment's pre-sigmoid total, keyed by label-type name; the values sum to logit(segment_score), so they explain how it was composed. Per-cluster types report the sum over their clusters, length-normalized where the type is; NoSidewalk reports the street's single pooled term (see How the score is computed), which does not scale with cluster_counts.NoSidewalk.
properties.severity_countsobjectNumber of segment clusters of each label type in each rating bucket ("1", "2", "3", or "null" for unrated clusters), keyed by label-type name. The buckets of a type sum to its cluster_counts entry.
properties.tag_adjustmentsobjectThe summed adjustment of the active tags of each label type, keyed by label-type name: the part of sub_scores that no weight scales, before length normalization. With severity_counts, length_meters, and the scoring configuration, enough to recompute the segment score under your own weights.

CSV Format

If filetype=csv, the first row is the header. The per-type objects are flattened into one column per label type, each named for its path through the JSON: cluster_counts.<type>, sub_scores.<type>, severity_counts.<type>.1 through .3 plus .null, and tag_adjustments.<type>; the geometry is simplified to start/end points.

street_edge_id,osm_way_id,region_id,score,segment_score,start_intersection_id,end_intersection_id,start_intersection_score,end_intersection_score,audit_count,length_meters,label_count,cluster_counts.CurbRamp,…,cluster_counts.NoSidewalk,sub_scores.CurbRamp,…,sub_scores.NoSidewalk,severity_counts.CurbRamp.1,severity_counts.CurbRamp.2,severity_counts.CurbRamp.3,severity_counts.CurbRamp.null,…,severity_counts.NoSidewalk.null,tag_adjustments.CurbRamp,…,tag_adjustments.NoSidewalk,start_point,end_point
766,11584845,8,0.6849,0.3846,412,415,0.8176,0.8525,2,142.7,4,0,…,0,0.0,…,0.0,0,0,0,0,…,0,0.0,…,0.0,"-74.0243,40.8839","-74.0245,40.8836"
...

The full header, in order: the twelve leading columns, then cluster_counts.<type> for each of the 7 scored types, sub_scores.<type> for each, the four severity_counts.<type>.* buckets for each, tag_adjustments.<type> for each, then start_point and end_point. Types are always in the order CurbRamp, NoCurbRamp, Obstacle, SurfaceProblem, Crosswalk, Signal, NoSidewalk.

Shapefile Format

If filetype=shapefile, the response is a ZIP archive of Shapefile components (.shp, .shx, .dbf, .prj). Because the DBF format truncates column names at 10 characters, the columns use short codes: segScore, sIntId / eIntId, and sIntScore / eIntScore for the headline's components, and per type cluster count nCRamp, sub-score sCRamp, cluster count per rating bucket n1CRamp, n2CRamp, n3CRamp and n0CRamp (unrated), and tag adjustment tCRamp; the GeoJSON, CSV, and GeoPackage formats keep the full names.

GeoPackage Format

If filetype=geopackage, the response is a GeoPackage (.gpkg) file with full geometry and attributes (full snake_case column names).

Error Responses

  • 400 Bad Request: Invalid parameter values (e.g., malformed bounding box, non-positive or unknown region id).
  • 500 Internal Server Error: An unexpected error occurred on the server.

Error Response Body

All errors are returned as RFC 7807 “problem details” with the application/problem+json content type and the following structure:

{
  "type": "about:blank",          // RFC 7807 problem-type URI ("about:blank" means no type beyond the status)
  "title": "Invalid Parameter",   // Short, human-readable summary of the problem type (stable for a given code)
  "status": 400,                  // HTTP status code (also repeated in the body)
  "detail": "Invalid value for the bbox parameter. Expected format: minLng,minLat,maxLng,maxLat.", // This occurrence
  "code": "INVALID_PARAMETER",    // Stable, machine-readable error code you can branch on
  "parameter": "bbox"             // Extension member: the specific parameter at fault (omitted when not applicable)
}

Best Practices

  • Treat the score as relative, not absolute: the weights are experimental. Use scores to compare streets, not as a calibrated index.
  • Read the breakdown: segment_score and the two intersection scores show which part of a trip along the street is the problem, and cluster_counts and sub_scores explain the segment.
  • Mind audit coverage: a null segment_score means the street has not been audited — absence of data, not poor accessibility. Its headline may still carry its crossings' scores.
  • Roll up to neighborhoods: use the AccessScore: Regions API for length-weighted, region-level scores.

Contribute

Project Sidewalk is an open-source project created by the Makeability Lab and hosted on GitHub. We welcome your contributions! If you found a bug or have a feature request, please open an issue on GitHub.

You can also email us at sidewalk@cs.uw.edu

Project Sidewalk in Your City!

If you are interested in bringing Project Sidewalk to your city, please read our Wiki page.

On This Page