Skip to main content

Swap in a drop-in for an existing client

You have a service that already talks to a geocoder. It builds Photon query strings, or reads Nominatim's jsonv2 result array, or posts to libpostallibpostalAn open-source C address parser used by Pelias. Mailwoman's rule-based v0 and neural classifier supersede it.'s /parse. Rewriting that caller to speak Mailwoman's own /v1 shape is work you did not ask for.

Three packages answer on those shapes instead. By the end of this page you'll have the Photon-compatible one running against your own gazetteergazetteerA geographical index that maps place names and postcodes to real-world coordinates. Mailwoman uses a custom-built Who's On First (WOF) SQLite database as its gazetteer — the 'atlas' half of the grammar/atlas architecture., a geopy client pointed at it by changing one argument, the Nominatim and libpostallibpostalAn open-source C address parser used by Pelias. Mailwoman's rule-based v0 and neural classifier supersede it. variants running the same way, and a table of which request parametersparameterA single learned number inside a model — one weight or bias. Mailwoman's encoder has roughly 30 million of them; training is the search for good values. each drop-in reads. About thirty minutes, most of it a download.

Prerequisites

Read the last two before you start. They decide whether this page can answer the question you have.

  • Node.js 24.18.0 or later, and about 1.7 GB of free disk.
  • jq for the curl output below. Drop the pipe if you'd rather read the raw JSON.
  • Python with geopy for step 4, in a throwaway virtual environment. Skip that step if your client is in another language; the change it makes is one constructor argument in any of them.
  • The gazetteergazetteerA geographical index that maps place names and postcodes to real-world coordinates. Mailwoman uses a custom-built Who's On First (WOF) SQLite database as its gazetteer — the 'atlas' half of the grammar/atlas architecture. is a 1,652,916,224-byte download and it is not on npm. Step 1 fetches it once. Everything after step 1 is local.
  • The ports collide with what you're replacing. The defaults are 2322 for Photon, 8080 for Nominatim, 8081 for libpostallibpostalAn open-source C address parser used by Pelias. Mailwoman's rule-based v0 and neural classifier supersede it. — the same ports the servers you already run are bound to. This page moves two of them with --port, because the machine it was verified on had 2322 and 8080 taken. Running the drop-in beside the original on a spare port is the right shape for a swap anyway: you can diff the two answers before you cut over.
  • /reverse needs a database this page does not download. Reverse geocodinggeocodingThe process of converting an address into geographic coordinates (latitude and longitude). Mailwoman geocodes in a multi-tier cascade: exact address-point match → street interpolation → locality centroid. Each tier is progressively coarser but more widely available. on the Photon and Nominatim drop-ins runs point-in-polygon over a full WOFWOF (Who's On First). An open-source gazetteer of places maintained by Mapzen/whosonfirst. Mailwoman builds a custom SQLite database from WOF GeoJSON repos, extended with postcode data, importance scores, and coincident-role relations. distribution at <data-root>/wof/admin-global-priority.db. mailwoman data pull does not ship one, so /reverse answers an empty result until you put one there. Step 3 shows what that looks like so you don't mistake it for a broken install. Forward geocodinggeocodingThe process of converting an address into geographic coordinates (latitude and longitude). Mailwoman geocodes in a multi-tier cascade: exact address-point match → street interpolation → locality centroid. Each tier is progressively coarser but more widely available. — the path most callers use — works from step 1.
  • There is no authentication in front of any of them, and --host defaults to 0.0.0.0. Bind 127.0.0.1 or put the server behind your own ingress before it goes anywhere shared.

1. Pull the gazetteer once

All three packages read one data root. Set it, and look at the plan before you commit to the transfer — --dry-run prints the URL, the size and the destination, and touches no network:

export MAILWOMAN_DATA_ROOT=/tmp/mailwoman-data
npx mailwoman data pull candidate --dry-run
✓ candidate: gazetteer/2026-07-07a/candidate.db: [dry-run] 1652.9 MB
https://public.sister.software/mailwoman/gazetteer/2026-07-07a/candidate.db →
/tmp/mailwoman-data/wof/candidate.db
PASS (1/1 checks)

Then drop the flag and let it run:

npx mailwoman data pull candidate
▸ pull https://public.sister.software/mailwoman/gazetteer/2026-07-07a/candidate.db (~1652.9 MB) → /tmp/mailwoman-data/wof/candidate.db
[DEBUG] (mailwoman data): HEAD: https://public.sister.software/mailwoman/gazetteer/2026-07-07a/candidate.db
[DEBUG] (mailwoman data): 200 (uncached) HEAD: https://public.sister.software/mailwoman/gazetteer/2026-07-07a/candidate.db

export MAILWOMAN_CANDIDATE_DB=/tmp/mailwoman-data/wof/candidate.db
✓ candidate: gazetteer/2026-07-07a/candidate.db: content-length verified (1652.9 MB) →
/tmp/mailwoman-data/wof/candidate.db
PASS (1/1 checks)

The paths in every transcript on this page are the data root it was verified against; yours read differently. The file lands at <data-root>/wof/candidate.db and every entry point finds it there on its own — the drop-ins, mailwoman geocode and mailwoman serve alike. The export line the pull prints is a leftover from 8.6.0, when only the drop-ins had that fallback; following it changes nothing. Pass --candidate-db <path> if your copy lives somewhere else.

Re-running the pull is safe: with the file already on disk it reports already present … skipped and downloads nothing, so this is a line you can leave in a provisioning script.

2. Start the Photon-compatible server

npx @mailwoman/photon serve --port 2323
[resolver] candidate-table backend (demo-parity, population-first): /tmp/mailwoman-data/wof/candidate.db
[@mailwoman/photon] listening on http://0.0.0.0:2323
wof: (none found — set MAILWOMAN_WOF_DB)
resolver: candidate gazetteer (worldwide) — /tmp/mailwoman-data/wof/candidate.db
cors: enabled (Access-Control-Allow-Origin: *)
endpoints: GET / GET /api GET /reverse GET /openapi.json

Four of those lines are the deployment describing itself, and they are the ones to read in a log when two hosts disagree. resolver: names the gazetteergazetteerA geographical index that maps place names and postcodes to real-world coordinates. Mailwoman uses a custom-built Who's On First (WOF) SQLite database as its gazetteer — the 'atlas' half of the grammar/atlas architecture. that will answer. wof: is the reverse-geocodinggeocodingThe process of converting an address into geographic coordinates (latitude and longitude). Mailwoman geocodes in a multi-tier cascade: exact address-point match → street interpolation → locality centroid. Each tier is progressively coarser but more widely available. database from the prerequisites, absent here — and the variable that line suggests is read by mailwoman serve, not by this server, which looks only at <data-root>/wof/. Permissive CORS is on by default because browser geocoder widgets call these endpoints cross-origin; --no-cors turns it off when a reverse proxy already sets the headers.

Leave it running and open a second terminal.

3. Ask it what your client asks

GET /api is Photon's forward endpoint, and it answers a GeoJSON FeatureCollection.

curl -s "http://127.0.0.1:2323/api?q=1600+pennsylvania+ave+nw+washington+dc+20500&limit=1" | jq '.features[0]'
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [
-77.016216,
38.904831
]
},
"properties": {
"osm_key": "place",
"osm_value": "city",
"type": "city",
"name": "Washington",
"city": "Washington",
"state": "District of Columbia",
"postcode": "20500",
"country": "United States",
"countrycode": "us"
}
}

