Docs · Publishing & distribution · NTD ID & FTA reporting

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:

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.

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:

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 firesWhat 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:

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.

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

See also