Address Validation API: One Said Invalid, One Said High
Same address, same minute, two endpoints. One returned valid false and confidence 0 while handing back a perfect parse. The other returned high.

Copy this line to your agent to clean a list of addresses.
set up https://monid.ai/SKILL.md and standardise these addresses, keeping the ones that resolve
We sent the address of Google's headquarters to two address endpoints from the same provider in the same minute. One came back valid: false with confidence: 0, and in the same response handed over a fully parsed, geocoded, correctly standardised address. The other came back confidence: "high". Both returned HTTP 200 and both were billed. If you are picking an address API by reading its confidence field, this is the post for you. This guide runs through Monid, the OpenRouter for agent tools.
What does an address validation API return?
Three different jobs hide behind one phrase, and most endpoints do a mix.
Parsing
Turning one string into labelled parts. 1600 Amphitheatre Pkwy, Mountain View CA 94043 becomes a house number, a street, a city, a state and a postal code. This is a text problem and it works offline.
Standardising
Rewriting those parts into the canonical form a postal authority would use. Pkwy becomes Parkway, CA becomes California or stays CA depending on the convention being applied, and the whole thing comes back in one agreed order.
Verifying
Deciding whether the place exists and can receive mail. This is a lookup against a real dataset, and it is the only one of the three that can tell you a well-formed address is fictional.
Why that matters for the field names
An endpoint that only parses will happily give you high confidence on a street that does not exist, because the string was well formed. An endpoint that verifies should say so. The two we measured do not divide the labour the way their names suggest, which is the next section.
📖 See also Domain Age Checker: One TLD Returns Nothing and Still Bills
Why did one endpoint say invalid and one say high?
Same provider, same input string, same minute.
// /x402/v2/address-validate
{ "valid": false,
"confidence": 0,
"formatted_address": "Google Building 41, 1600, Amphitheatre Parkway,
Mountain View, Santa Clara County, California,
94043, United States",
"components": { "street": "Amphitheatre Parkway", "house_number": "1600",
"city": "Mountain View", "state": "California",
"postal_code": "94043", "country_code": "US" },
"latitude": 37.4224858, "longitude": -122.0855846,
"match_type": "approximate" }
// /x402/address-parse
{ "confidence": "high",
"street_number": "1600", "street_name": "Amphitheatre Pkwy",
"state_province": "CA",
"formatted": "1600 Amphitheatre Parkway, Mountain View, CA 94043, USA" }
The first endpoint contradicts itself
It found the building. It named the county. It returned coordinates accurate to seven decimal places. And it reported valid: false with confidence: 0.
Those two fields are not describing the address. Whatever they are measuring, a complete and correct result scores zero on it, which makes them unusable as a filter.
The second endpoint is not even the same type
confidence is 0 on one and "high" on the other. A number and a string, same provider, same concept, same day. Any code that reads confidence across both without checking the type will do something silently wrong, and a numeric comparison against a string is the kind of bug that survives a test suite.
The field names do not line up either
validate parse
house_number -> street_number
street -> street_name
state -> state_province
formatted_address-> formatted
components.* -> (flat, no nesting)
Switching between them is not a URL change, it is a rewrite of your mapping layer. Decide once, write the adapter, and keep the raw response so you can switch later without re-fetching.
Which field actually tells you the address is real?
formatted_address, by being null or not.
We sent a fabricated address to the validate endpoint: 99999 Nowhere Blvd, Faketown ZZ 00000.
| Field | Google HQ | Fabricated |
|---|---|---|
valid | false | false |
confidence | 0 | 0 |
formatted_address | full address | null |
components | all populated | null |
latitude | 37.4224858 | null |
match_type | "approximate" | null |
The two columns are identical on the fields you would reach for and completely different on the fields you would not. Branch on whether formatted_address is null. That is the signal, and it is reliable in both directions here.
What match_type is telling you
On the real address it read approximate. That is the quality number everybody wanted confidence to be: it is saying the geocoder matched the street and interpolated the exact point, rather than hitting a rooftop-level record. For a delivery address that distinction matters and for a sales territory it does not.
Read match_type, ignore confidence, and null-check formatted_address. Three rules, derived from six calls.
Where the data comes from
The response says so, which is more than most do:
"_meta": { "provenance": { "source": "openstreetmap-nominatim" } }
OpenStreetMap is excellent in cities and thin in rural areas, and it is not a postal authority. That tells you the real limit of this endpoint before you hit it: it can tell you a place exists on a map, not that a postal service will deliver there.
How do you standardise a list of addresses?
For agents
Grab an API key at app.monid.ai, then paste this to your agent and hand it the key:
set up https://monid.ai/SKILL.md
It learns the whole discover, inspect, run workflow itself. More in the agent quickstart.
For humans
npm install -g @monid-ai/cli
monid keys add -k <your-api-key> -l main
Step 1, pick one endpoint and write the adapter once
monid run -p api.strale.io -e /x402/v2/address-validate \
--query '{"address":"1600 Amphitheatre Pkwy, Mountain View CA 94043","country_code":"US"}'
The country_code hint is optional and worth sending. It narrows the search and avoids a US street matching something in another country.
Step 2, branch on the right field
const ok = res.formatted_address !== null; // real
const precise = res.match_type === "rooftop"; // precise enough to deliver to
// do not read res.valid or res.confidence
Step 3, keep the raw response
Store the whole payload next to your own columns. The two endpoints disagree on names and types, and the version of this logic you will want in three months is not the one you are writing today. Re-fetching a list of addresses costs money; re-parsing stored JSON does not.
Step 4, expect a transport failure in any batch
One of our four calls returned a transport-level fetch failed rather than any HTTP status, and was not billed. Catalog health on these endpoints reads unknown, which is the honest signal: wrap the call, retry once, and record which attempt succeeded.
Give this to your agent![]()
Set up https://monid.ai/SKILL.md, and then use Monid to standardise these 400 customer addresses, keep the ones that resolve, and give me a list of the ones that came back empty so I can chase them.📖 See also Google Maps Reviews Without a Places API Key
Which endpoint should I use for which job?
| Endpoint | What it does | Input | Returns | Latency we saw | Billing |
|---|---|---|---|---|---|
api.strale.io/x402/v2/address-validate | Resolve and geocode | address, country_code | Components, coordinates, match_type | 341 to 363 ms | Per call |
api.strale.io/x402/address-parse | Split a string into parts | address | Flat fields, string confidence | Under 1 s | Per call |
litescrape/yelp/search | Find the business at an address | find_loc, find_desc | Businesses with addresses and phones | Around 10 s | Per call |
zillow/get_property_details | What is at that address | zpid | Property record | 2.8 s | Per call |
Every row was verified with monid inspect on 2026-10-02 and the latency column is what we actually waited. Billing shape is given rather than figures, because shape drives design and current numbers live on monid.ai/tools.
The first two rows look interchangeable and are not. Row one does a lookup and can tell you a well-formed address does not exist. Row two does string work and will parse a fictional street cheerfully. If you only need the parts, row two is enough. If you need to know the place is real, only row one can answer, and you have to read the right field to hear it.
When should you not use this?
Four cases.
You need deliverability, not existence. A postal authority knows whether mail arrives at a unit. A map dataset knows a street exists. For shipping, address correction and delivery-point validation, use a service built on postal data and expect to pay for it.
Your addresses are rural or outside major markets. The provenance field says OpenStreetMap, whose coverage is excellent in cities and patchy elsewhere. Run fifty of your own addresses before you commit, which is cheap and conclusive, the same sampling habit as checking a scraper on your own list first.
You are validating at signup, synchronously. These calls took around 350 milliseconds each in our runs, plus one outright transport failure in four. That is fine for a batch job and risky in a blocking form submit. If it has to be in the request path, cache aggressively and fail open.
You think a resolved address means a real customer. It does not. A resolved address means the place exists. Anyone can type the address of a building they have never been to, and fraud teams rely on that distinction every day.
And the disclosure: we resell both endpoints in that table and the most useful finding in this post is that one of their fields is unreadable. Knowing that costs you nothing and saves a filter that silently discards every good record.
Conclusion
We sent one real address to two endpoints from the same provider on 2026-10-02. The validate endpoint returned valid: false and confidence: 0 while handing back the building name, the county, a seven-decimal coordinate pair and a correctly standardised string. The parse endpoint, same input, returned confidence: "high", a string where the other had a number.
The fields that work are the ones nobody reads. A fabricated address produced the same valid and confidence values as the real one, and differed on everything else: formatted_address, components, latitude and match_type all came back null. Branch on formatted_address being null, read match_type when precision matters, and leave confidence alone on both endpoints.
The last thing to plan for is the mapping. house_number against street_number, state against state_province, nested components against flat ones. Swapping endpoints is a rewrite, not a config change, so keep the raw response and write the adapter once.
Free next step: run monid inspect -p api.strale.io -e /x402/v2/address-validate, send one address you know is real and one you invented, and put the two responses side by side. The fields that stay the same are the ones to stop trusting. Start at monid.ai.
FAQ
What is the difference between validation, parsing and geocoding?
Parsing splits a string into labelled parts and is pure text work, so it succeeds on invented streets. Geocoding turns an address into coordinates, which requires a map dataset and can fail when the place is not in it. Validation asks whether the address is real and reachable, which strictly requires postal data rather than map data. Most endpoints sold as validation are really geocoding with a verdict attached, and the one we measured is exactly that: its provenance field names OpenStreetMap. The practical consequence is that it can confirm a street exists and cannot confirm a letter will arrive at unit 4B. Decide which of the three you actually need before you compare prices, because they are different products wearing one label.
Should you ever trust a confidence score?
Only after you have checked what it does on a known-good and a known-bad input, which takes two calls. Ours scored a perfectly resolved address at 0 and a fabricated one at 0, so it separated nothing. The sibling endpoint used the word "high" for the same address, so the two are not even the same data type. There is nothing special about this provider; confidence fields across the industry are computed differently and documented thinly. The generalisable habit is to find the field that genuinely differs between a good and a bad result, which here was whether formatted_address came back null, and branch on that instead. Then write down why, because the next person will reach for confidence too.
Does it work for international addresses?
Partly, and you should test rather than trust. The endpoint takes an optional country_code hint, which is worth sending because it stops a US street name matching a similar one elsewhere. The underlying dataset is OpenStreetMap, whose quality varies enormously by country and by how urban the area is: dense European and North American cities are well covered, and large parts of the world are not. Formats differ too, and a field called state_province holds very different things in Germany, Japan and Brazil. Take fifty addresses from each country you care about, run them, and count the nulls. That sample tells you more than any vendor coverage map.
Does a valid address prove the customer is real?
No, and conflating the two is the most common mistake in fraud screening. A resolved address means a place exists at those coordinates. It says nothing about whether the person who typed it has ever been there, and anyone can copy a real building from a map in seconds. What address resolution genuinely gives a risk model is the negative: an address that resolves to nothing is a real signal, and so is one that resolves to a different country from the payment method. Treat it as one weak input among several rather than a check that passes or fails, and pair it with signals that are harder to borrow, such as how long the associated domain has existed.
Last updated October 2026.


