API changes, newest first
Ask for users from a particular region, city or postcode.
?state, ?city and ?zip. Alongside ?nat, a request can name the region, the city, the postcode, or any combination, and every user comes back living there - region, city, postcode, dialling code, timezone, street and coordinates all from that one place, and a named postcode is the one on the address. ?postcode is an alias for ?zip. Text is matched exactly but without regard to case or accents (kobenhavn finds København); postcodes without regard to spaces, hyphens or case, and a partial code matches the codes it starts - AB10 finds Aberdeen's AB10 addresses, M5V 2T6 finds Toronto's M5V area. A filter that matches nothing - a misspelt city, a code from another country, a city put in the wrong region - is not an error: the request returns the ordinary random addresses for that nationality, exactly as ?nat= alone would. Without ?nat the filters are ignored, and with several nationalities each user's filter applies to the country it was drawn from. The filtered draw is one draw, like the unfiltered one, so a seed with the same filters always yields the same users, and a request without filters is byte-identical to before. Documented on the documentation page and in the README; the spec asserts the filters for every nationality, the fallback, and that the seeded output is unchanged.Every address now names a street that exists in its city, and its coordinates fall inside that city - for all 22 nationalities.
Valwood Pkwy in Springfield, Ahornweg in Hamburg - everywhere except the Philippines and Singapore. Every location record now carries streets, an evenly spread sample of up to 60 named roads inside that city, read off OpenStreetMap by the new tools/import-streets.js, and the generator draws from it. The import placed all 1,413 cities of the 19 nationalities and gave streets to 1,408 of them; the five it did not - Ağrı and Hakkâri in eastern Turkey, Itanagar, Silvassa and the island of Kavaratti in India - have fewer than five named roads on the map, keep the generic pool, and are listed as the exceptions in the spec. Across all 22 nationalities that is 1,559 of 1,564 records with streets, 90,520 streets in all.tools/import-streets.js. One import for the 19 nationalities that had no streets, and the centre for the two that did. For each record it finds the city on Geofabrik's extract of the country: its administrative boundary where one is drawn under that name, or the place node marking the town centre where none is - Australian LGAs and Turkish districts are not what a postal address follows, so those always use the centre. Same-named places are told apart by the region's boundary and by the record's own postcodes, since OpenStreetMap tags buildings with addr:postcode and the buildings carrying this record's codes stand in this record's city: Springfield, Illinois is the Springfield inside Illinois, and Newport in Wales the one around NP19. Inside a large boundary the streets come from within 12 km of the centre, so Anchorage's 5,000 km² municipality yields Anchorage's streets. What each country looks like on the map - the admin levels its cities and regions sit at, prefixes such as "City of" and "Kommune", spellings that differ - is a table at the top of the script; Ukrainian and Serbian names are transliterated into the Latin alphabet those datasets use; and a city it cannot place is listed with the names the map has nearby. The extracts total 44 GB and each scans in minutes; a cache beside each extract makes every later run and probe a matter of seconds.location.coordinates was a uniformly random point on Earth - a Danish user in the Southern Ocean, noted as a known limit since 1.4.15. Every record now carries its city's centre (lat, lon) and a radius r in km - the boundary's, or a few km around the town centre - and the point is scattered evenly within that disc. This covers the Philippines and Singapore too.street.name and coordinates moves for a given seed. The coordinates are built from the same two draws faker always made and move nothing either. Measured against the build before: for a fixed seed, PH and SG users are byte-identical apart from the coordinates, and users of a nationality whose records have not been imported yet are byte-identical altogether./nats is sorted by country name. It used to return the generator's own order, which is the seed order - Turkey before Ukraine before the United States, then Singapore last - and read as arbitrary. The response is now alphabetical by country; the generator's list is untouched, so every seed still resolves to the nationality it did. The spec now compares the codes as sets and checks the country order, and the SG-last check reads the generator's list directly....21 asks for position 33 of 22. That left nat undefined, and the TypeError in randomName was an unhandled rejection that ended the process - every version from 1.0 up, and found by exactly that request against production. The position is now treated like an unknown nat and a random one is drawn, still from the seed, so the request stays repeatable. One spec assertion per version.Singapore joins as the 22nd nationality, built from the map rather than by hand, and new nationalities no longer shift the seeds of old ones.
SG). city is one of 39 URA planning areas and state its region - Central, East, North, North-East or West - because Singapore has no other division a postal address could name. Every record comes from OpenStreetMap through the new tools/import-sg-streets.js: the planning area's boundary, up to 40 real six-digit postal codes from the buildings inside it, and up to 60 real road names inside it, so a Toa Payoh address carries a 31 code and a Toa Payoh street, not a sector recalled from a table. Sixteen planning areas nobody lives in - the port, Tuas, the air bases, the reservoirs, the islands - are matched and reported but not written. Names are Chinese, Malay and Indian in roughly census proportion, and always whole: a Malay name is a given name and the father's joined by bin or binte, an Indian name is drawn from its own lists, and the email follows the name the user ends up with. id is an NRIC whose check letter validates, S before 2000 and T from it, with the birth year in the first two digits for anyone born since 1968. Landlines are 6xxx xxxx, mobiles 8xxx xxxx or 9xxx xxxx. Six spec assertions cover the NRIC, the phone shapes, the whole-name rule and the list order, on top of the coherence suite every locations.txt is held to.tools/import-sg-streets.js reads Geofabrik's Malaysia-Singapore-Brunei extract (about 250 MB, there is no Singapore-only file) with the PBF reader the Philippine import uses, keeps a 6 MB cache of what it needs, and writes locations.txt and cities.txt in about fifteen seconds. Only boundary=administrative relations count, since Changi's electoral division is a political boundary of the same name and is larger than the planning area. A postal sector on fewer than four buildings in an area is a mistyped tag and is dropped - Bedok has 1,590 codes and one says 14, which is Queenstown - but the floor is a count, not a share, because a real sector can be a small slice of a large area. Expressways, flyovers, the causeway and the checkpoint lanes are not streets and are left out.NAT_ORDER in api/1.4/api.js now fixes the first 21 in the order they had, SG is 22nd, and a folder not on the list is still picked up after them. Every existing seed keeps naming the country it did. A seeded request that names no nationality can still land on a different one, since the random draw now has 22 options - that part is unavoidable.The API can now say which nationalities it offers.
GET /api/<version>/nats, and GET /api/nats for the latest version, return the codes a version generates and the country each stands for: {"nats": [{"code": "AU", "country": "Australia"}, ...], "info": {"results": 21, "version": "1.4"}}. Until now the only way to learn the list was the documentation page, and an unknown nat is silently replaced with a random one, so there was nothing in a response to learn from either. The list is read from the generator's own nats, the same array that validates the parameter, so it cannot drift from what nat accepts. Versions 1.0 to 1.2 predate fullNatName and borrow the latest version's mapping, whose codes are a superset of theirs. A legacy 0.x version or one that does not exist answers 404. Documented under Nationalities, and covered by seven new spec assertions - one per version, one for the unversioned route, one for the 404 - the per-version check also fails if a code has no country name, which is the mistake that leaves location.country undefined.The street import no longer queries the public Overpass API. It reads Geofabrik's extract of the Philippines from a local file, and a full run takes two minutes instead of two hours.
504 - The server is probably too busy to everyone. A run that asked it a few questions per city took two hours and still came up short, and routing it through a residential proxy only added a twelve-second cut-off of the proxy's own. tools/import-ph-streets.js now downloads philippines-latest.osm.pbf (about 600 MB) into tmp/ once, scans it with a dependency-free PBF reader (tools/lib/osm-pbf.js) in about a minute, and keeps the part it needs — the boundaries as rings, the place nodes, one point per named road — in a 60 MB cache beside it. Every run after that loads the cache and does all 112 cities in about two minutes, with no rate limit, no retries and no network beyond the PSGC list. --update fetches a fresh extract. The full run placed streets for all 112 cities on the first attempt. OVERPASS_URL, OVERPASS_PROXY, CONCURRENCY, REQUEST_DELAY_MS, OVERPASS_TRIES and FETCH_TIMEOUT_MS are gone with the endpoint, and so is the undici dev dependency the proxy needed.Cebu City was searched as Cebu City City. The name forms were built by appending to the record's name, and five records already carry the suffix. The suffix is stripped first, so Cebu, Cebu City and City of Cebu are all tried.Bgy. No. 40, Balatong; OSM has Balatong on one node and Barangay 40 on another. Neither the letters nor the sound of those meet, and Laoag rejected all 105 of its place names. A numbered name is now indexed by its number and by the name after it, and an OSM name can be matched by either — only where the number belongs to exactly one barangay in the city, and never from a Purok 3.Barangay IV, Barangay 40 Balatong, Barangay Zone 2. Names such as Zone 2 Pob. (Consiliman) are also matched by either half, since OSM has Zone 2 on one node and Consiliman on another.The import was reporting the wrong cause, and half the country has no municipal boundary to find.
admin_level=4, exactly as queried — and Bangued has no boundary object in OSM at all, neither relation nor way. The query now emits out count for the province and the city separately, so the three cases are told apart and named correctly.A radius fallback for municipalities with no boundary. Much of the Philippines is mapped with a town place node and no administrative outline, which is why Bangued, Kabugao, Lagawe, Tabuk, Bontoc, Cabarroguis and a dozen others returned nothing however the query was phrased. Where no boundary exists the town's place node is used and everything within TOWN_RADIUS_M (default 6 km) is collected:
Bangued, Abra no boundary in OSM - used 6 km around the town centre
The PSGC check still applies, so a neighbouring municipality's barangays are rejected and its streets cannot arrive under one of its own barangay names. Cities handled this way say so on their line.
Making the street import survive a full run against a public endpoint.
out geom and then used a single representative point from it, so Davao City shipped ten thousand named roads with every node of every one — megabytes per city, and the reason ten cities died on 504 after 4 tries. out center returns what the matcher actually needs. A road long enough for the bounding-box centre and the midpoint to differ crosses several barangays anyway, so no meaningful accuracy is lost.fetch failed and terminated are the connection dropping under load, and they lost Bacolod and Calbayog outright while an equivalent 504 was retried. Both now back off like any other transient failure. Attempts per city go from 4 to 6, and the pause between cities from 2s to 3s — OVERPASS_TRIES and REQUEST_DELAY_MS override.--missing, which retries only the cities that came away with no streets. After a partial run that is the whole remaining job, and it costs a few minutes instead of twenty-five.Three defects the first full run exposed.
A country-wide fallback could file another town's streets under a real city. When a province boundary was missing from OSM the search widened to the whole country, which is safe for Baguio and not remotely safe for San Jose: the first run returned 653 roads for a municipality of twelve barangays, several San Joses unioned into one answer. The widening now happens only when the PSGC lists that name once nationally. Anything else is skipped and reported, because wrong streets filed under a real city are worse than none:
skipped: "San Jose" exists in more than one province and Dinagat Islands's
boundary is not in OSMprovince boundary not found — Abra, Ilocos Norte, Kalinga, Eastern Samar, Cebu and many more — and fell through to the country-wide path unnecessarily. Provinces are now resolved as a relation and mapped to an area, which works whether or not Overpass happens to hold a matching area object.name and the English form only in name:en. The search now covers name, name:en, official_name, alt_name and int_name.City of Isabela against Isabela.Two bugs that would have filed streets under the wrong town.
San Fernando appears twice in this dataset — La Union and Pampanga — and around twenty municipalities are called Santa Cruz. The Overpass query matched by name nationwide, so one query found several towns, unioned their roads, and both records would have received the same streets drawn from the wrong provinces. Santa Cruz, San Jose, Santiago, Naga, Bontoc, Tubod, Romblon and Siquijor were all exposed to it. Every lookup is now scoped to the record's own province, and the same narrowing is applied to the PSGC side by province or region code. Where the province boundary cannot be found the search falls back to the whole country and says so on the line, rather than silently returning nothing.San Fernando against the PSGC's name for the same place, City of San Fernando, and found nothing — so the barangay list came back empty and the names went unverified. That is the failure mode that looks like success: the import would have run to completion having checked almost nothing. Entries are now indexed under every written form (City of X, Municipality of X, X City, X).--probe <city> [province], since --probe "San Fernando" is ambiguous. Naming a city that exists twice now says which provinces it is in instead of quietly reporting on the first.The city lookup asked Overpass to scan the planet.
area["name"~"^Kalibo( City)?$", i], against every administrative area on Earth, so the server had to walk the lot and gave up. It is now three exact names unioned — Kalibo, Kalibo City, City of Kalibo — restricted to relations inside the Philippines and mapped to an area. An exact tag value is indexed, so it returns in a moment.A 504 was reported as "busy". It is not: Overpass sends 429 when it is rate limiting and 504 when the query itself ran out of time, and the response body says which in plain words. The body was thrown away. Both are now named correctly and the reason is printed with them:
query timed out - runtime error: Query timed out in "recurse" at line 4 after 120 seconds., retrying in 10s
A day of this would have looked like a busy server rather than a query that needed rewriting.
Spelling variants no longer cost a city its real barangays.
Matching on the letters alone threw away real barangays. A probe of Kalibo dropped Andagao, Bachao Sur and Bakhaw Norte as "not barangays". All three are, and the PSGC simply spells them Andagaw, Bachaw Sur and Bachaw Norte — three of the town's sixteen lost to orthography, alongside the subdivisions the check is meant to catch.
Filipino writes the same name several ways and OSM and the PSGC routinely disagree, so the comparison now also folds the Spanish-era consonants (ch/kh/qu/c → k, v → b, f → p, z → s, j → h) and the vowel pairs the language treats as one sound each (o/u, e/i, w/u, y/i). The result is stored under the PSGC's spelling, never OSM's.
Bachaw Norte and Bachaw Sur stay distinct. Busuang stays rejected on purpose: it could be Buswang New or Buswang Old and there is no way to tell which.Every run lists the variants it accepted, so the fuzziness is auditable rather than invisible:
spelling variants accepted (3): Andagao = Andagaw; Bachao Sur = Bachaw Sur;
Bakhaw Norte = Bachaw Norte
not barangays, dropped (6): Baybay; Ibog; Kawatihan; Magdalena Village;
Mercedes Village; PerazvilleBarangay names are now checked against the official list.
OpenStreetMap place nodes are not all barangays, and the importer was calling them one. A probe of Kalibo returned 23 place nodes for a town with 16 barangays, and the extras came through as addresses like:
Aguho Street, Barangay Magdalena Village <- a subdivision Aklan West Road, Barangay Purok 4 <- a purok, which sits INSIDE a barangay
Wrong in a way any Filipino reader would catch immediately. Every name is now checked against the PSGC, the Philippine Statistics Authority's official list of barangays for that city, and anything not on it is dropped. The run reports what it rejected.
Barangay/Brgy. prefix, and falls back to comparing sorted words — so OSM's "Old Buswang" matches the official "Buswang Old" and is stored under the official spelling rather than creating a second barangay that does not exist.The run and the probe report verification, so a city whose names could not be checked is visible rather than quietly guessed at:
Kalibo: 16 barangays on the PSGC list; OSM gave 0 verified boundaries and
5 verified place nodes; 5 named roads, 5 placed
not barangays, dropped (3): Magdalena Village; Purok 4; Sitio MahigpitIf the PSGC list cannot be reached the import still runs, says so, and leaves the names unverified — degrading rather than stopping.
The Philippine street import asked OSM the wrong questions and got nothing back.
admin_level 7 or 8, which is the European convention. In the Philippines the country is 2, the region 3, the province 4, the city or municipality 6, and the barangay 10. Bangued, Butuan, Prosperidad and Kalibo all reported 0 barangays, 0 named roads — not missing data, a query that matched nothing. Now 6|7|8, since a few cities really are tagged 7 or 8, and nothing at 5 or above, which keeps a province out even where it shares its capital's name (Cebu, Iloilo).Barangay boundaries are not drawn for much of the country. Legazpi resolved fine and returned 394 named roads, then placed none of them, because it has no admin_level=10 polygons. The importer now asks for barangay place nodes as well and falls back to the nearest one, capped at NEAREST_BARANGAY_M (default 3000 m) so a road cannot be bound to a barangay across the municipality. Every run reports how many roads were placed each way, so the approximation is never silent:
[ 4/112] Kalibo: 60 kept of 214 — 0 inside a boundary, 214 by nearest place
node (0 boundaries, 17 place nodes, 231 roads)Boundary containment is still preferred wherever boundaries exist.
place=barangay was not requested. OSM uses it in the Philippines alongside village and suburb, so asking only for the generic values missed barangays that are mapped with the local tag.--probe <city>, which reports what OSM actually holds for one city in a few seconds and writes nothing — enough to tell a name that did not match from a city with no barangay data, without committing to a 25-minute run.OVERPASS_URL can point at a mirror instead.Philippine addresses can now carry a real street and the barangay it runs through.
tools/import-ph-streets.js. A Philippine address is close to undeliverable without the barangay — street names repeat across barangays inside one city, and the barangay is what a courier sorts on. The importer runs one Overpass query per city, collecting the barangay boundaries and the named roads, then works out which barangay polygon each road actually sits in and stores the pair:
before 7757 Legarda Street, Legazpi, Albay 4500 after 5979 Mabini Street, Barangay San Roque, Legazpi, Albay 4500
The matching is point-in-polygon against the barangay boundary, with multipolygon relations stitched from their outer members — hand-rolled, so the script adds no dependency. A road it cannot place inside a barangay is dropped rather than guessed at.
streets array on PH location records, and the PH inject draws street.name from it. The generator still makes its usual draw against the generic pool first and the inject overrides the value, so the random sequence — and therefore every other field — is untouched.location.street.name, and only for Philippine cities the import reached. Measured: with street removed from the comparison, a fixed seeded page is byte-identical before and after for all 21 nationalities.8929 Valwood Pkwy is. README.md says so where someone will see it before running the import.The timezone had nothing to do with the address.
location.timezone was an independent random draw. api/1.4/api.js took it from the 30-entry common list before the location was chosen, so it agreed with the address only by coincidence — measured over 300 users per nationality:
DK 1% correct Aarhus -> "Alaska", "Tehran", "Kathmandu" RS 1% correct Niš -> "Hawaii", "Bangkok, Hanoi, Jakarta" GB 2% correct Manchester -> "Azores, Cape Verde Islands" US 21% correct (widest, because the US spans six of the list's zones)
Every record in every locations.txt now carries a tz offset, and the generator looks that offset up in the common list to recover the description. All 21 nationalities now match their own address 100% of the time.
Region-level for the twenty-one countries whose regions each sit in one zone, with two per-city exceptions where a line cuts through a region: Rapid City is Mountain while the rest of South Dakota is Central, and Ciudad Juárez kept US-aligned time when the rest of Chihuahua moved to Central in 2022. France's overseas départements (Guadeloupe, Martinique, Guyane, Mayotte, La Réunion) and the Canaries are handled by region.
location.timezone changes for every nationality. Nothing else does. The draw that used to pick the timezone is still made in the same place and is simply overridden, so the random sequence is untouched: with timezone removed from the comparison, a fixed seeded page is byte-identical before and after for all 21 nationalities. Measured, not assumed.location.coordinates is still a uniformly random point on Earth (faker.address.latitude()/longitude()), unrelated to the address — a Danish record came back at 57°S, 127°W, in the Southern Ocean. Deliberately left for now; fixing it needs a centroid for every city.Complete real postcodes for the six nationalities that were inventing the tail.
tools/import-postcodes.js. Thirteen nationalities already ship complete real postcodes. Six store only the part that names the town and let the inject script generate the rest, so the prefix is real and belongs to the city but the finished code usually does not exist:
| real | generated | |
|---|---|---|
GB AB51 2BR | AB51 outward code | 2BR |
NL 7741 LR | 7741 | LR |
BR 55044-832 | 550 CEP prefix | 44-832 |
IN 248377 | 248 PIN prefix | 377 |
MX 34008 | 340 | 08 |
TR 71280 | 71 plate code | 280 |
The new script reads the GeoNames postal export, collects every real code that opens with a prefix the record already holds — so a code can only ever land in a city the prefix already served — keeps an evenly spread sample of 40 per city, and marks the record "full": true. It is idempotent, and it is opt-in: nothing changes until it is run. README.md has the four steps.
"full": true, meaning its postcodes are complete. The six inject scripts emit the stored code verbatim in that case. They still draw the street-level remainder first and discard it, so the random sequence — and therefore every later field — is identical either way.full is set, instead of only matching the prefix. The other requires a record that claims full to actually hold complete codes.Kosovo's five districts now carry Kosovo's own postal codes.
Priština, Gnjilane and Kosovska Mitrovica moved onto the Kosovo Post numbering series. RS/lists/states.txt — which is upstream's, unchanged by this branch — lists five Kosovo districts among Serbia's regions: Kosovo, Kosovo-Pomoravlje, Kosovska Mitrovica, Peć and Prizren. Kosovo runs its own postal system, so the Serbian codes those cities carried would not route:
| City | Was | Now |
|---|---|---|
| Priština | 38000 | 10000 |
| Gnjilane | 17520 | 60000 |
| Kosovska Mitrovica | 38220 | 40000 |
Peć (30000) and Prizren (20000) already held their Kosovo codes and are untouched, which is why only three rows move.
Dialling is unchanged. Kosovo kept the geographic area codes it held under +381 when it moved to +383, so 038, 0280, 028, 039 and 029 are correct either way, and the rendered phone number carries no country prefix.
The record's country field still reads Serbia, because the nationality is RS and the region vocabulary is upstream's. That is a deliberate trade: the address is now deliverable, at the cost of a country line Kosovo does not recognise. Dropping the five districts instead would have edited upstream's region list; this does not.
The last six nationalities. Every nationality the API offers now generates a geographically coherent address.
locations.txt for the six that remained:dial field at all and their phone numbers stay unanchored, which is correct rather than an omission. The spec skips the dialling assertions for them and enforces them everywhere else.Description:NO/lists/states.txt held a mix of three eras — the pre-2020 nineteen counties, Trøndelag from the 2018 merger sitting alongside both Nord-Trøndelag and Sør-Trøndelag that it replaced, and Bergen, which is a city. Replaced with the 15 counties in force since January 2024, after the Viken, Vestfold og Telemark, and Troms og Finnmark splits were reversed: Agder, Akershus, Buskerud, Finnmark, Innlandet, Møre og Romsdal, Nordland, Oslo, Rogaland, Telemark, Troms, Trøndelag, Vestfold, Vestland, Østfold.DK/lists/states.txt line 1 was Danmark — the country name in the region list. Removed, leaving the five real regions.UA/lists/states.txt ended with Chernigivska oblasti — the list's footer word welded onto the last oblast. Now Chernigivska.CH/inject.js drew both phone and cell from the 075–079 mobile prefixes. phone is now a real Vorwahl-anchored landline; cell keeps the mobile prefixes, correctly.0 plus two arbitrary digits. Now the district's real code.phone is now anchored to the oblast's area code; cell stays mobile. This completes the Ukrainian phone work begun in 1.4.8, which fixed the letters but left the number non-geographic.CH and FI postcodes are now strings like every other nationality. Switzerland's were numbers; Helsinki's 00100 needs its leading zeros.cities.txt lists replaced with the curated ones backing their datasets. CH drops from 2241 entries to 49 and NO from 1013 to 66.NO and DK now draw the postcode from the matched city, falling back to their post_codes.txt when no locations dataset is present.location.state for Norwegian addresses will see county names that no longer exist after 2024 replaced by current ones.Nothing on the original list. Every nationality has a locations.txt, every region in every states.txt has at least one city, and every postcode format on the brief's table is fixed.
Iran removed, and a latent spec flaw fixed that removing it would have made worse.
nat=IR is no longer supported, in any API version. Iran's postcodes are ten digits whose leading five identify the postal region, and this project could not source that mapping; matching a city to a postcode it does not belong to would be worse for a consumer validating addresses than not offering the nationality at all. Removed rather than left half-done:api/1.0 through api/1.4 data/IR/ deleted.IR: "Iran" dropped from fullNatName in api/1.3 and api/1.4.IR removed from the nationality list in spec/api/modern/1.0.js and from the published lists in views/pages/documentation.ejs.views/pages/changelog.ejs still records Iran being added in a past release. That is a historical statement and remains true, so it is left alone.
This is a breaking change.** A request for ?nat=IR does not error — the nerator treats an unknown nationality as invalid and returns a random one stead, so a consumer asking for Iranian users will now silently receive meone else.
should retrieve random nat when invalid nat is specified was failing about one run in five. The assertion checked the randomly returned nationality against the 1.0-era list hardcoded in spec/api/modern/1.0.js, but that spec runs nested inside 1.1 through 1.4, and against 1.4 the generator draws from every nationality that version ships. Any draw landing on IN, MX, RS, UA or PH failed. It now reads the data directory of the version under test, so it stays correct as nationalities are added or removed. Latent since IN, MX, RS and UA were added; surfaced when PH shifted the draw onto RS.should retrieve IR nat when specified checks, one per nesting level, go with the nationality.Five nationalities still draw city and region independently: CH, DK, FI, NO, RS. All five have sourceable postcodes and dialling codes.
Matched locations for Brazil, India, Mexico and Turkey, and the Philippines added as a new nationality.
Actual output from the previous build:
BR Porto Velho | Rondônia | 11910 | (46) 5909-6383 IN Amritsar | Punjab | 10813 | 7656670656 MX Coatzacoalcos | Veracruz | 70937 | (650) 731 0329 TR Nevşehir | Nevşehir | 59958 | (757)-224-7229
Every postcode there is the generator's range(10000, 99999) default. Brazil uses an eight-digit CEP, India a six-digit PIN, and neither (46) for Rondônia nor (650) for Veracruz nor (757) for Nevşehir is a real dialling code.
locations.txt for four more nationalities:PH) as a new nationality, built from nothing: 111 cities across all 81 provinces plus Metro Manila, Filipino given names and surnames, street names, four-digit ZIPs, and the geographic area codes. Metro Manila and Rizal share 02 and took an eighth subscriber digit in the 2019 renumbering, so they format as (02) 8123 4567 while the provinces keep (033) 293 9961. Mobile numbers are non-geographic and drawn independently. id is a TIN. getNats() scans the data directory, so the only code change is PH: "Philippines" in fullNatName — without it location.country would be undefined. v1.4 now offers 22 nationalities, up from 21.29177-460.208405.70937 is not a Veracruz code. Now drawn from the city's own block.05 as they should, rather than reusing the landline shape.BR, IN and MX cities.txt replaced with the curated lists backing their datasets — 283 to 62, 316 to 88, and 2759 to 77. TR/lists/cities.txt is untouched apart from gaining a trailing newline; its 81 provinces already were the city list.BR, IN, MX and TR inject.js now derive postcode and phone from the drawn city, each in its own convention: (11) 3123-4567, 0512 3575692, (443) 905 8984, 0416 309 83 47.Measured rather than assumed, across all 21 existing nationalities:
info.seed is unchanged for every nationality.states.txt is in Persian script, and while its area codes are well documented, the ten-digit postcode prefixes are not something this pass could source. It keeps its five-digit number until there is a real list to work from — inventing plausible prefixes would be worse than the gap.Three standalone data and generation defects, none of which needed a locations.txt to fix. Ahead of the next batch of matched locations so they land on their own rather than buried in a large data diff.
UA/inject.js built them with random(4, 1), which draws from the uppercase alphabet; random(3, 1) draws digits. Every Ukrainian user got a number like (066) M57-7023. This is the same mistake Canada had, fixed there in 1.4.4.NO/lists/states.txt line 5 was the literal word Description — a scraping artefact sitting in the county vocabulary, so roughly one Norwegian user in twenty-two had Description as their county. Removed.DK/lists/post_codes.txt has shipped 1192 real Danish postcodes (1050–9990) all along, and nothing read it: DK/inject.js set no postcode, so every Danish user fell through to the generator's range(10000, 99999) five-digit default. Denmark's postcodes are four digits. Now drawn from the list, as Norway already did.UA — only phone and cell, and only the single character that was wrong. Name, city, region, postcode and email are byte-identical.NO — only location.state. The list is one entry shorter, so a given random index selects a different county; roughly 19 in 50 move. Every other field is byte-identical.DK — everything moves. Reading a postcode consumes a draw that was not consumed before, which shifts the shared random stream for every user after the first. This is the usual consequence of a list-length or draw-count change and is accepted.locations.txt are unaffected, and so are the ten that have one.NO/lists/states.txt has two further oddities that are vocabulary decisions rather than clear bugs, so they are left alone until Norway gets its matched locations:
Bergen is a city, not a county.Trøndelag sits alongside both Nord-Trøndelag and Sør-Trøndelag — the merged county and the two it replaced in 2018 are all present.A failed first MongoDB connection no longer disables the process permanently.
Found while running the suite on a Windows machine with a healthy, local MongoDB 8.0 that the app could not use. mongod logged exactly one client connection, lasting 700ms, and then nothing for the next 24 minutes:
"msg":"client metadata" ... "driver":{"name":"nodejs|Mongoose","version":"6.20.0|8.24.4"} "msg":"Connection ended" ... "connectionCount":0
The database was reachable in 26ms from a standalone script. What broke it was startup order: app.js connects before loadGenerators reads every dataset for all five API versions, and on a cold or virus-scanned disk that work held the event loop past the 30-second server-selection deadline. The driver's socket got through just as the deadline expired, so the driver closed it.
mongoose.connect(...).catch() swallowed the error, and mongoose closes the client when the initial connect rejects, so nothing ever reconnected. Every subsequent query then buffered for bufferTimeoutMS and threw, for the lifetime of the process — 352 of 376 specs failing on "Operation requests.updateOne() buffering timed out after 10000ms", and a 24-minute run. The same would happen in production to any deployment whose database was briefly slow at boot, until someone restarted it. models/db.js now retries with backoff (1s, 2s, 5s, 10s, then every 30s) until it connects.RANDOMUSER_MONGO_URI — server portion of the connection string, default mongodb://127.0.0.1:27017. The database name is still appended by models/db.js, which varies it for spec runs. Previously the host and port were hardcoded while the database name was already configurable.RANDOMUSER_DB_TIMEOUT — server-selection timeout in ms, default 30000, accepted range 1000–600000. Raising it avoids the failed first attempt altogether on a machine with slow startup.readInteger in settings.js takes optional min/max bounds instead of assuming the 1–65535 port range. Existing callers are unaffected.models/db without running a server needs to exit explicitly. The specs already pass --exit.Every county in the GB vocabulary now has a town behind it, and the spec enforces that for all ten nationalities.
1.4.1 shipped 66 GB cities against the store's 114-entry postal-county list, so 65 counties had no city that could ever be generated for them — Middlesex, Rutland, the Isle of Man, the Isles of Scilly, Fermanagh, the historic counties of Avon, Cleveland, Humberside and Borders, and 56 others. A WooCommerce customer in any of them had no address the generator could produce.
106 GB towns: one for each of the 65 uncovered counties, plus a second and in a few cases third town for the 41 larger ones — Essex, Surrey, Berkshire, Fife, Warwickshire, Cleveland and the like — so those do not always generate the same place. Each carries its real postcode districts and STD dialling code. GB/lists/locations.txt goes from 66 records to 172, covering all 114 counties, 33 of which now hold more than one town:
Douglas | Isle of Man | IM2 9DT | 01624 857556 Harrow | Middlesex | HA3 4WS | 020 8817 9912 St Marys | Isles of Scilly | TR21 3LR | 01720 891862 Stornoway | Western Isles | HS1 9FR | 01851 443941 Weston-super-Mare | Avon | BS23 8NJ | 01934 677335 Nuneaton | Warwickshire | CV10 6TQ | 024 7664 3601 Enfield | Middlesex | EN3 5XJ | 020 8370 7278
spec/api/modern/1.4.js now asserts that every region in a nationality's states.txt has at least one city, for every nationality shipping a locations.txt. This is the check that catches a region added to states.txt that nobody gave a city to; the failure message names it. It was left out in 1.4.4 precisely because GB could not pass it.GB/lists/cities.txt grows from 66 entries to 172.GB/lists/locations.txt is now sorted by city, which it already nearly was. The 66 existing records are otherwise untouched.locations.txt remain byte-identical to 1.4.3.Geographically coherent French, Dutch, Spanish, Irish and New Zealand addresses, and correct postcode formats for the three of them that were wrong.
Actual output from the previous build:
FR Orléans | Corrèze | 78948 | 04-97-76-65-33 NL Leimuiden | Zeeland | 66536 | (087)-124-9959 ES Cartagena | Aragón | 77645 | 975-968-702 IE Arklow | Galway | 71592 | 011-734-2085 NZ Greymouth | Tasman | 20684 | (104)-942-3055
Orléans is in the Loiret, Cartagena is in Murcia, Arklow is in Wicklow and Greymouth is on the West Coast. Every postcode there is a five-digit number from the generator's default range(10000, 99999): France has no 789xx, Spain has no province 77, and the Netherlands, Ireland and New Zealand do not use five-digit numeric postcodes at all. 011 and (104) are not dialling codes in their respective countries.
locations.txt for five more nationalities:04 72/04 78 and Strasbourg on 03 88. Overseas départements use a four-digit indicatif and six subscriber digits.ES/lists/states.txt is the community list, not the 50 provinces, so coverage is measured against the 19.cities.txt lists replaced with the curated list backing its locations.txt. NL/lists/cities.txt drops from 2999 entries to 70 and IE/lists/cities.txt from 100 to 76.FR, NL, ES, IE and NZ inject.js now derive postcode and landline from the drawn city, with each country's own formatting: 04 72 34 56 78, 026 858 4692, 93 241 84 15, 071 186672, (09) 434 6300. The previous unanchored generators remain as a fallback if the dataset is absent.location.postcode is now a string for these five.F28 DW8P — drawn from the reduced Eircode alphabet, which excludes the vowels and B, G, I, J, L, M, O, Q, U and Z.0620 and 0110 also need to stay strings to keep their leading zero.locations.txt are unaffected.location.postcode as a number for these five now receive a string — and for IE, NL and NZ its shape changes as well.IN (should be a six-digit PIN), DK and IR (four and ten digits), and BR (a CEP, 01310-100). DK/lists/post_codes.txt is still dead data that nothing reads.random(4, 1) bug Canada had, fixed for CA in 1.4.4.NO/lists/states.txt line 5 is still the literal word Description.Geographically coherent US, Canadian, Australian and German addresses, using the locations.txt mechanism introduced for GB in 1.4.1.
Actual output from the previous build:
US Tucson | Rhode Island | 34083 | (343) 388-7043 CA Greenwood | Saskatchewan | C5R 8M8 | N04 B03-1437 AU Darwin | New South Wales | 4161 | 00-6878-3589 AU Busselton | Queensland | 416 | 00-0490-1160 DE München | Baden-Württemberg | 51694 | 0079-6266222
Tucson is in Arizona, Darwin is in the Northern Territory and München is in Bayern. Beyond the mismatched regions: 00 is not an Australian area code, 0079 is not a German Vorwahl, 416 is not a four-digit Australian postcode, and the Canadian phone number has letters where the area code belongs.
locations.txt for four more nationalities, same one-JSON-object-per-line convention as GB:spec/api/modern/1.4.js now loops over every nationality that ships a locations.txt instead of testing GB alone, so a new dataset is covered the moment it lands. It checks the region, postcode and dialling code of 200 generated users per nationality, that the postcode is a string of the right shape, and that the internal scratch fields never reach the response.cities.txt lists replaced with the curated list backing its locations.txt. DE/lists/cities.txt drops from 2181 entries to 83 and US/lists/cities.txt from 354 to 112 — the oversized lists held towns with no region, postcode or dialling data behind them.US, CA, AU and DE inject.js now derive postcode and landline from the drawn city. The previous unanchored generators remain as a fallback if the dataset is absent.(602) 555-0123 for US and CA, (02) 9123 4567 for AU, 0221 1234567 for DE.location.postcode is now a string for these four, as it already was for GB. It has to be: Boston is 02108, Dresden is 01067 and Darwin is 0800.api/1.4/data/GB/lists/locations.txt gains a pc field naming its postcode shape, so every record is self-describing for the spec. No behaviour change — GB/inject.js does not read it.CA/inject.js built them with random(4, 1), which draws from the uppercase alphabet rather than the digits, so every Canadian user got a number like N04 B03-1437.range(200, 9999) emitted a bare number, so anything below 1000 lost its leading zero — 416 where the Northern Territory uses 0416-style four-digit codes.locations.txt are unaffected.location.postcode as a number for these four nationalities now receive a string.Data loading fixes. Applies to every API version, 1.0 through 1.4.
A carriage return was appended to every value loaded from a data list. loadData split on \n alone, so on a Windows checkout — where core.autocrlf rewrites these files as CRLF — every name, title, city, state and street reached the response with a trailing \r:
{"name":{"title":"Miss\r","first":"Florence\r","last":"Hoffman\r"}, "location":{"street":{"name":"W Belt Line Rd\r"},"city":"Berkeley\r", ...
Invisible in a browser, but it lands in anything that consumes the data — a WooCommerce import gets a stray control character in every address field.
slice(0, -1) assumed one; 28 of the shipped lists don't have it. IN/lists/cities.txt holds 316 cities and 315 were loaded, so Yamunanagar could never be generated. Same for Düzce, the last Serbian surname, the last Ukrainian city, the last Norwegian postcode, and the Auckland/Wellington timezone.Both are fixed by a single util.splitList helper now shared by all five generator versions.
.gitattributes pinning text and data files to LF, so a Windows checkout stops producing CRLF data in the first place. splitList tolerates CRLF regardless; this stops the problem at the source rather than papering over it.IN, NO, RS, TR, UA — name, location and email all change.location.timezone, because the timezone list grew from 30 usable entries to 31. rified across 21 nationalities × 2 seeds; no other field moves.git add --renormalize . and commit if you want the working tree converted to LF immediately. Not required — the code handles either.Correctness and performance pass over the API and server. No change to the response schema or to any documented query parameter.
FI/inject.js read contents.dob — an object — into new Date(), producing an Invalid Date, so every Finnish user got a HETU like NaNNA464undefined. It also used the weekday instead of the day of the month, a zero-based month, and summed the date parts instead of reading them as one number for the check character. Now emits a valid, checksum-correct HETU (190944-464U). This is the only generated field whose value changes.returnOriginal: false to findOneAndUpdate, which never returns null, so the guard meant to skip the increment on insert never fired.?noinfo tested the wrong parameter. this.noInfo was derived from options.lego, so ?noinfo=false still stripped the info block and ?noinfo&lego=false failed to strip it.Date.now() alone; two ?dl requests in the same millisecond shared a path, so one served the other's file and the loser's unlink threw an unhandled rejection. Names now carry six random bytes, and the unlink tolerates an already-deleted file.loadGenerators wrapped its work in new Promise(async …), which swallows throws: the promise never settled, startServer() was never called, and the process sat idle with no error.String.length counts UTF-16 units, not bytes. Now Buffer.byteLength.nino_1, nino_2 and nino in GB/inject.js were assigned without declaration, leaking onto the global object and throwing under strict mode.compression() was registered after express.static, which ends the response itself, so everything in public/ went out uncompressed. Moved ahead of the static handler.updateOne(…, {$inc}, {upsert: true}); Mongo creates the document on the first request of the day and increments it thereafter.await at a time. Measured 50 ms → 26 ms median for the 1.4 dataset over 9 cold inits.results=5000.setInterval(updateStats, 2500) kept firing regardless of whether the previous three aggregations had finished; it now reschedules after completion and survives a failed refresh instead of dying on an unhandled rejection.id.value differs — verified field-by-field across 21 nationalities × 4 seeds, plus mixed-nationality runs. Nationality ordering, which seeds index into, is unchanged.?noinfo=false now returns the info block, where previously it was stripped. That is the intended meaning of the parameter and matches how ?lego=false behaves.app.js, routes/api.js, loadGenerators.js and sockets.js changes are syntax-checked and reasoned but were not executed — the sandbox they were written in has no network access to install express or mongoose. Run npm test against a real install before deploying.faker and dayjs, so the generation figures overstate the relative gain on a production install where those libraries dominate the per-user cost.Geographically coherent UK (nat=GB) address generation.
Previously a GB user's city, county, postcode and phone number were each drawn independently. Actual output from the previous build:
Cardiff | Shropshire | A1R 3PW | 01348 391746 Kingston upon Hull | County Antrim | LT48 9ZA | 016973 36959 Lisburn | Mid Glamorgan | V8I 8PU | 025 5364 5436
None of those addresses can exist: the county does not contain the city, the outward codes are not real districts (and V8I uses two letters Royal Mail excludes), and 025 is not an allocated UK dialling code. Any consumer that validates a UK address — a WooCommerce checkout, for example — rejected them.
api/1.4/data/GB/lists/locations.txt — 66 UK cities, each bound to exactly one county, its real postcode districts (496 in total) and its real STD dialling code. One JSON record per line, matching the existing common/lists/timezones.txt convention.locations dataset support in api/1.4/api.js: when a nationality ships a locations.txt, a single draw fixes city and state together and carries the matching postcode districts and dialling code through to that nationality's inject script. Other nationalities are untouched.spec/api/modern/1.4.js asserting that every generated GB record's county, postcode district and dialling code belong to its city, and that the internal scratch field never reaches the response.api/1.4/data/GB/lists/states.txt replaced with the 114-entry county vocabulary used by the target WooCommerce store (postal counties, covering England, Scotland, Wales, Northern Ireland, plus Channel Islands, Isle of Man and Isles of Scilly).api/1.4/data/GB/inject.js now derives the postcode from the drawn city's districts and the landline from its dialling code, including per-city local number prefixes — so Durham lands on 0191 3xx xxxx and Sunderland on 0191 5xx xxxx, Belfast on 028 90xx xxxx and Armagh on 028 37xx xxxx. The previous unanchored generators remain as a fallback if the dataset is absent.020/023/ 024/028/029, 7 after 0113-style codes, 6 after five-digit 01xxx).state remains the plain county name.outcodes endpoint.