NTD ID & FTA reporting
If your agency reports to the National Transit Database, FTA has to be able to match the GTFS feed you publish to the agency that filed the report. GTFS·X carries your NTD ID through the feed you publish, warns you before you break the link, and lays out exactly the values FTA's P-50 form asks for.
What an NTD ID is, and why your feed cares
Your NTD ID is the five-digit number FTA uses to identify your agency in the National Transit Database (for example 00123 or 90157). If you receive FTA formula funds, you already have one, and you already report under it every year.
Until recently that number had nothing to do with your GTFS feed. That changed. FTA now wants to connect the two, so that the schedule data riders see and the service data you report are demonstrably the same agency's. The mechanism is the enhanced P-50 form (Transit Agency Identification), which collects your published feed's URL alongside the agency_id values inside it, and crosswalks them to your NTD ID.
Two consequences follow, and they're the reason this page exists:
- Your
agency_idvalues matter now. FTA's crosswalk is matched on them. In its July 10, 2025 final notice (FR 2025-12813), FTA madeagency_idnon-conditional for NTD reporters — meaning always required, including inroutes.txt. The GTFS spec itself is looser: it lets a single-agency feed leaveagency_idout entirely. So a feed that is perfectly valid GTFS can still be unusable for your NTD reporting. - Your feed URL matters now. FTA needs a stable one. That's what hosted publishing gives you.
FTA did not require agency_id to equal your NTD ID. That was proposed and then withdrawn in July 2025. You do not need to renumber your agency, your routes, or anything else in your feed. Keep the agency_id values you already have — stability is what FTA needs, not a particular value.
Setting your NTD ID
The NTD ID belongs to the agency, not to the project, so that is where you set it. Open the Agency panel and fill in the field labelled NTD / External ID. That's the only place it lives, and everything else on this page follows from it: the export column, the published metadata, the DMFR file, and the P-50 helper all read it from the agency.
It's optional. Leave it blank if your agency doesn't report to the NTD (most Canadian, tribal, and private operators don't) and nothing on this page applies to you.
The field is deliberately free-form. It's called "NTD / External ID" rather than "NTD ID" because it accepts any external identifier your agency is known by, not only an FTA one, and because GTFS·X does not enforce a five-digit shape on it. Type the identifier exactly as the issuing body writes it.
Leading zeros are part of the ID. If your NTD ID is 00123, type 00123 — not 123. GTFS·X treats the value as text end to end and never drops a leading zero, but a spreadsheet will, so double-check whatever you copied it from.
Feeds with more than one agency
Each agency in a feed carries its own NTD ID. That's the right model: if three operators share one feed and each reports to the NTD separately, each row of agency.txt gets its own identifier, and the export, the published metadata, the DMFR file, and the P-50 helper all keep them distinct.
You set them the same way you set the first one. Once a feed has more than one agency, the Agency panel shows an Editing dropdown at the top: choose the agency you want, and the fields below it — including NTD / External ID — are that agency's. Work through the operators one at a time and each ends up with its own ID. Add an operator with + Add Agency at the bottom of the panel; see Agency setup for the full joint-feed workflow, including assigning each route to its operator.
IDs can also arrive by import: put an external_id column on the agency.txt of the feed you import and every agency's value is read in, kept through editing, and written back out. Either way, each agency's ID stays distinct all the way to the published feed.
Where your NTD ID ends up
Once an agency has an NTD ID, it shows up in three places. The first is inside the feed itself; the other two are the machine-readable files published alongside it.
agency.txt, in every export — as anexternal_idcolumn, one value per agency row. This is a provisional extension column, described below.feeds.gtfsx.com/<slug>/feed_info.json— the sidecar metadata file that already carries your publisher name, service dates, and version. It now also lists every agency in the feed, each with itsagency_id,agency_name, andexternal_id. Monitoring scripts and catalog ingestors read this.feeds.gtfsx.com/<slug>/dmfr.json— a registry document, described next.
An agency with no NTD ID simply has no external_id in the listing, so a consumer sees "not declared" rather than a blank value.
The DMFR file: getting into Transitland and the Mobility Database
Every published GTFS·X feed now also serves a DMFR document at https://feeds.gtfsx.com/<slug>/dmfr.json. DMFR (Distributed Mobility Feed Registry, v0.5.1) is the format Transitland and the wider open-transit-data ecosystem use to describe "here is a feed, here is who operates it, here is the license." It's a small JSON file, generated for you, and it needs no configuration.
Why you'd want it: getting listed in the Mobility Database and Transitland is how trip planners, researchers, OpenTripPlanner deployments, and app developers discover your feed in the first place. Historically that means filling in a form and hoping someone types your details correctly. With a DMFR file you hand over one URL and the registry has everything it needs, already structured:
- Your feed URL, and your GTFS-Realtime URLs if you've registered any (vehicle positions, trip updates, alerts) — they appear as a companion realtime entry.
- An operator record for each agency in the feed, tagged
us_ntd_idwith that agency's own NTD ID, and tied back to itsagency_idinside the feed.us_ntd_idis the tag the Transitland atlas uses, so the NTD crosswalk survives into the catalog instead of being lost at the door. Because each operator names the agency it describes, a feed carrying several NTD reporters crosswalks correctly rather than ambiguously. - Your license, if you declared one.
Publishing the file doesn't submit you to anything. It's a document you can point a registry maintainer at, or attach to a submission form. GTFS·X's own one-click Mobility Database submission (in the distribution checklist) is unchanged and still the easiest path.
Declaring a feed license
In Share & Publish (the footer tab) you can declare the feed's license from a short list — CC0 1.0 (public domain), CC BY 4.0 (attribution), or ODbL 1.0 (share-alike) — or leave it unspecified. Whatever you pick is published in feed_info.json and the DMFR file as its standard SPDX identifier, so a reuser can see the terms without emailing you. GTFS·X never guesses a license on your behalf; unspecified stays unspecified.
The agency_id warnings
Because FTA's crosswalk is matched on agency_id, the validator now flags feeds that don't have usable ones. Both messages are warnings, not errors — they never block an export or a publish — and both are dismissible from the Validation panel if you don't report to the NTD.
| When it fires | What it means |
|---|---|
Your feed has more than one agency, and an agency or a route has no agency_id. |
This is a genuine GTFS defect, not just an NTD one: the spec requires agency_id once a feed contains multiple agencies, in agency.txt and in routes.txt. You get one message per offending row, so you can click straight through and fix each. Give each agency a stable id (MTA, SVT) and point every route at its operator. |
Your feed has one agency, and agency_id is blank on the agency or on some routes. |
Advisory. The GTFS spec allows this, so nothing is broken — but FTA's July 2025 notice made agency_id non-conditional for NTD reporters, including in routes.txt, and FTA cannot crosswalk your feed to your NTD ID without it. You get a single feed-level message (never one per route). If you report to the NTD, set an agency_id on the agency and on every route. If you don't, dismiss it. |
The fix is cheap and you only do it once. Pick a short, meaningful id, and then leave it alone forever — which brings us to the next section.
Don't change your agency_id after you publish
Renaming or removing an agency_id that's already in your published feed silently breaks things. FTA's crosswalk stops matching. So does every downstream consumer keyed on that id — trip planners, your own analytics, a realtime producer someone else operates.
So when you publish a snapshot that drops or renames an agency_id present in the currently-published feed, GTFS·X stops and shows you exactly which ids are disappearing. You have two choices:
- Go back and keep the ids. Almost always the right answer. If what you actually wanted was to rename the agency as riders see it, change
agency_nameand leaveagency_idalone — the two are independent, and only the name is rider-facing. - Publish anyway. Sometimes the change is deliberate (a merger, a genuinely wrong id set years ago). The warning is an acknowledgement, not a block. If you take this path, re-file your P-50 with the new ids so FTA's crosswalk catches up.
This check runs on every published feed, whether or not you have GTFS-Realtime. It's separate from the realtime ID-stability check, which covers route_id, stop_id, and trip_id as well.
Filing the P-50: the helper panel
Once a feed is published, Share & Publish shows an NTD / FTA P-50 reporting panel. It's a transcription aid: it lays out, copy-ready, precisely the values the enhanced P-50 form collects, so you're not digging through your feed with a text editor at 4pm on a filing deadline.
- The canonical feed URL — the stable
feeds.gtfsx.comaddress that keeps working across re-publishes. This is the URL to give FTA. - Every agency in the feed, one row each: its
agency_id, itsagency_name, and its NTD ID. Every value has its own copy button, and any agency still missing an NTD ID is called out so you notice before you file rather than after.
Copy each value across as the form asks for it. If several of the agencies in your feed report to the NTD separately, the panel gives you each one's ID next to its agency_id — which is exactly how the form wants them. Nothing is submitted to FTA on your behalf; the P-50 is filed by you, in FTA's reporting system, during your reporting cycle.
The external_id column on agency.txt (provisional)
external_id is not part of the GTFS specification. It is a provisional extension column we ship so agencies that want the NTD ID inside the feed itself can have it today. A proposal for a standard field is pending; if and when one lands, the column name will change to match it. Track it at GTFS-X/gtfsx#62. Consumers that don't recognise the column ignore it, as GTFS consumers are expected to do with any column they don't know.
Background: when FTA proposed forcing agency_id to equal the NTD ID, commenters objected and asked instead for a separate, dedicated field, so that existing agency_id values could stay put. FTA withdrew the requirement. No standard field exists yet, so external_id fills the gap in the meantime. The name is generic on purpose: the column carries whatever external identifier the agency is known by, and an NTD ID is the common case rather than the only one.
There is nothing to switch on. Whenever at least one agency in your feed has an NTD / External ID, your export's agency.txt carries an external_id column with each agency's own value in its own row. When no agency has one, the column isn't written at all: the ZIP is byte-for-byte what it would otherwise be, with no empty column and no header change. The Export dialog has no NTD setting to find, because there's no decision left to make there.
The column round-trips. Import a feed whose agency.txt carries external_id and GTFS·X reads each row's value onto that agency, holds it through editing, and writes it back out on export. Nothing is silently dropped, and a multi-agency feed keeps each agency's own ID distinct all the way through.
Looking up another agency's NTD ID
The GTFS Feed Health dashboard's state-by-state agency view now shows each agency's NTD ID next to its Mobility Database feed ID. Handy for state DOTs reconciling a sub-recipient list, and for anyone who needs to confirm which NTD ID belongs to which operator before filing.
Edge cases and gotchas
- Once set, the NTD ID goes into your GTFS ZIP. There's no separate opt-in, so if you specifically need an export with no extension columns in it, clear the NTD / External ID field before exporting.
- Setting an NTD ID does not file anything with FTA. The P-50 is still yours to submit. GTFS·X gets the values right and keeps them stable; the reporting cycle is between you and FTA.
- Changing your feed's slug breaks the URL you gave FTA. The same caveat as any catalog submission — see hosted publishing. Pick a slug you can live with before you file.
- Importing a feed replaces the NTD IDs. Import is a whole-project replace, so importing a feed whose
agency.txthas noexternal_idcolumn clears whatever IDs were set. Re-enter them if you're importing into an existing published project. - On a joint feed, set an ID for every agency that reports. Switch between them with the Editing dropdown in the Agency panel (see the multi-agency note above). An agency you skip simply has no
external_id, and FTA has nothing to crosswalk it on. - Dismissing the
agency_idwarnings is safe if you don't report to the NTD. They're advisory for non-reporters. The multi-agency one, though, is a real spec violation regardless of NTD — fix that one.
See also
- Hosted publishing — the stable feed URL FTA needs, and the distribution checklist.
- Agency setup — the Agency panel, where
agency_id,agency_name, and the NTD / External ID live. - Validation — how warnings and dismissals work.
- Import & export — the
external_idcolumn, read on import and written on export. - National Transit Database — look up your NTD ID and the P-50 reporting cycle.
- FTA final notice, July 10 2025 (FR 2025-12813) — the reporting changes behind all of this.