Usage
Command Line Interface (CLI)
Basic Usage
# Validate local file
gtfs-guru --input /path/to/gtfs.zip --output_base ./report
# Validate from URL
gtfs-guru --url https://example.com/gtfs.zip --output_base ./report
Add --storage_directory /tmp/gtfs to keep a downloaded feed around instead of
re-fetching it.
Output files
Written into --output_base:
| File | Written |
|---|---|
report.json |
always (rename with --validation_report_name) |
report.html |
always (rename with --html_report_name) |
system_errors.json |
always (rename with --system_errors_report_name) |
the name given to --sarif |
with --sarif |
notice_schema.json |
with --export_notices_schema |
--sarif takes a file name resolved inside --output_base. Badges are the
exception: they go to the exact path you name — see
Status badges.
With --stdout, the JSON report goes to standard output and no report files are
written.
CLI Options
| Option | Short | Description |
|---|---|---|
--input <PATH> |
-i |
Path to GTFS zip file or directory |
--url <URL> |
-u |
URL to download GTFS feed |
--output_base <DIR> |
-o |
Output directory for reports (required unless --stdout) |
--stdout |
Write only the JSON validation report to stdout | |
--country_code <CODE> |
-c |
ISO country code (e.g., US, RU, DE) |
--date <DATE> |
-d |
Validation date (YYYY-MM-DD) |
--pretty |
-p |
Format JSON output |
--export_notices_schema |
-n |
Export notice schema to JSON |
--storage_directory <DIR> |
-s |
Save downloaded feed to directory |
--validation_report_name <NAME> |
-v |
Custom name for JSON report |
--html_report_name <NAME> |
-r |
Custom name for HTML report |
--system_errors_report_name <NAME> |
-e |
Custom name for system errors report |
--skip_validator_update |
Skip validator update check | |
--validated-at <TIMESTAMP> |
Override validated_at in report metadata |
|
--threads <N> |
Number recorded in report metadata; does not size the thread pool | |
--google_rules |
Enable Google-specific rules | |
--sarif <NAME> |
Write a SARIF report for CI/CD into --output_base |
|
--fail-on <LEVEL> |
none (default), error, or warning; exit 2 at that severity |
|
--badge <PATH> |
Write a shields.io endpoint descriptor for a README badge | |
--badge-svg <PATH> |
Write a self-contained SVG badge | |
--badge-label <TEXT> |
Left-hand badge text (default GTFS) |
|
--fix-dry-run |
List suggested auto-fixes without modifying files | |
--fix |
Write a repaired copy with the safe fixes applied | |
--fix-unsafe |
Like --fix, but also applies confirm-level and unsafe fixes |
|
--fix-output <PATH> |
Destination for the repaired feed (default <input>.fixed.<ext>) |
|
--thorough |
Enable thorough validation (recommended fields) | |
--timing |
Print timing breakdown | |
--timing-json |
Print timing report as JSON | |
--version |
Print the validator version |
--fix writes a new feed and never changes the input. It safely normalizes
supported field values, trims declared GTFS fields, and sorts stop_times.txt
by trip and stop_sequence. --fix-unsafe can additionally delete rows whose
foreign key references a missing parent. After writing the copy, the CLI
validates it again and reports resolved, remaining, and introduced notices.
Ambiguous values are deliberately left alone — 01-05-2026 (day-first or
month-first?) and 1,500 (1.5 or 1500?) get no suggestion at all. See
Fixes for the notice-by-notice repair table and its safety
levels.
Subcommands
diff — compare two feed versions
gtfs-guru diff old.zip new.zip
gtfs-guru diff old.zip new.zip --json diff.json --markdown diff.md --fail-on-new-errors
The diff covers agencies, routes, stops, route-level trip and frequency
aggregates, and validation notice deltas. Under --fail-on-new-errors it exits
2 when the new feed adds error occurrences. --no-validation gives a faster
structural-only comparison.
profile and explain — deterministic feed facts
gtfs-guru profile -i feed.zip --date 2026-07-27 --pretty
gtfs-guru explain -i feed.zip --date 2026-07-27
gtfs-guru explain -i feed.zip --json --pretty
profile reports unique entity counts, route types, completeness facts, seven
actual service dates with calendar exceptions applied, and exact grouped
validation totals. explain is derived from nothing but that profile, so every
statement can be checked and no feed is sent to an LLM provider.
spec-surface — what this build answers for
Prints the files, fields, enum values, and notice codes the build supports, along with the specification revision and canonical validator release it was checked against.
Which standard a report answers for
Every JSON report's summary states the upstream revisions the build was
aligned with, alongside the validator's own version:
gtfs-guru -i feed.zip --stdout | jq '.summary | {validatorVersion, specRevision, canonicalBaseline}'
{
"validatorVersion": "1.0.0",
"specRevision": "google/transit@3215f98f26615f1b925dca1bf2205311b747e308",
"canonicalBaseline": "MobilityData/gtfs-validator@v8.0.1"
}
specRevision is the GTFS specification commit, and canonicalBaseline the
release of the canonical Java validator, that this build was checked against.
Both are extensions to the canonical report schema. gtfs-guru spec-surface
prints the files, fields, enum values, and notice codes that follow from them.
Parallelism
--threads is report metadata retained for Java compatibility. Set Rayon's
environment variable to control actual parallelism:
Exit codes
| Code | Meaning |
|---|---|
0 |
Validation completed |
1 |
The run failed (invalid arguments, unreadable input, or I/O failure) |
2 |
The feed did not meet --fail-on |
Use --fail-on error in CI. Without it, a completed validation exits 0 even
when the feed contains validation errors.
Status badges
--badge writes a shields.io endpoint descriptor describing
the run:
Publish that file (a gh-pages branch, an object store, anywhere reachable) and
reference it from a README:
The message is valid on a clean feed, 0 errors, N warnings when only
warnings remain, and N errors otherwise; the colour follows. --badge-svg
writes a self-contained SVG for places that cannot reach shields.io, and
--badge-label replaces the GTFS on the left with, say, a feed name.
Pair it with --fail-on none when the badge is the point: a workflow that
aborts on the first error never gets to write one.
Both paths are taken as given rather than resolved against --output_base,
so a badge can be written straight into the directory a workflow publishes,
and they work with --stdout too.
Continuous integration
GitHub Actions
The repository ships a composite action that installs a checksum-verified binary, runs it, uploads SARIF to code scanning, and fails the job on a bad feed:
jobs:
validate:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write # so SARIF reaches the Security tab
steps:
- uses: actions/checkout@v4
- uses: abasis-ltd/gtfs.guru/action@v1
with:
feed: feed.zip
fail-on: error
See action/README.md
for every input, the outputs it sets, and the badge-publishing recipe.
Or drive the CLI yourself:
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install gtfs-guru
run: |
curl -fsSL https://raw.githubusercontent.com/abasis-ltd/gtfs.guru/main/scripts/install.sh | bash
echo "$HOME/.local/bin" >> $GITHUB_PATH
- name: Run validation
run: gtfs-guru -i feed.zip -o out --fail-on error
GitLab CI
validate:
image: ubuntu:22.04
before_script:
- apt-get update && apt-get install -y ca-certificates curl
- curl -fsSL https://raw.githubusercontent.com/abasis-ltd/gtfs.guru/main/scripts/install.sh | bash
- export PATH="$HOME/.local/bin:$PATH"
script:
- gtfs-guru -i feed.zip -o out --fail-on error
Web API
Starting the Server
API Endpoints
GET /healthz- Health checkGET /version- Version infoGET /cors-proxy?url=...- Same-origin remote feed fetch, restricted to public HTTP(S) addresses and bounded by rate, concurrency, timeout, redirect, and size limitsPOST /create-job- Create validation jobPUT /upload/{job_id}- Upload GTFS fileGET /jobs/{job_id}/status- Check statusGET /jobs/{job_id}/report.json- JSON reportGET /jobs/{job_id}/report.html- HTML report