StopPoint retrieval by multiple ids

Hi, StopPoint API supports retrieval by multiple ids supplied separated by coma (“Max. approx. 20 ids” to be specific according to documentation). However response itself does NOT include id itself - how should one map input ids to result objects returned (which one is the one)? Only thing I can think of is ensured order however that’s not the best approach and it’s not documented. Thanks

Welcome @dmp-external-service

Given you can do up to 500 a minute, the thing probably to do is to write yourself some rate-limitation code and pick the items off one-by-one.

Sleep for 0.002s

Well that’s an obvious “brute force” approach - I was hoping to save on those requests and take off some load from API as well given it does support “batch mode” (at least in theory as there is an issue with it described here).


I just make parallel calls using curl_multi_exec

Hi, each StopPoint object has an id property. However, StopPoints are actually a tree (so a given StopPoint can have multiple children, each of which can have its own children). The Unified API will provide the entire tree; therefore if you wish to locate a specific node, you will need to traverse the tree recursively to find it.

I put together the following JavaScript snippet as an example of how to do this. Note that the Unified API will sometimes return just a single StopPoint object and other times it will return an array of StopPoint objects. The array test at the top of the function is to handle both scenarios.

function getStopPointById(stopPointResults, id) {
    if (!Array.isArray(stopPointResults)) {
        stopPointResults = [stopPointResults];
    for (const stopPoint of stopPointResults) {
        if ( == id) {
            return stopPoint;
        if (stopPoint.children) {
            const result = getStopPointById(stopPoint.children, id);
            if (result) {
                return result;

I hope this helps. Please let me know if you have any questions. :slight_smile:

Hi, thanks for your help. Could you help me understand what a StopPoint is exactly and how API works - I was under impression that I can get a list of “stops” from and ATCOCode would be StopPoint id I would pass to the API (mainly interested in London’s area if that is significant). And it seems to be working for single items. As API accepts multiple ids its more efficient both from client & server side to use batch requests however in case of batch request it’s not obvious on how to “interpret” results - specifically which response item corresponds to which request id parameter.
Take for example request,490000002A,490000002B - in response I get only 2 records of which first on has id property not matching to supplied ones while another no id property in response at all while no third record at all (while single call to is successfull).

Hi, I agree that it is better to batch requests, so you are taking the right approach.

A StopPoint is a public transport access node, however there are different types of StopPoint representing different levels of hierarchy. For example, you might have a StopPoint representing a Tube station, but that StopPoint may include child nodes to represent the station entrances, the platforms, or even bus stops outside the station.

You can find more details in the NaPTAN schema (PDF), but please note that whilst it makes a distinction between StopPoints and StopAreas, the Unified API calls them both StopPoints1.

Let’s take a look at your example request:,490000002A,490000002B

It returns an array with two objects. If you look at the id properties of the two objects, you’ll see they have IDs of 940GZZLUACT and 940GZZLUKPK. Neither of these IDs match your original input, however if you look at the children properties, you’ll see they actually represent two trees of StopPoints.

To make things easier to understand, I shall represent the two trees as a hierarchy of bullet points. Each bullet point is a StopPoint showing its id and stopType properties.

  • 940GZZLUACT:NaptanMetroStation
    • 4900ZZLUACT1:NaptanMetroEntrance
      • 4900ZZLUACT2:NaptanMetroEntrance
        • 490G0000S:NaptanOnstreetBusCoachStopCluster
          • 490000002A:NaptanPublicBusCoachTram
            • 490000002B:NaptanPublicBusCoachTram
              • 490000002RB7:NaptanPublicBusCoachTram
                • 49000002SE:NaptanPublicBusCoachTram
                • 9400ZZLUACT:NaptanMetroAccessArea
                  • 9400ZZLUACT1:NaptanMetroPlatform
                    • 9400ZZLUACT2:NaptanMetroPlatform
                      • 9400ZZLUACT3:NaptanMetroPlatform
                        • 9400ZZLUACT4:NaptanMetroPlatform
                        • 940GZZLUKPK:NaptanMetroStation
                          • 4900ZZLUKPK1:NaptanMetroEntrance
                            • 490G00127KB:NaptanOnstreetBusCoachStopCluster
                              • 490000001:NaptanPublicBusCoachTram
                                • 490000127KA:NaptanPublicBusCoachTram
                                  • 490000127KB:NaptanPublicBusCoachTram
                                    • 490000127N:NaptanPublicBusCoachTram
                                      • 490000127RB:NaptanPublicBusCoachTram
                                      • 9400ZZLUKPK:NaptanMetroAccessArea
                                        • 9400ZZLUKPK1:NaptanMetroPlatform

                                        You’ll see that if you navigate the trees, you’ll find all three StopPoints that you are looking for, which I have highlighted in bold. The reason why you only get two items in the array is because 490000002A and 490000002B share a common ancestor (490G0000S) so they belong to the same tree (which is only returned once), whereas 490000001 belongs to an entirely separate tree.

                                        You can use the technique I described earlier with my JavaScript function to find the specific nodes of the tree you are looking for. To retrieve the three StopPoints from the array, I can call the function three times:

                                        getStopPointById(stopPointResults, "490000001")
                                        getStopPointById(stopPointResults, "490000002A")
                                        getStopPointById(stopPointResults, "490000002B")

                                        I realise this is not particularly straightforward, but I hope this helps. Do let me know if it still doesn’t make sense.

                                        1 Pedantic point: a StopPoint in the Unified API can also represent a hub, which is a special construct not present in NaPTAN, which is how we group multiple StopPoint trees together.

                                        Thanks so much for a MVP level answer! :slight_smile: It clears a lot those “muddy waters” in my understanding of before. Could you clarify if id and naptanId are interchangeable (same value) as from empirical testing I see same values. This would save me call’s in some cases and could simply use ATCOCode instead of making calls to StopPoint API to retrieve ones

                                        1 Like

                                        It’s difficult for me to say with complete certainty, but I believe that for StopPoint objects, id and naptanId are indeed interchangeable, and (other than hubs) they map to ATCO codes in the NaPTAN dataset.