osm_key, osm_value and type are always present, because Photon clients dereference them without checking. They are derived from the resolved place's type, not read from OpenStreetMapOpenStreetMap (OSM). A community-curated global map database (ODbL-licensed) with addr:* tagged features and place hierarchies. A secondary corpus source and a source of street names. — see the table in step 7.

The lat/lon parametersparameterA single learned number inside a model — one weight or bias. Mailwoman's encoder has roughly 30 million of them; training is the search for good values. are a proximity bias, and an ambiguous query is where you can watchnamed watchA known below-target reading recorded at ship with an owner and a retirement condition — never a silent waiver. Example: fr.cedex shipped at 83.3 under the waived floor, named, and retired when the from-scratch base read 90.5. them work. Two requests, one query, two orderings:

curl -s "http://127.0.0.1:2323/api?q=paris&limit=3" | jq -c '[.features[] | {name: .properties.name, cc: .properties.countrycode, coords: .geometry.coordinates}]'
curl -s "http://127.0.0.1:2323/api?q=paris&limit=3&lat=33.66&lon=-95.55" | jq -c '[.features[] | {name: .properties.name, cc: .properties.countrycode, coords: .geometry.coordinates}]'
[{"name":"Paris","cc":"fr","coords":[2.342841,48.856599]},{"name":"Paris","cc":"us","coords":[-95.54435,33.668553]},{"name":"Paris","cc":"us","coords":[-88.30661,36.293581]}]
[{"name":"Paris","cc":"us","coords":[-95.54435,33.668553]},{"name":"Paris","cc":"fr","coords":[2.342841,48.856599]},{"name":"Paris","cc":"us","coords":[-88.30661,36.293581]}]

