Local Businesses
curl --request POST \
--url https://win.oneshotagent.com/v1/tools/local/search \
--header 'Content-Type: application/json' \
--data '
{
"name": "<string>",
"address": "<string>",
"city": "<string>",
"region": "<string>",
"postal_code": "<string>",
"phone": "<string>",
"category": [
"<string>"
],
"keywords": [
"<string>"
],
"location": [
"<string>"
],
"min_rating": 123,
"min_review_count": 123,
"is_chain": true,
"operating_status": {},
"has_domain": true,
"limit": 123
}
'import requests
url = "https://win.oneshotagent.com/v1/tools/local/search"
payload = {
"name": "<string>",
"address": "<string>",
"city": "<string>",
"region": "<string>",
"postal_code": "<string>",
"phone": "<string>",
"category": ["<string>"],
"keywords": ["<string>"],
"location": ["<string>"],
"min_rating": 123,
"min_review_count": 123,
"is_chain": True,
"operating_status": {},
"has_domain": True,
"limit": 123
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
name: '<string>',
address: '<string>',
city: '<string>',
region: '<string>',
postal_code: '<string>',
phone: '<string>',
category: ['<string>'],
keywords: ['<string>'],
location: ['<string>'],
min_rating: 123,
min_review_count: 123,
is_chain: true,
operating_status: {},
has_domain: true,
limit: 123
})
};
fetch('https://win.oneshotagent.com/v1/tools/local/search', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://win.oneshotagent.com/v1/tools/local/search",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'name' => '<string>',
'address' => '<string>',
'city' => '<string>',
'region' => '<string>',
'postal_code' => '<string>',
'phone' => '<string>',
'category' => [
'<string>'
],
'keywords' => [
'<string>'
],
'location' => [
'<string>'
],
'min_rating' => 123,
'min_review_count' => 123,
'is_chain' => true,
'operating_status' => [
],
'has_domain' => true,
'limit' => 123
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://win.oneshotagent.com/v1/tools/local/search"
payload := strings.NewReader("{\n \"name\": \"<string>\",\n \"address\": \"<string>\",\n \"city\": \"<string>\",\n \"region\": \"<string>\",\n \"postal_code\": \"<string>\",\n \"phone\": \"<string>\",\n \"category\": [\n \"<string>\"\n ],\n \"keywords\": [\n \"<string>\"\n ],\n \"location\": [\n \"<string>\"\n ],\n \"min_rating\": 123,\n \"min_review_count\": 123,\n \"is_chain\": true,\n \"operating_status\": {},\n \"has_domain\": true,\n \"limit\": 123\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://win.oneshotagent.com/v1/tools/local/search")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"<string>\",\n \"address\": \"<string>\",\n \"city\": \"<string>\",\n \"region\": \"<string>\",\n \"postal_code\": \"<string>\",\n \"phone\": \"<string>\",\n \"category\": [\n \"<string>\"\n ],\n \"keywords\": [\n \"<string>\"\n ],\n \"location\": [\n \"<string>\"\n ],\n \"min_rating\": 123,\n \"min_review_count\": 123,\n \"is_chain\": true,\n \"operating_status\": {},\n \"has_domain\": true,\n \"limit\": 123\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://win.oneshotagent.com/v1/tools/local/search")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"<string>\",\n \"address\": \"<string>\",\n \"city\": \"<string>\",\n \"region\": \"<string>\",\n \"postal_code\": \"<string>\",\n \"phone\": \"<string>\",\n \"category\": [\n \"<string>\"\n ],\n \"keywords\": [\n \"<string>\"\n ],\n \"location\": [\n \"<string>\"\n ],\n \"min_rating\": 123,\n \"min_review_count\": 123,\n \"is_chain\": true,\n \"operating_status\": {},\n \"has_domain\": true,\n \"limit\": 123\n}"
response = http.request(request)
puts response.read_bodyLocal Businesses
Local Businesses
Discover main-street businesses by category and location, and resolve a business name + address to its website domain, phone, and operating status.
POST
/
v1
/
tools
/
local
/
search
Local Businesses
curl --request POST \
--url https://win.oneshotagent.com/v1/tools/local/search \
--header 'Content-Type: application/json' \
--data '
{
"name": "<string>",
"address": "<string>",
"city": "<string>",
"region": "<string>",
"postal_code": "<string>",
"phone": "<string>",
"category": [
"<string>"
],
"keywords": [
"<string>"
],
"location": [
"<string>"
],
"min_rating": 123,
"min_review_count": 123,
"is_chain": true,
"operating_status": {},
"has_domain": true,
"limit": 123
}
'import requests
url = "https://win.oneshotagent.com/v1/tools/local/search"
payload = {
"name": "<string>",
"address": "<string>",
"city": "<string>",
"region": "<string>",
"postal_code": "<string>",
"phone": "<string>",
"category": ["<string>"],
"keywords": ["<string>"],
"location": ["<string>"],
"min_rating": 123,
"min_review_count": 123,
"is_chain": True,
"operating_status": {},
"has_domain": True,
"limit": 123
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
name: '<string>',
address: '<string>',
city: '<string>',
region: '<string>',
postal_code: '<string>',
phone: '<string>',
category: ['<string>'],
keywords: ['<string>'],
location: ['<string>'],
min_rating: 123,
min_review_count: 123,
is_chain: true,
operating_status: {},
has_domain: true,
limit: 123
})
};
fetch('https://win.oneshotagent.com/v1/tools/local/search', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://win.oneshotagent.com/v1/tools/local/search",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'name' => '<string>',
'address' => '<string>',
'city' => '<string>',
'region' => '<string>',
'postal_code' => '<string>',
'phone' => '<string>',
'category' => [
'<string>'
],
'keywords' => [
'<string>'
],
'location' => [
'<string>'
],
'min_rating' => 123,
'min_review_count' => 123,
'is_chain' => true,
'operating_status' => [
],
'has_domain' => true,
'limit' => 123
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://win.oneshotagent.com/v1/tools/local/search"
payload := strings.NewReader("{\n \"name\": \"<string>\",\n \"address\": \"<string>\",\n \"city\": \"<string>\",\n \"region\": \"<string>\",\n \"postal_code\": \"<string>\",\n \"phone\": \"<string>\",\n \"category\": [\n \"<string>\"\n ],\n \"keywords\": [\n \"<string>\"\n ],\n \"location\": [\n \"<string>\"\n ],\n \"min_rating\": 123,\n \"min_review_count\": 123,\n \"is_chain\": true,\n \"operating_status\": {},\n \"has_domain\": true,\n \"limit\": 123\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://win.oneshotagent.com/v1/tools/local/search")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"<string>\",\n \"address\": \"<string>\",\n \"city\": \"<string>\",\n \"region\": \"<string>\",\n \"postal_code\": \"<string>\",\n \"phone\": \"<string>\",\n \"category\": [\n \"<string>\"\n ],\n \"keywords\": [\n \"<string>\"\n ],\n \"location\": [\n \"<string>\"\n ],\n \"min_rating\": 123,\n \"min_review_count\": 123,\n \"is_chain\": true,\n \"operating_status\": {},\n \"has_domain\": true,\n \"limit\": 123\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://win.oneshotagent.com/v1/tools/local/search")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"<string>\",\n \"address\": \"<string>\",\n \"city\": \"<string>\",\n \"region\": \"<string>\",\n \"postal_code\": \"<string>\",\n \"phone\": \"<string>\",\n \"category\": [\n \"<string>\"\n ],\n \"keywords\": [\n \"<string>\"\n ],\n \"location\": [\n \"<string>\"\n ],\n \"min_rating\": 123,\n \"min_review_count\": 123,\n \"is_chain\": true,\n \"operating_status\": {},\n \"has_domain\": true,\n \"limit\": 123\n}"
response = http.request(request)
puts response.read_bodyresearch/company and enrich/company are backed by a B2B database: strong for a 40-person HVAC company, thin for the single-location taqueria. These two endpoints cover the long tail — independent restaurants, contractors, dental practices, auto shops, owner-operator trucking companies.
Both are async. You POST a request, get back a request_id, and poll GET /v1/requests/{request_id} until the job finishes. Both return the same LocalResult shape.
All paid endpoints on this page accept optional
memo (≤ 1000 chars) and
decisionContext (object) body fields. Stored on the receipt for debugging
and audit — see Audit Trail.Local Resolve
POST /v1/tools/local/resolve
Given a business name and a street address, what is this place’s domain?
Every public registry (a state business licence, NPPES, FMCSA) gives you a legal name and an address. enrich/email needs company_domain. This is the join.
string
required
Business name as it appears on the record, e.g.
"TAQUERIA LOS AMIGOS"string
Street address, e.g.
"2334 Mission St"string
City
string
State / province. Not a locating field on its own.
string
Postal code
string
Phone number. The strongest single match signal after the name.
name plus at least one of address, city, postal_code, or phone is required.
curl -X POST https://win.oneshotagent.com/v1/tools/local/resolve \
-H "Content-Type: application/json" \
-H "X-Payment-Proof: <signature>" \
-d '{
"name": "Franklin Barbecue",
"address": "900 E 11th St",
"city": "Austin",
"region": "TX"
}'
Response Structure
{
"status": "completed",
"result": {
"found": true,
"confidence": 0.97,
"result": {
"domain": "franklinbbq.com",
"operating_status": "open",
"confidence": 0.97,
"phone": "(512) 653-1187",
"category": "Barbecue restaurant",
"is_chain": null,
"name": "Franklin Barbecue",
"address": "900 E 11th St, Austin, TX 78702",
"socials": {},
"review_count": 8412,
"rating": 4.7,
"id": "loc_3f1c9a2b7d8e4f60",
"website": "https://franklinbbq.com/",
"latitude": 30.2701,
"longitude": -97.7313
},
"candidates_considered": 4
}
}
A miss is a completed job with
found: false, result: null, and the best confidence seen — the same contract as enrich/email. It is never an error. When a candidate existed but did not clear the threshold, it is returned as closest_match so you can inspect it.confidence is 0–1, a weighted mean over the signals you supplied (name, phone, address, postal code, city). found requires confidence ≥ 0.65 and a name similarity ≥ 0.5.
Local Search
POST /v1/tools/local/search
Which businesses of this kind operate in this place?
Discovery by category × geography. Priced flat per search, not per result, so you can size a territory without metering every row.
string[]
Business categories, e.g.
["hvac contractor", "dental practice"] (max 50). category or keywords is required.string[]
Free-text keywords, an alternative to
category (max 50)string[]
required
Cities, neighborhoods, or
"City, ST" strings, e.g. ["Austin, TX", "Round Rock, TX"] (max 50)number
Minimum star rating, 0–5
number
Minimum review count
boolean
true keeps only detected chains; false drops them. Rows where no chain was detected have is_chain: null and survive false.'open' | 'any'
default:"open"
open drops businesses detected as closedboolean
true returns only rows with a resolvable website domain — the ones enrich/email can act onnumber
default:"100"
Max results, 1–500. See
truncated below.curl -X POST https://win.oneshotagent.com/v1/tools/local/search \
-H "Content-Type: application/json" \
-H "X-Payment-Proof: <signature>" \
-d '{
"category": ["hvac contractor"],
"location": ["Austin, TX"],
"has_domain": true,
"is_chain": false,
"limit": 50
}'
Response Structure
{
"status": "completed",
"result": {
"results": [
{
"domain": "example-hvac.com",
"operating_status": "open",
"phone": "(512) 555-0142",
"category": "HVAC contractor",
"is_chain": null,
"name": "Example Heating & Air",
"address": "4100 Example Ln, Austin, TX 78744",
"socials": {},
"review_count": 212,
"rating": 4.8,
"id": "loc_a91b2c3d4e5f6a70",
"website": "https://example-hvac.com/",
"latitude": 30.19,
"longitude": -97.75
}
],
"total_found": 50,
"truncated": false,
"vendor_calls": 5,
"filters": { "category": ["hvac contractor"], "location": ["Austin, TX"], "has_domain": true, "is_chain": false, "limit": 50 }
}
}
Each search is capped at a fixed number of upstream calls to keep the flat price honest. When the cap stops the search before
limit is reached, truncated is true. In practice a search yields up to ~60 rows (three pages of 20); split large territories across searches.The LocalResult shape
Fields are ordered by how much they matter to a caller: the join key first, then whether the place is real and operating, then contactability.| Field | Type | Notes |
|---|---|---|
domain | string | null | Bare website domain. null when the business only has a social or aggregator page (see socials). Pass to enrich/email as company_domain. |
operating_status | open | closed | Best-effort closure detection. |
confidence | number | Resolve results only. |
phone | string | null | |
category | string | null | Free-text category from the data source. |
is_chain | true | null | true when the business is a known national brand or the same name appears at 3+ addresses in the result set. null means not detected — never false. |
name, address | string | |
socials | object | Platform → URL, e.g. { "facebook": "https://facebook.com/…" }. |
review_count, rating | number | null | |
id | string | Stable across runs and data sources (a hash of normalized name + address). Dedupe on this. |
website, latitude, longitude | Raw website URL and coordinates when available. |
What a registry still answers
A local tool tells you whether a business is real, operating, and reachable. It does not carry a licence issue date, an NPI taxonomy, an FMCSA authority type, or a NAICS code. Registries say who just became a buyer and what they’re licensed to do; these endpoints say whether they’re real and how to reach them. Use both.SDK
const territory = await agent.localSearch({
category: ['hvac contractor'],
location: ['Austin, TX'],
has_domain: true,
is_chain: false,
limit: 50,
});
const match = await agent.localResolve({
name: 'TAQUERIA LOS AMIGOS',
address: '2334 Mission St',
city: 'San Francisco',
region: 'CA',
});
if (match.found) {
const { email } = await agent.findEmail({ full_name: 'Owner Name', company_domain: match.result!.domain! });
}