The scorecard publishes static JSON, and that JSON is a public read API. Other tools, dashboards, and an agency's own website can pull a grade without scraping the page. This document is the contract those consumers depend on. The roadmap (docs/roadmap.md, Year 3) makes it an intentional, versioned interface; this file is the start of that.
On the deployed site, artifacts are served under data/artifacts/. When the
CloudFront origin is configured (ADR 0002), the same paths sit under the CDN
domain. Every path below is relative to that base.
| Path | What it is |
|---|---|
index.json |
Every published feed record with its score/grade history. Powers the picker and trends. |
directory.json |
Slim covered-set directory: per-scorecard grade, portable location, size tier, plus guarded corpus-wide, country/subdivision, and legacy place summaries. Individual percentiles are not published. |
changes/latest.json |
Canonical feed records whose grade or score moved under the same current producer contract since their last check. Auditable dated copies are retained at changes/<date>.json. |
<agency>/latest.json |
The most recent full scorecard for one agency. |
<agency>/<date>.json |
The scorecard for one agency on one date (YYYY-MM-DD). |
<agency>/badge.svg |
Embeddable grade badge (see below). |
<agency>/badge.json |
The same grade in the Shields.io endpoint format, for custom badges. |
rollups/index.json |
Every published program rollup, summarized. |
rollups/<id>.json |
One rollup across many agencies. |
rollups/<id>.csv |
The same rollup's members as a spreadsheet (grade, score, expiry status, top fix) for a liaison's report. |
catalog.json |
Flat list of every agency with grade, feed URL, freshness, identity, and provenance, in one request. |
catalog.csv |
The same catalog as CSV. |
scoring.json |
Machine-readable methodology: category weights, grade bands, and the correctness severity deductions. |
/api/v1/coverage.json |
Separately named registry, organization-key, published-page, and scored-row counts. |
/schemas/artifact.schema.json, /schemas/catalog.schema.json, /schemas/directory.schema.json, /schemas/coverage.schema.json |
JSON Schemas (Draft 2020-12) for validating the per-agency artifact, catalog, directory, and coverage counts in CI. |
The published scorecard data is offered under CC-BY-4.0. Reuse it, including
commercially, with attribution: "GTFS Scorecard (gtfsscorecard.org), scored on
top of the MobilityData gtfs-validator." The catalog.json and directory.json
documents carry license and attribution fields so the grant travels with the
data. The grade is a derived data-quality signal, not a compliance
determination.
The scorecard scores feeds discovered through the Mobility Database plus a curated set. An agency that publishes no GTFS, or that appears in no catalog, is not scored and simply does not appear. Absence therefore means "not covered," never "failing." Do not infer a national denominator from the row count; it is the covered set, not the universe of US agencies.
/api/v1/coverage.json makes the service's internal denominators explicit.
configured_feed_records and active_canonical_feed_records describe the
curated registry. distinct_organization_keys includes feed-ID fallbacks;
provisional_organization_keys says how many of those fallbacks still need
curation. published_scorecard_pages counts entries retained in the artifact
index, while scored_latest_rows counts latest rows with a numeric score.
These values can differ because removing a feed from the registry does not
erase its published history. The older /api/v1/stats.json agency_count
field remains for API v1 consumers and counts every row in that endpoint's
published score dataset. comparison_eligible_count names the narrower
producer-, methodology-, category-, and identity-safe denominator used for
score aggregates. Use coverage.json for registry, organization, and rendered
page counts.
Every artifact carries a schema_version (currently 1.11). The rule for
consumers: tolerate added fields, and treat a change in the major version as a
breaking change worth pinning against. New fields are additive within a major
version. When a field's meaning changes or a field is removed, the major
version increments and the change is noted in the rubric changelog.
A JSON Schema for the per-agency artifact (latest.json, <date>.json),
catalog.json, and directory.json is published under /schemas/, so a
consumer can validate against it in CI and catch a breaking change as a test
failure. The pipeline enforces the artifact schema on its own output: every
artifact is validated against artifact.schema.json before it is written, so a
shape change cannot reach production without a schema update. scoring.json
exposes the category weights, grade bands, and severity deductions, so the
grade is reproducible rather than opaque.
The raw feed bytes behind a grade are archived too, deduplicated by the
feed.sha256 every artifact already carries (content-addressed: one copy per
distinct hash, not per agency per day). The score job writes each new hash to
a local dedup store and, when a durable bucket is configured
(RAW_ARCHIVE_BUCKET, falling back to ARTIFACTS_BUCKET), to S3 as the
second, durable tier (archive.py). scorecard reproduce <agency> <date>
pulls the archived bytes for a published snapshot, re-runs the validator
version that artifact recorded, rescores it, and diffs the result against the
published grade, score, and category scores: the mechanism a disputed grade
or a validator-upgrade study cites. The archive is private to the pipeline;
public redistribution of a re-served copy is a separate, license-gated
decision (see docs/ideation/02-large-scale-fixes.md, FIX-02).
The flat analysis exports (dataset.json, dataset.csv,
api/v1/agencies.parquet) carry two version fields: schema_version is the
version of the flat export's own shape, and pipeline_schema_version is the
artifact schema (the 1.x documented here) the export was derived from. A
citation should pin the release tag, which fixes both.
Flat export schema 1.2 adds rubric_version, scoring_profile_id,
scoring_profile_rubric_version, validator_version, and feed_sha256.
Together with the category columns, these fields let consumers build the same
single-producer, single-category-set, deduplicated comparison cohort as the
public summaries.
For citation, do not cite the live site: it changes daily. A monthly
dataset-YYYY-MM release (the Dataset release workflow) pins the flat
exports, the parquet file, the NTD rollup, this data dictionary, and
CITATION.cff to an immutable tag, so a paper's reference resolves to exactly
the bytes analysed. Releases:
https://github.com/ChelseaKR/gtfs-scorecard/releases.
Changelog:
1.11addsconfigured_kindsandkinds_configuredto Realtime category details. Together withkinds_reachable, they state the exact denominator behind capability-aware reachability scoring for partial GTFS-Realtime publishers. Additive.1.10addseffective_expiry_date,service_horizon_review_years, andservice_horizon_statusto freshness details; the status is also copied to index history, catalog rows, and the open dataset/API. An unusually distant service end date is a display and trust advisory, not a GTFS error, finding, or score deduction. Readers derive the same status for legacy rows when the field is absent but snapshot date and expiry evidence are present, so an existing artifact is safe on the first deployment. The downloadable dataset shape was1.1with the added status column. Additive.1.9broadens portable country fields from the initial US/Canada values to every assigned ISO 3166-1 alpha-2 country. Published schemas validate the stable alpha-2 shape; the generated global ISO vocabulary remains the stricter admission boundary for country and subdivision codes. Scores and the scoring profile are unchanged. Additive.1.8adds the requiredscoring_profileblock to every per-agency artifact. It identifies the scoring contract independently from the API schema and states that its weights, deductions, thresholds, grade bands, and fix ranking are project choices, not worldwide authority. No existing score, category, grade, or top-fix field moved or changed. Additive.1.7adds portable agency location fields: ISO 3166-1country, ISO 3166-2subdivision_code, and practitioner-facingsubdivision_name. The legacystatefield remains available for existing US consumers. The initial published values wereUSandCA. Additive.1.7introducedstate_percentileon per-state rollups. Public percentile claims were subsequently retired. Current rollup payloads retain the field asnullfor compatibility and expose guarded comparison-cohort metadata instead. Historical 1.x documents may still contain the former integer.1.6adds aconfidenceblock to every scorecard: alevel(provisional,medium, orhigh) reading how much of the grade this run could measure, plus the measured category count, fetch source, realtime sampling depth, and snapshot age behind it. A legibility layer on the one grade, never a second grade. Additive.1.5addsshapes_readinessto every US agency artifact: shapes.txt coverage of trips, mapped onto FTA's July 2025 shapes.txt requirement (Full Reporters RY2025; Reduced, Rural, and Tribal Reporters RY2026). Additive.1.4carries identity and provenance on every catalog and directory row (mdb_id,validator_version,rubric_version,retrieved_at,feed_sha256) and alicense/attributionon the catalog and directory documents. Additive.1.3exposed the freshness fields described below (days_until_expiryin index history,expiry_statusin the catalog and rollup members). Additive.
scoring_profile.id is the stable identifier consumers should use when they
need to compare like-for-like grades. It changes when the scoring contract
changes, while schema_version changes when the artifact shape changes. The
duplicated rubric_version makes the relationship explicit without requiring
consumers to parse the profile identifier. This block describes the one shared
profile only; it does not select or apply a jurisdiction overlay.
In a scorecard's agency block, an omitted country means the legacy US
default. The portable subdivision fields are optional when the primary
subdivision is not known. US artifacts may also carry state as a compatibility
alias; non-US artifacts do not use it. Consumers must treat country as an open
ISO 3166-1 alpha-2 value rather than an enum of the countries covered today. A
new country can appear within API v1 without changing the field's meaning or
type. The agency registry determines which countries currently have scorecards;
it does not determine which assigned ISO locations the data model can represent.
The write boundary validates codes against the packaged global ISO vocabulary.
A category is either "status": "measured" (has score, summary,
findings, details) or not measured ("status": "not_yet_measured" with a
neutral summary and no score). An agency without realtime is never a zero.
The fetch block states how the graded bytes were obtained. When an origin
403s or times out, the pipeline scores the MobilityData hosted mirror instead
of dropping the agency; "source": "mirror" makes that visible, since a mirror
copy can lag what the agency republished. The block is additive within schema
1.4 (consumers tolerate added fields, per the versioning rule above).
The confidence block states how much of this grade the pipeline could
measure this run, not a second grade on top of it: level is always a word
(provisional, medium, or high), never a letter or a number, so it cannot
be mistaken for a second score. measured_categories and total_categories
state the breadth measured; fetch_source mirrors fetch.source above;
rt_windows is 1 when realtime was sampled this run; feed_age_days is how
old the scored snapshot was at scoring time; notes are the same
plain-language sentences shown in the scorecard page's "How we measured this"
panel. Absent on artifacts published before schema 1.6. Additive within schema
1.6.
The shapes_readiness block (US agencies only, schema 1.5) reads the feed's
shapes.txt coverage against FTA's NTD shapes requirement: status is ready
(every trip has a shape), at_risk (partial coverage), or not_ready (no
shapes), with a plain-language detail and, when not ready, a concrete fix.
Like ntd_ready, it is a data-quality heads-up, never an official
determination.
The freshness category's details carry the feed's validity window, and two
fields are surfaced for consumers:
-
details.days_until_expiry(integer, ornullwhen the feed states no end date): days until the feed's service window closes. Negative means it already expired that many days ago. Also copied onto eachindex.jsonhistory point. -
details.effective_expiry_date(ISO date, ornull): the exact earlier date used fromfeed_infoand scheduled service. -
details.service_horizon_review_years(integer): the documented ten-year review threshold used for this artifact. -
details.service_horizon_status(string, also copied onto index history, catalog, and open-dataset rows):within_review_threshold,unusually_distant, orunknown. Unusually distant means the effective end date is strictly more than ten calendar years after the check. The exact boundary stays within the threshold. This advisory is outside category findings, Top 3 fixes, finding prevalence, and finding diffs; it changes no score. For legacy artifacts and rows without this field, consumers can derive it from the snapshot/date pluseffective_expiry_dateordays_until_expiry. Missing date evidence remainsunknown. -
expiry_status(string): a stable bucket derived fromdays_until_expiry, published oncatalog.jsonagencies and rollup members. One of:Value Meaning current30+ days of service left expiring_soon1 to 30 days left lapsedexpired within the last year (likely still running) staleexpired over a year ago (source went quiet) unknownno end date in the feed
One document listing every published feed record, for consumers that want the
whole picture in a single request rather than fetching each latest.json.
{
"source": "https://gtfsscorecard.org",
"schema_version": "1.11",
"rubric_version": "1.2",
"license": "CC-BY-4.0",
"attribution": "GTFS Scorecard (gtfsscorecard.org), scored on top of the MobilityData gtfs-validator",
"agencies": [
{ "id": "yolobus", "name": "Yolobus (...)", "country": "US",
"subdivision_code": "US-CA", "subdivision_name": "California",
"state": "California", "grade": "B", "score": 84.1,
"correctness": 90, "freshness": 85, "completeness": 72,
"realtime": 88, "size_tier": "small",
"national_percentile": null, "peer_percentile": null,
"snapshot_date": "2026-06-12", "days_until_expiry": 120,
"service_horizon_status": "within_review_threshold", "expiry_status": "current",
"ntd_ready": "ready", "google_gate": "pass", "stops": 312,
"mdb_id": "1234", "validator_version": "8.0.1", "rubric_version": "1.2",
"scoring_profile_id": "gtfs-scorecard-1.2",
"scoring_profile_rubric_version": "1.2",
"retrieved_at": "2026-06-12T13:25:01+00:00", "feed_sha256": "...",
"feed_url": "...", "top_fix": "...", "scorecard_url": "https://..." }
]
}catalog.csv carries the key columns (including mdb_id, country,
subdivision_code, subdivision_name, and
validator_version). Use mdb_id to join a row to the Mobility Database rather
than matching on the scorecard's own slug or on the feed URL.
Three readiness fields ride on every catalog and directory row and are worth consuming directly rather than re-deriving:
ntd_ready(string): readiness for the FTA NTD GTFS requirement, rolled up from the published/valid/current/agency_id pillars. One ofready(all four pillars hold),at_risk(a recoverable gap: validator errors, service running out soon, or identity not checked),not_ready(unreachable, lapsed, no readable end date, or no nonblank agency_id). A data-quality heads-up, never an official determination; the agency's own D-10 certification is the official one.google_gate(string): whether the feed clears the Google/Apple Maps four-week service-coverage bar. One ofpass(four or more weeks of service ahead),at_risk(under four weeks),fail(expired).stops(integer or null): boardable stop count read from the feed's stops.txt, a rough size signal alongsidesize_tier.country(string): ISO 3166-1 alpha-2 country code. It defaults toUSfor registry entries that predate the international location fields. The schema validates the portable alpha-2 shape, not a closed list of countries; clients should preserve unfamiliar valid codes.subdivision_code(string or null): ISO 3166-2 code for the agency's state, province, or territory, such asUS-CAorCA-ON.subdivision_name(string or null): practitioner-facing subdivision name, such asCaliforniaorOntario. Use it for display; usesubdivision_codefor joins and grouping.state(string or null): legacy US display field retained for compatibility. Existing US consumers may keep reading it. New consumers should usesubdivision_codeandsubdivision_name; do not treat a nullstateon a non-US agency as unlocated.
The covered-set document the web app's overview reads: one record per published feed with
the same fields as a catalog row (identity, grade, freshness, readiness,
provenance, and size tier) plus a summary block with expiring and expired
counts, size-tier counts, and guarded score aggregates. The comparison object
states the required rubric, scoring profile, validator, measured category set,
and exclusions; unresolved duplicate feed identities do not influence medians
or grade distributions. feed_records counts every published row,
scored_feed_records counts the subset with a numeric score, and
comparison_eligible_count states the still-narrower score-aggregate
denominator. The legacy
national_percentile and peer_percentile keys remain present as null; no
individual percentile is published. It carries the same license and
attribution as the catalog. Prefer it over index.json when you want the
current coverage picture without the full per-feed history.
The paths above are per-agency or whole-catalog. The api/v1/ endpoints add the
cross-agency views a state program or app developer asks for, as small flat JSON
files under a versioned path. v1 is a stability contract: fields may be added,
but existing fields keep their meaning and type, and a breaking change lands at
api/v2. Built from the same index, so the numbers match the pages (ADR 0013).
| Path | What it is |
|---|---|
api/v1/index.json |
The API's self-description: version, endpoint list, license, attribution. |
api/v1/agencies.json |
Every published feed record's latest check in one list (id, name, date, grade, score, rubric and scoring-profile fields, validator version, feed hash, category scores, days to expiry, and service-horizon review status). realtime is null when not measured. |
api/v1/leaderboard.json |
Compatibility path for named changes. top and bottom are always empty; most_improved and most_declined compare a canonical feed only with its own prior check under the same rubric, scoring profile, validator, and measured category set. |
api/v1/by-state.json |
Legacy U.S.-state rollups. count covers every U.S. published row in the state; comparison_eligible_count, median score, and grade distribution use the guarded comparison cohort. U.S. feeds without a known state group under Unlocated. |
api/v1/by-location.json |
Portable country rollups with nested ISO 3166-2 subdivisions. Each count covers every published row in that location; comparison_eligible_count, median score, and grade distribution use the guarded comparison cohort. Null codes collect rows whose curated location is unknown. |
api/v1/stats.json |
Covered-row count and current-feed share over every published row, plus average score, median score, and grade distribution over the guarded cohort. comparison_eligible_count and the comparison block state that narrower denominator and its exclusions. |
api/v1/equity.json |
United States-only state ACS need tiers (poverty, zero-vehicle, disability) joined to agency grades. Refreshed weekly from U.S. Census ACS. |
api/v1/ids.json |
Identity crosswalk: every agency's scorecard slug joined to its Mobility Database id, NTD id, and feed URL, so grades join to either registry (or FTA data) without fuzzy matching. |
api/v1/ridership-impact.json |
United States-only quality context weighted by NTD annual rider-trips (ADR 0021). Weighting uses the guarded comparison cohort and only unique, unambiguous NTD reporter matches. matched_ntd_reporters, total_feed_records, and the duplicate-reporter exclusion fields disclose coverage; legacy matched_agencies and total_agencies keys remain as aliases. Present when the daily NTD fetch succeeded. |
api/v1/scoring.json |
The same machine-readable methodology as scoring.json at the artifact base (weights, grade bands, deductions), served under the versioned path. |
api/v1/accessibility.json |
Covered-set accessibility-data completeness: how many feeds populate wheelchair fields, overall and by portable country/subdivision. Backs the coverage section at /adoption/#access. |
api/v1/adoption.json |
Which newer GTFS capabilities (Flex, Fares v2, pathways, cEMV) feeds publish, overall and by portable country/subdivision. Backs /adoption/. |
api/v1/realtime.json |
Realtime reliability over sampled windows, overall and by portable country/subdivision. Backs /realtime/. |
api/v1/problems.json |
The most common validator findings across the covered corpus, with prevalence counts. Its input contains findings without agency identity, so this endpoint has no geographic rows. Backs /problems/. |
api/v1/trend.json |
The covered-set quality time series. Backs the trend section at /pulse/#trend. |
api/v1/status.json |
Intended cadence plus liveness outcomes restricted to the current published artifact index. Its scope block discloses included and excluded liveness records. |
api/v1/run-status.json |
Latest completed-run evidence. Aggregate counts retain that run's historical attempted set; named unreachable records are restricted to the current published catalog, with older records counted but not named. |
api/v1/canada-equity.json |
Canada served-area equity overlay (StatCan CIMD, ADR 0027), refreshed monthly. Appears once the monthly job has run. |
Per-agency detail stays the published artifact (<agency>/latest.json); the API
does not duplicate it. Human-readable named changes and the corpus trend render
on the coverage overview. Absolute score
rankings and individual percentiles are not published.
The comparison block on aggregate endpoints pins the required rubric,
scoring-profile id, validator version, and measured category set. Overall scores
that use three categories are not mixed with scores that also measure Realtime.
The block also reports exclusion counts and the selected category-set cohort, so
consumers can audit why comparison_eligible_count is smaller than count or
agency_count.
accessibility.json, adoption.json, and realtime.json each add a
countries array. Every country row carries country_code, country_name,
that endpoint's metric fields, and a nested subdivisions array. Subdivision
rows carry subdivision_code (null when unknown), subdivision_name, and the
same metric fields. An unknown subdivision is retained as Unlocated; it is
never inferred from an agency name.
Their existing states arrays remain U.S.-only compatibility views with the
same row shapes and metric meanings. New consumers should use countries.
Historical source records that omit country remain U.S. records by the v1
contract. The additive country rows do not change overall counts, scores,
bands, samples, or grades.
problems.json is intentionally the exception. Its aggregation input contains
only finding lists after agency identity has been removed, so a geographic
split would require guessing or changing the upstream contract. It remains a
covered-corpus prevalence view until identity-carrying inputs are available.
For arbitrary filters and joins, the same covered-set table is published as Parquet
at api/v1/agencies.parquet. A DuckDB or Athena user queries it directly with no
server:
SELECT grade, count(*) FROM 'https://gtfsscorecard.org/api/v1/agencies.parquet'
GROUP BY grade ORDER BY grade;The pipeline ships the same engine: scorecard query "<sql>" runs DuckDB over
the dataset locally (the table is named agencies), and scorecard query --export agencies.parquet writes the file. Install the query extra first:
pip install 'scorecard-pipeline[query]'.
The static JSON serves the bounded cross-agency reads; the Parquet table serves
arbitrary SQL over the covered dataset, both from object storage with no query
server (ADR 0013). A managed database follows only if interactive multi-tenant
queries genuinely appear. The decision and trigger are in
docs/decisions/0013-static-public-api.md.
For consumers that ingest transitions rather than diffing the whole catalog each
day. Lists active canonical feed records whose grade or score moved between two
checks under the same rubric, scoring profile, validator, and measured category
set. Records with unresolved duplicate identities are omitted. Regressions come
first, then the largest move.
changes/<date>.json is an immutable dated copy only when it carries the full
comparison contract. Pre-contract snapshots were withdrawn because their named
moves could not be audited against rubric, scoring-profile, validator, measured-
category, and canonical-identity boundaries.
{
"schema_version": "1.11",
"license": "CC-BY-4.0",
"generated_at": "2026-06-20T13:25:01+00:00",
"feed_record_count": 1128,
"comparison_eligible_count": 1000,
"comparison": {
"eligible_count": 1000,
"required_rubric_version": "1.2",
"required_scoring_profile_id": "gtfs-scorecard-1.2",
"required_validator_version": "8.0.1",
"required_measured_categories": ["correctness", "freshness", "completeness"]
},
"count": 2,
"changes": [
{ "id": "...", "name": "...", "from_grade": "B", "to_grade": "D",
"from_score": 85.0, "to_score": 62.0, "score_delta": -23.0,
"regressed": true, "since": "2026-06-18", "date": "2026-06-19" }
]
}{
"schema_version": "1.11",
"rollup": { "id": "california", "name": "California agencies" },
"agency_count": 2,
"average_score": 78.2,
"grade_distribution": { "B": 1, "C": 1 },
"comparison": { "eligible_count": 2, "excluded_count": 0,
"required_rubric_version": "1.2",
"required_scoring_profile_id": "gtfs-scorecard-1.2",
"required_validator_version": "8.0.1",
"required_measured_categories":
["correctness", "freshness", "completeness"] },
"state_percentile": null,
"needs_attention": 1,
"expired": { "lapsed": 1, "stale": 0, "total": 1 },
"members": [ { "id": "...", "name": "...", "score": 0, "grade": "C",
"snapshot_date": "...", "needs_attention": true,
"days_until_expiry": -30, "expiry_status": "lapsed", "top_fix": "..." } ],
"common_fixes": [ { "code": "...", "fix": "...", "agencies": 2 } ]
}Members needing attention come first, ordered by rider impact when known and
then by name; other members are alphabetical. average_score and
grade_distribution use only the canonical, non-duplicate cohort under the
single rubric, scoring profile, validator, and measured-category set described
by comparison. state_percentile is retained as null for v1 compatibility.
expired counts the members whose feed
has run out, split into recently lapsed and long stale. common_fixes lists
fixes shared by more than one comparison-eligible member, so excluded legacy or
duplicate records cannot inflate a program-wide fix count.
annual_trips is null for every member when multiple feed records claim the
same NTD reporter id, so a reporter's rider count is never assigned twice or
used to break the worklist order ambiguously.
<agency>/badge.svg is a self-contained SVG grade badge. Embed it as an image
that links back to the scorecard, using the canonical domain so it stays stable
for whoever embeds it:
[](https://gtfsscorecard.org/agency/yolobus/)The badge regenerates each day with the rest of the artifacts, so it always shows the current grade. Its accessible name is "GTFS quality: ". When the feed has expired or is expiring, the badge appends a status segment ("feed expired" or "expires soon") and its accessible name gains the status in parentheses, so a stale feed reads at a glance, not only by its letter.
<agency>/badge.json carries the same grade in the
Shields.io endpoint format
({ "schemaVersion": 1, "label": "GTFS quality", "message": "B 84.1", "color": "green" }),
so a consumer can render a custom-styled badge:
https://img.shields.io/endpoint?url=https://gtfsscorecard.org/data/artifacts/yolobus/badge.json
A feed-deployment repository can score a candidate feed before publishing and fail the build on a low grade or an imminent expiry, using the same scoring the site uses (no on-demand public endpoint; it runs in your own CI):
uvx --from gtfs-scorecard scorecard try "$FEED_URL" --country CA \
--min-grade B --min-days-to-expiry 30--country accepts an assigned ISO 3166-1 alpha-2 code and defaults to US so
existing commands retain their behavior. It is passed to the MobilityData
validator and written into the ad-hoc artifact. The command prints the grade,
category bars, and top fixes, and exits non-zero
when a threshold is not met. --min-grade and --min-days-to-expiry are
optional; with neither, it just reports.
- Served over HTTPS as
application/jsonwithAccess-Control-Allow-Origin: *, so the artifacts are fetchable directly from a browser or an edge function. - Dated artifacts (
<agency>/<date>.json) are immutable once written and are retained, so a consumer can pin a specific date as a stable reference.latest.json,catalog.json, anddirectory.jsonare rewritten when a scoring run completes;/api/v1/status.jsonand/api/v1/run-status.jsondisclose the observed freshness and latest completed run. - Each row's
retrieved_at(and a scorecard'sgenerated_at) is the authority on freshness; read it rather than re-fetching on a loop.
The data is scheduled to refresh once a day. There is no value in polling it
more often than that, and the CDN caches for a few minutes regardless.
Consumers should read generated_at/retrieved_at and the status endpoints
rather than assuming the schedule completed or re-fetching on a tight loop.
{ "schema_version": "1.11", "rubric_version": "1.2", "scoring_profile": { "id": "gtfs-scorecard-1.2", "rubric_version": "1.2", "provenance": "GTFS Scorecard's project-authored weights, deductions, thresholds, grade bands, and fix ranking, informed by the California Transit Data Guidelines and the MobilityData gtfs-validator. It is not a worldwide standard or a compliance determination." }, "validator_version": "8.0.1", // the MobilityData gtfs-validator release used "agency": { "id": "barrie-transit", "name": "Barrie Transit (Ontario)", "country": "CA", "subdivision_code": "CA-ON", "subdivision_name": "Ontario", "operating_note": "optional curator-verified status; absent if unset" }, "generated_at": "2026-06-12T13:25:01+00:00", // when this grade was produced (retrieved_at in the catalog) "snapshot_date": "2026-06-12", "feed": { "static_url": "...", "sha256": "...", "size_bytes": 0, "license_note": "..." }, "overall": { "score": 84.1, "grade": "B", // distance to the grade-band edges: points up to the next letter's // floor (null for an A) and points above this band's own floor "margin_to_next_band": 5.9, "margin_to_lower_band": 4.1 }, "fetch": { "source": "origin", // or "mirror" (MobilityData hosted copy); "unknown" for // snapshots downloaded before provenance recording // (fetch policy itself: gtfsscorecard.org/fetcher/) "final_url": "...", // the URL that actually served the graded bytes "user_agent": "...", // the User-Agent presented to that server "max_attempts": 4, // configured attempt ceiling; omitted when unknown "origin_error": "..." }, // exception that forced the mirror; only on mirror fetches "confidence": { "level": "high", // "provisional", "medium", or "high" — a word, never a letter or a number "measured_categories": 4, "total_categories": 4, "fetch_source": "origin", "rt_windows": 1, "feed_age_days": 0, "notes": [ "All four score categories were measured this run.", "..." ] }, "shapes_readiness": { "status": "ready", // or "at_risk" / "not_ready"; US agencies only "detail": "plain language", "fix": "present unless ready", "total_trips": 0, "trips_with_shape": 0 }, "categories": { "correctness": { "name": "...", "status": "measured", "score": 0, "weight": 0.35, "summary": "plain language", "findings": [ /* see below */ ], "details": { /* category-specific */ } }, "freshness": { "...": "..." }, "completeness": { "...": "..." }, "realtime": { "status": "not_yet_measured", "summary": "neutral note", "weight": 0.20 } }, "top_fixes": [ { "rank": 1, "code": "...", "what": "...", "why": "...", "fix": "...", "effort": "...", "severity": "WARNING", "count": 0 } ] }