Unbiased, Paris in France wins on population. Biased to a point in north-east Texas, the Texan Paris takes the first slot and France drops to second. The bias re-ranks; it never filters, so both alternatives stay in the collection. Feed it your map center and a type-ahead orders results the way the person typing expects.

Now the endpoint from the prerequisites:

curl -s "http://127.0.0.1:2323/reverse?lat=52.52&lon=13.405" | jq -c
{"type":"FeatureCollection","features":[]}

An empty collection, not an error. That is the wof: (none found) line from step 2 showing through: with no full WOFWOF (Who's On First). An open-source gazetteer of places maintained by Mapzen/whosonfirst. Mailwoman builds a custom SQLite database from WOF GeoJSON repos, extended with postcode data, importance scores, and coincident-role relations. distribution on disk the reverse geocoder is never constructed, and the route answers the empty shape rather than a 500. Put admin-global-priority.db in <data-root>/wof/ and restart to change that answer.

4. Point your existing client at it

Nothing above required a client library. This step is the swap itself, and in geopy it is one argument. Install it in a throwaway environment:

python3 -m venv /tmp/geopy-venv && /tmp/geopy-venv/bin/pip install geopy

Then hand the Photon geocoder a domain and a scheme instead of letting it default to the hosted service:

from geopy.geocoders import Photon

photon = Photon(domain="localhost:2323", scheme="http")
loc = photon.geocode("1600 Pennsylvania Ave NW, Washington DC 20500")
print(loc.address)
print((loc.latitude, loc.longitude))
Washington, 20500, Washington, District of Columbia, United States
(38.904831, -77.016216)

Same object your code already handles, from a server you run. Every Photon client has this pair of knobs under some name — a base URL, a host, an endpoint override — because the hosted instance is a default, not a hard-coded constant.

5. The Nominatim shape

Same data root, same pull, different package and a different result envelope. Start it beside the Photon server:

npx @mailwoman/nominatim serve --port 8088
[resolver] candidate-table backend (demo-parity, population-first): /tmp/mailwoman-data/wof/candidate.db
[@mailwoman/nominatim] listening on http://0.0.0.0:8088
wof: (none found — set MAILWOMAN_WOF_DB)
resolver: candidate gazetteer (worldwide) — /tmp/mailwoman-data/wof/candidate.db
cors: enabled (Access-Control-Allow-Origin: *)
endpoints: GET / GET /search GET /reverse GET /lookup GET /status GET /openapi.json

/status is the route a health check hits, and it answers Nominatim's two-key object:

curl -s http://127.0.0.1:8088/status
{"status":0,"message":"OK"}

/search takes free text or the structured parametersparameterA single learned number inside a model — one weight or bias. Mailwoman's encoder has roughly 30 million of them; training is the search for good values., and addressdetails=1 adds the address block:

curl -s "http://127.0.0.1:8088/search?q=10+Downing+Street,+London+SW1A+2AA&addressdetails=1" | jq '.[0]'
{
"place_id": 1178923677,
"licence": "Data © Who's On First, Overture Maps, OpenAddresses, US Census TIGER",
"lat": "51.500525578898",
"lon": "-0.109400835283853",
"display_name": "10, Downing Street, London, SW1A 2AA",
"address": {
"city": "London",
"postcode": "SW1A 2AA",
"house_number": "10",
"road": "Downing Street"
},
"annotations": {
"DMS": {
"lat": "51° 30′ 1.89″ N",
"lng": "0° 6′ 33.84″ W"
},
"MGRS": "30UYC0062309449",
"Maidenhead": "IO91wm",
"Mercator": {
"x": -12178.445276157312,
"y": 6710313.068983022
},
"geohash": "gcpuvx2k6",
"qibla": 119.00112658398905,
"sun": {
"rise": {
"apparent": 1785731284
},
"set": {
"apparent": 1785786464
}
}
}
}

The annotations block is an addition, in OpenCage's shape. Everything derived from the coordinate alone is always there — the formats, the qibla bearing, the sun times. Three more, flag, currency and callingcode, need a countrycountryThe top-level address component (an ISO country). Closed-vocabulary, so it is best handled by a deterministic matcher feeding a proposal rather than a retrained model head. in the resolved hierarchy, which this result has not got and ?q=Berlin,+Germany has. The IANA timezone, UN/LOCODE and EU NUTS codes join them when those lookup databases are present in the data root. A Nominatim client that ignores unknown keys will not notice any of it.

countrycodes is honored as a hard restriction, which makes it the manual override when a bare place name resolves to the wrong countrycountryThe top-level address component (an ISO country). Closed-vocabulary, so it is best handled by a deterministic matcher feeding a proposal rather than a retrained model head.:

curl -s "http://127.0.0.1:8088/search?q=springfield&countrycodes=de" | jq -c
[]

An empty array: the restriction held, and nothing in Germany matched. A hard filter returning nothing is the parameterparameterA single learned number inside a model — one weight or bias. Mailwoman's encoder has roughly 30 million of them; training is the search for good values. working, not a lookup failure, and it is exactly what step 3's soft bias will never do to you.

geopy's Nominatim geocoder takes the same two arguments as its Photon one, plus the user_agent it requires:

from geopy.geocoders import Nominatim

nom = Nominatim(domain="localhost:8088", scheme="http", user_agent="swap-tutorial")
loc = nom.geocode("10 Downing Street, London SW1A 2AA", addressdetails=True)
print(loc.address)
print((loc.latitude, loc.longitude))
print(loc.raw["address"])
10, Downing Street, London, SW1A 2AA
(51.500525578898, -0.109400835283853)
{'city': 'London', 'postcode': 'SW1A 2AA', 'house_number': '10', 'road': 'Downing Street'}

6. The libpostal shape

The parseaddress parsingThe process of decomposing a free-text postal address string into structured components — house number, street name, locality, region, postcode, and country — so a geocoder can resolve them to coordinates.-only drop-in needs no gazetteergazetteerA geographical index that maps place names and postcodes to real-world coordinates. Mailwoman uses a custom-built Who's On First (WOF) SQLite database as its gazetteer — the 'atlas' half of the grammar/atlas architecture. at all — the model weightsmodel weightsThe learned parameters of the neural classifier, shipped as ONNX files in the @mailwoman/neural-weights-* packages. Weights are locale-specific bundles that include the model, tokenizer, and a model-card.json metadata file. arrive as a package dependency, so this one starts on a machine that never ran step 1:

npx @mailwoman/libpostal serve --port 8081
[@mailwoman/libpostal] listening on http://0.0.0.0:8081
cors: enabled (Access-Control-Allow-Origin: *)
endpoints: GET / POST/GET /parse POST/GET /expand GET /openapi.json

/parse returns libpostallibpostalAn open-source C address parser used by Pelias. Mailwoman's rule-based v0 and neural classifier supersede it.'s ordered [{label, value}] array, over GET or POST:

curl -s "http://127.0.0.1:8081/parse?query=1600%20Pennsylvania%20Ave%20NW%2C%20Washington%20DC%2020500"
curl -s -X POST http://127.0.0.1:8081/parse -H 'content-type: application/json' -d '{"query":"30 Rue de Rivoli, 75004 Paris"}'
[{"label":"house_number","value":"1600"},{"label":"road","value":"Pennsylvania Ave NW"},{"label":"city","value":"Washington"},{"label":"state","value":"DC"},{"label":"postcode","value":"20500"}]
[{"label":"house_number","value":"30"},{"label":"road","value":"Rue de Rivoli"},{"label":"postcode","value":"75004"},{"label":"city","value":"Paris"}]

/expand answers with a set, and the set is small on purpose:

curl -s "http://127.0.0.1:8081/expand?address=1600%20Pennsylvania%20Ave%20NW"
{"expansions":["1600 Pennsylvania Ave NW","1600 Pennsylvania Avenue Northwest"]}

The route builds three strings — the input, its normalized form, and its abbreviation-expanded form — and drops the duplicates, which is why this address yields two. Mailwoman's normalization stagestageOne of the dataflow stages in the runtime pipeline (normalize, locale gate, kind classify, phrase group, token classify, sequence correct, reconcile, resolve). Distinct from tier (model vocabulary) and phase (plan milestone). is deterministic, so the result is one canonical alternative rather than the multi-variant hypothesis set libpostallibpostalAn open-source C address parser used by Pelias. Mailwoman's rule-based v0 and neural classifier supersede it.'s probabilistic expander produces. A caller that indexes every expansion gets fewer keys per address here; a caller that takes expansions[0] or the last element sees no difference.

A missing parameterparameterA single learned number inside a model — one weight or bias. Mailwoman's encoder has roughly 30 million of them; training is the search for good values. is a status code and a one-key object, not a stack trace:

curl -s -i "http://127.0.0.1:8081/parse" | head -1
curl -s "http://127.0.0.1:8081/parse"
HTTP/1.1 400 Bad Request
{"error":"query is required"}

7. What each drop-in honors

The tables below are the compatibility surface as of 8.7.0, read off the route handlers and the engines behind them, then checked against a running server. Request parametersparameterA single learned number inside a model — one weight or bias. Mailwoman's encoder has roughly 30 million of them; training is the search for good values. get one of three verdicts:

  • Honored — the parameterparameterA single learned number inside a model — one weight or bias. Mailwoman's encoder has roughly 30 million of them; training is the search for good values. changes the answer.
  • Accepted, no effect — the request is valid and the parameterparameterA single learned number inside a model — one weight or bias. Mailwoman's encoder has roughly 30 million of them; training is the search for good values. is parsed, and nothing downstream reads it. Your client will not break; it will get the unfiltered answer.
  • Not read — the parameterparameterA single learned number inside a model — one weight or bias. Mailwoman's encoder has roughly 30 million of them; training is the search for good values. never reaches a handler.

Rows for response fields and whole routes carry their own verdict instead, and each one says what it means in the third column.

Photon: /api and /reverse

ParameterparameterA single learned number inside a model — one weight or bias. Mailwoman's encoder has roughly 30 million of them; training is the search for good values. or fieldVerdictWhat that means here
qHonoredParsed and resolved. Empty or over 512 characters answers an empty collection
limitHonoredCaps the featuresfeatureAn input signal a model conditions on. Beyond the raw tokens, Mailwoman feeds soft features — gazetteer-membership channels and the postcode anchor — that inform predictions without overriding them. returned. /reverse returns at most one regardless
lat + lon on /apiHonoredSoft proximity bias, both required. Re-ranks candidates, never filters
format=jsonldHonored, additionRe-serializes the same collection as schema.org Place[]. Upstream Photon has no such format
langAccepted, no effectNames come back as the gazetteergazetteerA geographical index that maps place names and postcodes to real-world coordinates. Mailwoman uses a custom-built Who's On First (WOF) SQLite database as its gazetteer — the 'atlas' half of the grammar/atlas architecture. stores them. ?q=cologne&lang=en still answers Köln
osm_tagAccepted, no effectNo category filter. A request for amenity:cafe returns the same places as one without it
layerAccepted, no effectNo layerlayerOne transformer block — attention plus a feed-forward network, with normalization and residual connections — applied to every position. Stacking layers lets the model build up richer representations; Mailwoman's encoder has 6. filter
radius on /reverseAccepted, no effectThe reverse geocoder returns the containing place, not a radius search
bboxNot readThe handler parsesaddress parsingThe process of decomposing a free-text postal address string into structured components — house number, street name, locality, region, postcode, and country — so a geocoder can resolve them to coordinates. no bounding box. Use lat/lon for viewport influence
osm_key/osm_value/typeAlways presentDerived from the resolved place's type so clients can dereference them. Not OpenStreetMapOpenStreetMap (OSM). A community-curated global map database (ODbL-licensed) with addr:* tagged features and place hierarchies. A secondary corpus source and a source of street names. tag values
osm_id / osm_typeNever setNothing here is keyed to an OpenStreetMapOpenStreetMap (OSM). A community-curated global map database (ODbL-licensed) with addr:* tagged features and place hierarchies. A secondary corpus source and a source of street names. object. A client that stores them stores undefined
/reverse without a full WOFWOF (Who's On First). An open-source gazetteer of places maintained by Mapzen/whosonfirst. Mailwoman builds a custom SQLite database from WOF GeoJSON repos, extended with postcode data, importance scores, and coincident-role relations. distributionEmpty collectionSee the prerequisites

Nominatim: /search, /reverse, /lookup, /status

ParameterparameterA single learned number inside a model — one weight or bias. Mailwoman's encoder has roughly 30 million of them; training is the search for good values. or fieldVerdictWhat that means here
qHonoredFree-text forward geocodinggeocodingThe process of converting an address into geographic coordinates (latitude and longitude). Mailwoman geocodes in a multi-tier cascade: exact address-point match → street interpolation → locality centroid. Each tier is progressively coarser but more widely available.
street, city, county, state, country, postalcodeHonoredJoined into one query string in that order, then parsed. ?city=Berlin&country=Germany resolves
countrycodesHonoredHard countrycountryThe top-level address component (an ISO country). Closed-vocabulary, so it is best handled by a deterministic matcher feeding a proposal rather than a retrained model head. restriction, first code only. Can legitimately answer []
addressdetailsHonoredAdds the address block
formatHonoredgeojson and jsonld change the envelope; jsonv2 (the default) and json both answer the result array. /lookup ignores jsonld — a preserved quirk
limitAccepted, capped/search returns at most one result today, so limit=5 still answers one
boundedAccepted, no effectNo viewbox restriction
accept-languageAccepted, no effectSame gazetteergazetteerA geographical index that maps place names and postcodes to real-world coordinates. Mailwoman uses a custom-built Who's On First (WOF) SQLite database as its gazetteer — the 'atlas' half of the grammar/atlas architecture.-name behavior as Photon's lang
zoom on /reverseAccepted, no effectThe detail level is whatever the containing hierarchy gives
viewbox, extratags, namedetails, polygon_*, dedupeNot readThe handlers parseaddress parsingThe process of decomposing a free-text postal address string into structured components — house number, street name, locality, region, postcode, and country — so a geocoder can resolve them to coordinates. none of them
place_idSubstitutedForward results carry a deterministic hash of coordinate plus display name, not a Nominatim id. Reverse results carry the gazetteergazetteerA geographical index that maps place names and postcodes to real-world coordinates. Mailwoman uses a custom-built Who's On First (WOF) SQLite database as its gazetteer — the 'atlas' half of the grammar/atlas architecture.'s own place id
osm_id / osm_typeNever setAs with Photon
licenceSubstitutedData © Who's On First, Overture Maps, OpenAddresses, US Census TIGER — the sources these answers come from
annotationsAdditionThe OpenCage-style block from step 5
/lookup501Not implemented
/reverse without a full WOFWOF (Who's On First). An open-source gazetteer of places maintained by Mapzen/whosonfirst. Mailwoman builds a custom SQLite database from WOF GeoJSON repos, extended with postcode data, importance scores, and coincident-role relations. distributionnullSee the prerequisites

libpostal: /parse and /expand

ParameterparameterA single learned number inside a model — one weight or bias. Mailwoman's encoder has roughly 30 million of them; training is the search for good values. or fieldVerdictWhat that means here
query, addressHonoredOn /parse, either name works, over GET or POST. /expand takes address
Component labelscomponent tagOne of the 25 labels in Mailwoman's address schema — street, locality, region, postcode, house_number, unit, po_box, country, venue, intersection, and others. Each parsed span carries exactly one component tag.MappedMailwoman's tags project onto libpostallibpostalAn open-source C address parser used by Pelias. Mailwoman's rule-based v0 and neural classifier supersede it.'s names — street to road, locality to city, region to state, dependent_locality and neighbourhood both to suburb, and so on through twenty entries. A tag with no entry passes through under its own name
/expand outputDeduplicated setThe input, its normalized form and its abbreviation-expanded form, with duplicates dropped — one to three strings, not a hypothesis set. See step 6
Language optionsNot readOne weights bundlemodel weightsThe learned parameters of the neural classifier, shipped as ONNX files in the @mailwoman/neural-weights-* packages. Weights are locale-specific bundles that include the model, tokenizer, and a model-card.json metadata file. (en-us) is loaded at startup, and there is no per-request language parameterparameterA single learned number inside a model — one weight or bias. Mailwoman's encoder has roughly 30 million of them; training is the search for good values.

8. Stop them

Ctrl-C in each server's terminal. Nothing is written to the data root by serving, so a restart picks up where you left off.

What you have now

Three compatible endpoints on your own hardware, reading one gazetteergazetteerA geographical index that maps place names and postcodes to real-world coordinates. Mailwoman uses a custom-built Who's On First (WOF) SQLite database as its gazetteer — the 'atlas' half of the grammar/atlas architecture., answering the request shapes your existing callers already build — and a written record of which parametersparameterA single learned number inside a model — one weight or bias. Mailwoman's encoder has roughly 30 million of them; training is the search for good values. each one reads, so a swap that changes an answer is a row in a table rather than a mystery.

Next

  • Run the API server — Mailwoman's own /v1 surface, with the fuller result object and a generated OpenAPI document. The rule of thumb: an existing integration goes to a drop-in, a new one goes to /v1.
  • Improve geocode precision — the per-state download that moves these answers from a city centroidlocality centroidThe representative center point of a city or locality, used as a coarse coordinate when no exact address point is available — the coarsest tier of the geocode cascade. to a rooftoprooftopGeocoding precision at the building or parcel level — coordinates within a few meters — the highest tier of the geocode cascade. Sourced from address-point and situs data..
  • Parse in the browser — the same modelneural classifierThe machine learning model at the core of Mailwoman's parser — a transformer encoder (~30M parameters) trained from scratch to do BIO token classification over addresses. It learns the 'grammar' of address formats; the gazetteer supplies the 'atlas.' with no server at all.