Valid is not the same as right
A format check asks whether every file has the right columns and whether the IDs point at something. It cannot ask whether a bus really drives 9 km in two minutes. Those schedule problems come from wrong times, wrong coordinates or a calendar that stops early, and they reach riders as missing trips, odd arrival predictions and routes that vanish for a week.
The checks below are the ones worth running before you publish a feed and when you inherit someone else's. Each section says what to look for in the raw files, so you can do it with a spreadsheet or a short script, and names the matching finding in Feed Health.
Times that cannot be true
Everything here lives in stop_times.txt. Sort it by trip_id, then by stop_sequence, and read each trip from top to bottom. Compare times as a count of seconds, not as clock text, because a trip that runs past midnight is allowed to use values such as 25:10:00.
Out-of-order times
Along one trip the times must never go down. A later stop that departs earlier than the stop before it means the schedule is out of order, usually a typo in the hour or a row in the wrong place.
trip_id,arrival_time,departure_time,stop_id,stop_sequence
T1,08:00:00,08:00:00,A,1
T1,08:09:00,08:09:00,B,2
T1,08:07:00,08:07:00,C,3
Stop C departs two minutes before stop B. Feed Health calls this Departure times go backwards, and a trip that arrives at a stop before it has left the previous one is listed as Arrives before leaving the previous stop.
Arrival after departure at the same stop
On every row, arrival_time must not be later than departure_time. A negative dwell is impossible. Feed Health lists it as Departs before it arrives.
A blank first or last time
The first and last stop of every trip need both an arrival and a departure time. Blank times in the middle of a trip are allowed only for stops marked timepoint=0, where apps estimate the time. A blank at either end means trip planners cannot place the trip at all, and Feed Health lists it as First or last stop has no time.
Hops faster than the mode allows
For each pair of consecutive stops, take the straight-line distance between them from the coordinates in stops.txt, and divide it by the time from the first stop's departure to the second stop's arrival. Straight-line distance is the shortest possible path, so a road or track can only be longer and the real speed only higher.
For example, two stops 6 km apart that are scheduled 2 minutes apart imply 180 km/h, which no bus does. Feed Health flags Implausibly fast travel when a hop beats the limit for the route's mode: 150 km/h for bus and subway, 100 km/h for tram and light rail, 80 km/h for ferry and 500 km/h for rail. It skips hops shorter than 50 m, and when both times are whole minutes it adds one minute to the travel time, because "10:00 then 10:01" can mean almost two minutes. Do the same by hand, or you will chase false alarms.
When a hop is flagged, the cause is almost always one of two things: a wrong time in stop_times.txt, or a stop whose latitude or longitude is wrong in stops.txt. Look at the map before deciding which.
One vehicle in two places
The optional block_id column in trips.txt says which trips are run one after another by the same vehicle. To check it by hand, group the trips by block_id and by operating day (trips sharing a service_id run on the same days), then sort each group by its first departure. Each trip should start no earlier than the previous one ends. If two trips in a block overlap in time, one vehicle is booked twice. Feed Health lists this under Block integrity as Overlapping trips in block.
If your feed has no block_id column, there is nothing to check here. That is the case for the TIB Mallorca feed used below.
Calendar holes and dates that disagree
These checks use calendar.txt, calendar_dates.txt and feed_info.txt. The calendar basics are in Understanding GTFS Calendars.
- A week or more with no service. Work out the days that have service after exceptions are applied, and look for a run of 7 or more days with none inside the feed's own period. It usually means a missing calendar row or a mistyped date. Feed Health lists it as A week or more with no service.
- The next 7 and 30 days. A feed that covers no days in the coming week gives riders an empty schedule. Feed Health raises No service in the next 7 days when none of the next 7 days has service, and Sparse service in the next 7 days when fewer than 5 of them do.
- feed_info against the calendar.
feed_end_dateinfeed_info.txtshould match the last day of service. Feed Health compares the two (Service after declared feed end, Service before declared feed start), and it reads expiry fromfeed_end_datewhen the feed gives one: Feed expires within 7 days, Feed expires within 30 days and Feed has expired.
Blank direction_id
The direction_id column in trips.txt is optional, but a feed that fills it in on most trips and leaves it blank on others is hard to read. Count the blanks, and count them per route. Apps cannot tell which way a blank trip runs, so they may drop it or count it twice. Feed Health lists Some trips have no direction_id and says that TransitLens places those trips on a direction from their stop list and headsign, so counts by direction rest on that placement.
Shapes and stops
Geometry mistakes do not break a schedule, but riders see them straight away on a map.
- A reversed shape. If the shape starts near the last stop and ends near the first, the route is drawn backwards. Feed Health: Shape runs opposite to stops.
- A shape far from the stops. A shape that ends far from the first or last stop may be truncated, and a stop well away from the line may itself be misplaced. Feed Health: Shape ends far from terminals and Stop far from route shape.
- Near-duplicate stops. Two stops a few metres apart that are not platforms of one station usually describe the same place. Feed Health: Stops likely duplicated.
- Leftovers. Feed Health also lists trips that visit fewer than 2 stops and platforms that no trip calls at, in its Trip data and Stop data topics.
Walkthrough in Feed Health
Feed Health runs on any feed you have open in TransitLens, so there is nothing to set up. It checks for common problems and lists what it finds with a severity and the evidence behind each finding. It is not the official specification check. That is the MobilityData GTFS Validator, and the two work well together.
- Open the TIB Mallorca feed, or load your own. Wait for the import to finish.
- Choose Feed Health in the navigation. The page opens with a score, counts of findings to Fix, Review and note, and a Fix first list of the widest findings across every topic.
- Open a topic card, for example Schedule integrity, Block integrity, Service calendar or Publishing & validity. Topics with nothing to report fold into a Passing row.
- Open a finding group to get a list of the trips, routes or stops involved. You can search it, sort it, group it, hide the ones you have reviewed, bookmark findings and show them on the map.
- Select one to read its evidence, such as the two stops of a fast hop with their distance, scheduled time and the speed limit it breaks.
Basic accounts see 5 evidence rows per topic, and Pro accounts see every finding. Pro also exports the findings as a CSV file.
What the TIB Mallorca feed shows
This walkthrough reflects the snapshot of the feed from 22/04/2026, which has 78 routes and 3,720 trips. The time-based checks come back clean on it, which is a useful result in itself. We recomputed these from the raw files:
- No trip has times that go backwards, an arrival after its departure, or a blank first or last time, and no hop beats its mode's speed limit.
trips.txthas noblock_idcolumn, so there are no vehicle blocks to overlap.- The calendar has no gap of 7 days or more, and every one of the 30 days from 21/10/2026 has service.
feed_info.txtdeclares a period of 01/11/2023 to 21/10/2026, whilecalendar.txtruns to 31/12/2035. That is the kind of mismatch the feed_info checks exist for, and because Feed Health reads expiry from the declared end date, the expiry findings depend on the day you open the feed.- 518 of the 3,720 trips (13.9%) leave
direction_idblank.
The feed is a snapshot, so what you see may differ if you open a newer version. The point of the example is the method: the same questions, answered from the raw files and then in Feed Health.
A pre-publish checklist
- Times never decrease along a trip, and no stop has an arrival after its departure.
- Every trip has a time on its first and last stop.
- No hop between consecutive stops is faster than the mode allows.
- Trips that share a
block_iddo not overlap on the same day. - No run of 7 or more days without service, and the next 30 days are covered.
feed_start_dateandfeed_end_datematch the calendar.direction_idis filled on all trips or on none.- Shapes run in the direction of travel and end at the terminals, and no two stops sit on the same spot.
- Run the official MobilityData GTFS Validator as well, then review the feed in TransitLens.
What this does not do
Feed Health checks for common problems. It does not validate a feed against the full GTFS specification, it does not replace the MobilityData GTFS Validator, and it does not fix anything. Finding a problem is the easy half. The fix still happens in your own files or in the tool that produces them. A clean result means these checks found nothing, not that the schedule is right, and some findings are judgement calls, such as a stop that really is far from its shape.
To keep learning, read How to Read GTFS Data and GTFS Routes, Trips & Stops Explained. To see what changed between two releases of a feed, read Compare GTFS Feed Versions.