ARTICLE NAVIGATION
If you are reading this, you have probably typed something like “DataForSEO business listings same results different cities” into Google. We did too. We got nothing useful back. So here is what happens, why it happens, and the one parameter change that fixes it.
Short version for people in a hurry: business_data/business_listings/search/live silently ignores the location_name parameter. Use location_coordinate with latitude, longitude, and a radius. Add a filters entry for address_info.country_code if you want US-only results. Working code is below.
Long version follows. Skip ahead if you just need the working pattern.
What Happened
We are building a contractor database at Kore Komfort Solutions. The plan was straightforward. Use DataForSEO’s Business Listings API to discover roofing, HVAC, plumbing, and electrical contractors in major US metros. Add new records nightly. Scale up over time. The math worked: at roughly $0.30 per metro-trade query and a yield of a few hundred contractors per query, we could grow the database meaningfully on a tight budget.
The first night went well. Chicago roofing returned 813 net-new contractors for $0.31. We thought we had a working pattern. We did not.
The next night we ran Chicago HVAC, Chicago plumbing, and Chicago electrical. The first one returned 746 net-new records. The next two returned zero. We assumed Chicago had been over-sampled by earlier work and moved on.
Two days later we tried a wider rotation. Twenty-four queries across six different US metros: Houston, Dallas, Phoenix, Philadelphia, San Antonio, Atlanta. Each one across all four trades.
Twenty-four queries returned three net-new records. Total. We had spent roughly $7. We had been certain we were querying six distinct cities. The dedup rate looked like the same data set returning over and over.
That is the moment we realized something was off.
The Symptom: Identical Results Across Six Cities
Here is the pattern that gave it away. Across the six metros we queried, the filtered record counts were identical to the byte:
- Houston roofing: 608 records after filter
- Dallas roofing: 608 records after filter
- Phoenix roofing: 608 records after filter
- Philadelphia roofing: 608 records after filter
- San Antonio roofing: 608 records after filter
- Atlanta roofing: 608 records after filter
That cannot happen by chance. Six wildly different metros cannot return exactly the same number of post-filter records.
The Diagnostic: Two Queries, $0.62
We ran two diagnostic queries. Both used location_name as we had been doing. The only difference between them was the city.
# Query 1: Houston
curl -s -X POST "https://api.dataforseo.com/v3/business_data/business_listings/search/live" \
-H "Authorization: Basic $AUTH" \
-H "Content-Type: application/json" \
-d '[{"categories":["roofing_contractor"],"description":"roofing contractor","location_name":"Houston,TX,United States","limit":5}]'
# Query 2: Phoenix (same call, different location_name)
curl -s -X POST "https://api.dataforseo.com/v3/business_data/business_listings/search/live" \
-H "Authorization: Basic $AUTH" \
-H "Content-Type: application/json" \
-d '[{"categories":["roofing_contractor"],"description":"roofing contractor","location_name":"Phoenix,AZ,United States","limit":5}]'
Both queries returned the same five contractors in the same order:
- American Homestead Exteriors, Mansfield, OH
- DJM Coating Svc, Santa Rosa, CA
- Peach State Roofing, Alabaster, AL
- Flow Roofing (no address)
- Tinsley Roofing LLC, Selma, TX
None of them are in Houston. None of them are in Phoenix. The location_name parameter was being completely ignored. The endpoint was returning the same global highest-rated contractors per category every time, regardless of what city we asked for.
The Fix: location_coordinate Instead
The DataForSEO documentation for business_data/business_listings/search/live tells the truth in plain sight. Their official example uses location_coordinate, not location_name:
“To set a location, we will use the London coordinates. You should specify latitude, longitude, and radius,
location_coordinate: 51.509865,-0.118092,10.”
That is from their docs page for the endpoint. The example payload uses location_coordinate. The location_name parameter is not listed in the field reference for this specific endpoint, although it appears prominently in their other endpoints (SERP, keywords data) where it works correctly.
We rebuilt the diagnostic query using coordinates. Houston center at 29.7604, -95.3698, with a 30km radius:
curl -s -X POST "https://api.dataforseo.com/v3/business_data/business_listings/search/live" \
-H "Authorization: Basic $AUTH" \
-H "Content-Type: application/json" \
-d '[{
"categories": ["roofing_contractor"],
"description": "roofing contractor",
"location_coordinate": "29.7604,-95.3698,30",
"filters": [["address_info.country_code", "=", "US"]],
"limit": 10
}]'
The response was ten actual Houston-area contractors. Bellaire, Friendswood, Houston proper, Sugar Land. All within Harris and Fort Bend counties. All US-coded. The filter on address_info.country_code stops Canadian and other international results from leaking in, which they otherwise will, since Google Maps data does not respect borders.
Working bash script on GitHub. Same auth, same endpoint. The only difference is the location parameter and the country_code filter.
Before and After: The Numbers
We re-ran the same workflow with location_coordinate. The change was not subtle.
Before the fix (location_name). Chicago, IL, roofing returned the same 1,000 global top-rated contractors as every other city. After dedup against records we had already inserted, net-new yield was near zero on every query after the first one.
After the fix (location_coordinate). Chicago, IL, roofing at 50km radius returned 232 actual records. The geographic distribution made sense: 177 in Illinois, 7 in Indiana (which is correct, the 50km radius from Chicago center crosses into northwest Indiana), 1 unverified.
We then ran a 200-query batch across 50 US metros, all four trades. Total cost: $3.91. Total net-new contractors added: 3,772. After fixing one parameter name in the request payload, the same workflow that had been useless for three nights produced a real geographic database in twelve minutes.
To put the cost difference in concrete terms: with location_name, our first 24 queries cost about $7 and added 3 records. With location_coordinate, our next 200 queries cost about $4 and added 3,772 records. The unit economics are not in the same universe.
Why This Happens (and Why It Is Easy to Miss)
DataForSEO has a large API surface. They serve search engine results, keyword data, backlink intelligence, business listings, and several adjacent products. Most of those products use location_name as the primary geographic parameter, and the format is consistent across them. "Chicago,IL,United States" works for SERP queries. It works for keyword volume queries. It works for backlink locality queries.
For Business Listings Search, it does not work. Their docs do not say it fails. The API does not return an error when you send it. The response is shaped exactly like the response from a working query. The endpoint silently substitutes a default global ranking and returns those results, with no indication that the location filter was discarded.
If you came to business_data/business_listings/search/live from one of DataForSEO’s other endpoints, you almost certainly carried location_name with you, because it is the parameter you have been using everywhere else in their product. The docs example for this specific endpoint shows location_coordinate, but it does not flag location_name as unsupported. The pattern from other endpoints leaks in. The query runs. The results come back. The bill arrives. The data is wrong, but it looks right enough that you do not notice until you compare results across multiple queries.
Once you know what to look for, the failure mode is obvious. Two queries with different cities should return two different result sets. If they return the same set, the location parameter is not doing what you think it is doing.
The Working Script
The full working script is on GitHub at kore-komfort-solutions/dataforseo-business-listings-fix. Here is the minimum viable version, the smallest amount of code that demonstrates the fix:
#!/usr/bin/env bash
# Minimum working example: discover contractors at a coordinate.
# Replace LAT, LNG, RADIUS_KM, and CATEGORY with your values.
source ~/.dataforseo.env # exports DATAFORSEO_LOGIN and DATAFORSEO_PASSWORD
AUTH=$(echo -n "${DATAFORSEO_LOGIN}:${DATAFORSEO_PASSWORD}" | base64 -w 0)
LAT="29.7604"
LNG="-95.3698"
RADIUS_KM="30"
CATEGORY="roofing_contractor"
curl -s -X POST \
"https://api.dataforseo.com/v3/business_data/business_listings/search/live" \
-H "Authorization: Basic $AUTH" \
-H "Content-Type: application/json" \
-d "[{
\"categories\": [\"$CATEGORY\"],
\"description\": \"roofing contractor\",
\"location_coordinate\": \"$LAT,$LNG,$RADIUS_KM\",
\"filters\": [[\"address_info.country_code\", \"=\", \"US\"]],
\"limit\": 1000,
\"order_by\": [\"rating.value,desc\"]
}]" | jq '.tasks[0].result[0].items[] | {title, address, rating: .rating.votes_count}'
Three things this gets right that the broken version did not:
- Geographic filter that actually works.
location_coordinatewith lat/lng/radius is the documented and functional way to constrain results to a specific area. - Country filter at the API level. The
filtersarray entry stops international records from entering the response, so you do not have to clean them out post-hoc. - Honest sort order.
order_by: rating.value,descreturns highest-rated contractors first, which is usually what you want for prospecting.
For radius selection: a 30km radius covers most US metropolitan areas and their first-ring suburbs. Mega-metros (NYC, LA, Chicago, Houston, DFW) benefit from 50km. Smaller metros (Wichita, Reno, Boise) work fine at 25km. There is no penalty for over-shooting; DataForSEO returns at most 1,000 records per query regardless of how big the radius is, but the records will be sorted by relevance to the center coordinate.
What This Cost Us
We spent about $13 on DataForSEO across the entire experiment. Of that, roughly $8 was the cost of debugging. Three iterations of broken code, two batches of queries that returned mostly duplicates, several diagnostic runs to confirm the bug. The remaining $5 produced a database of just over 7,000 net-new contractor records across 60+ US metropolitan areas.
If we had known about the location_coordinate requirement at the start, the same database would have cost about $4 and taken less than an hour. The $8 of debug cost was the price of figuring out a single parameter name. That is not a complaint, just a number worth putting on the page so the next person Googling this problem can see it and know they are not crazy for spending real money on it.
Takeaways for Developers
If you are using DataForSEO Business Listings Search, three things are worth remembering.
First, the parameter you want is location_coordinate, not location_name. The docs example shows it. The field reference does not call out location_name as invalid for this endpoint, but in practice it is silently ignored. If you are seeing the same results across multiple cities, this is the cause.
Second, always include a country code filter. The endpoint will return Canadian, UK, Australian, and other international records at a coordinate, especially in border regions. Adding filters: [["address_info.country_code", "=", "US"]] to your request payload (adjusted for your target country) will save you a cleanup pass.
Third, run a diagnostic before scaling. Before you run 200 queries, run two queries with different parameters and compare the results. If a parameter you think is doing something is not actually doing it, you want to find out at $0.62 of cost, not at $7 of cost.
The Business Listings API is a useful product. The geographic filter works. The country filter works. The combination of category, coordinate, and country lets you build a real database for not much money. The single gotcha is the parameter name, and now you know it.
If you are interested in what we built with this (a contractor intelligence database used internally for our Echelon Reports product), that is a different story for a different post. For now, the working code is on GitHub, the bug is documented, and the fix is one parameter name away.
Frequently Asked Questions
Does this affect other DataForSEO endpoints?
No. The SERP, Keywords Data, Backlinks, and DataForSEO Labs endpoints all support location_name correctly with formats like "Chicago,Illinois,United States". The issue is specific to business_data/business_listings/search/live. If you are using location_name elsewhere, keep using it. Just not here.
Why use kilometers instead of miles for the radius?
DataForSEO’s documented format is "latitude,longitude,radius_in_km". We have not tested whether the API accepts a unit suffix or alternative formats. The kilometer convention works and matches their examples, so we stuck with it.
Are the results limited to 1,000 records per query?
Yes. The limit parameter caps at 1,000. Some metros (Manhattan, central LA) probably have more contractors than that in DataForSEO’s index, but you will only see the top 1,000 by rating. If you need to capture beyond the 1,000 ceiling, your options are: shrink the radius and run multiple overlapping queries, or use the offset parameter for pagination on subsequent calls.
What is a typical query cost?
Pricing is roughly $0.01 per task plus $0.0003 per row returned. A query that returns 1,000 rows costs approximately $0.31. A query that returns 50 rows costs approximately $0.025. The cost scales with results, so smaller metros are cheaper.
Does DataForSEO know about this?
Possibly. We have not contacted them directly about it. If they read this and want to update their docs to flag the difference, we welcome the change. The point of writing this up is that the next developer hitting this problem should not have to spend $8 to figure it out.
Can I use this approach for non-contractor business categories?
Yes. The categories array accepts any of the Google Maps category IDs DataForSEO supports. plumber, electrician, hvac_contractor, pizza_restaurant, dentist, etc. The location_coordinate fix is independent of the category. If you are pulling restaurants instead of contractors, the same pattern works.