Business Monster API
Everything the dashboard does, from your own code. Start a Google Maps search, poll it, and download every business with contact details, verified emails, social profiles, reviews and photos as JSON, NDJSON, CSV, XLSX or XML.
https://api.businessmonster.co/v1Auth: Bearer API key1 credit = 1 businessNo minimum per searchWorks with Claude & ChatGPT (MCP)API keys are created in your dashboard under API. New accounts start with 20 free credits, so you can make your first calls before buying anything.
Quick start
Three calls: start a search, wait for it to finish, download the records.
# 1. Start a search
curl -X POST https://api.businessmonster.co/v1/searches \
-H "Authorization: Bearer bm_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"location": "Austin, TX, USA", "keyword": "dentist", "max_results": 500}'
# 2. Poll until "status" is "completed" (every 5 to 10 seconds is fine)
curl https://api.businessmonster.co/v1/searches/SEARCH_ID \
-H "Authorization: Bearer bm_live_YOUR_KEY"
# 3. Download the records in the format you need
curl "https://api.businessmonster.co/v1/searches/SEARCH_ID/results?format=xlsx" \
-H "Authorization: Bearer bm_live_YOUR_KEY" -o dentists-austin.xlsx
Authentication
Send your key in the Authorization header of every request:
Authorization: Bearer bm_live_23e8a631…
- Keys are created and revoked in the dashboard (API page). A key is shown once, at creation; we store only a hash of it.
- You can have up to 5 active keys, for example one per integration. Revoking a key stops it immediately.
- Every key acts on the credits of the account that created it. Keep keys on your server, never in browser code.
AI assistants (MCP)
Business Monster ships an MCP server, so Claude, ChatGPT, Cursor and any other assistant that speaks the Model Context Protocol can run searches for you directly in the conversation. Ask for "opticians in Kütahya with phone numbers" and the assistant starts the search, waits for it, and hands you the file. It spends the credits on your own account, at the same prices.
https://api.businessmonster.co/mcpStreamable HTTPStatelessProtocol 2024-11-05 to 2026-07-28Connecting
If your client can send headers (Cursor, Claude Code, n8n, most SDKs), point it at the server URL and add your key as a Bearer token:
{
"mcpServers": {
"business-monster": {
"url": "https://api.businessmonster.co/mcp",
"headers": { "Authorization": "Bearer bm_live_YOUR_KEY" }
}
}
}
If your client only accepts a URL (for example Claude's "Add custom connector" box), put the key in the path instead. It works exactly the same:
https://api.businessmonster.co/mcp/k/bm_live_YOUR_KEY
Tools the assistant gets
| Tool | What it does |
|---|---|
| search_businesses | Starts a search from a location and a keyword. Returns a search id straight away. Spends credits. |
| get_search | Status and progress. Takes wait_seconds (up to 45) so the assistant waits on the server instead of polling in a loop. |
| get_search_results | A page of records. view compact (default), simple (the 38-column lead list) or full (every field); fields to pick columns; unwind=reviews for one row per review; a page size, an offset, or the last N. |
| cancel_search | Cancels a search that has not started yet and releases its reserved credits. |
| list_searches | Your recent searches with status and credit cost. |
| get_account | Credit balance, current prices and limits. |
| list_available_fields | The fields a business record can contain, grouped by topic, so the assistant can answer "can you get their Instagram?" without guessing. |
What to expect
- Results come back a page at a time. A conversation cannot hold a thousand rows, so the assistant reads 25 records at a time, up to 200 if it asks, and can request only the fields you asked for. The complete file with all 100+ fields is downloaded from your dashboard, never handed out as a link in the chat.
- Searches are not instant. A few hundred businesses take one to five minutes. The assistant checks the status every 15 to 30 seconds while it waits.
- Same credits, same prices. 1 credit per business; reviews and photos beyond the free first page cost 10 items = 1 credit. No surcharge for using MCP. The assistant is told to warn you before starting anything larger than about 1,000 businesses.
- Same limits. 60 requests per minute per key. No limit on how many searches run at once.
- Built for assistants. Every tool declares an output schema, and every result carries a
summaryof what happened and anext_stepnaming the single follow-up call with the id already filled in. That is what keeps the assistant from guessing its way through a multi-step job.
Endpoints
| Endpoint | What it does |
|---|---|
| POST/v1/searches | Start a search. Returns the search object with status: "queued". |
| GET/v1/searches | List your searches, newest first. Query: limit (1 to 100), page, status. |
| GET/v1/searches/{id} | Status, progress, result count and credits of one search. |
| GET/v1/searches/{id}/results | Download records. Query: format = json, ndjson, csv, xlsx, xml; after_id for NDJSON streaming. |
| POST/v1/searches/{id}/cancel | Cancel a search that has not started yet. Reserved credits are released. |
| GET/v1/account | Credit balance, current prices and your limits. |
Start a search
POST/v1/searches
JSON body. Only location and keyword are required.
| Field | Type | Description |
|---|---|---|
| location | string, required | Where to search. City, region or country, e.g. "Austin, TX, USA", "Berlin, Germany", "Manchester, UK". |
| keyword | string, required | What to search for, as you would type it on Google Maps: "dentist", "coffee shop", "car rental". |
| max_results | integer | Upper bound on businesses. Omit it to collect everything the area yields, limited only by your credits. Any value from 1 upwards. |
| language | string | Google Maps language code for names and categories, default en. Examples: de, fr, es, tr. |
| max_reviews_per_place | integer, 0 to 5000 | Reviews to collect for each business, newest first. Default 0 keeps the free first page only. |
| max_images_per_place | integer, 0 to 5000 | Photos to collect for each business. Default 0 keeps the free first page only. |
| reviews_since | date, YYYY-MM-DD | Only collect reviews newer than this date. |
| max_credits | integer | Spending cap for this search. The search stops before it would exceed this amount. |
status: "queued" and starts automatically after a top-up.The search object
{
"id": "24b3ca0c-1f3a-4111-9ff5-31feb4e51660",
"status": "completed", // queued · running · completed · failed · cancelled
"location": "Eskişehir",
"keyword": "pharmacy",
"query": "Eskişehir, Turkey pharmacy",
"language": "en",
"max_results": 20,
"max_reviews_per_place": 15,
"max_images_per_place": 0,
"reviews_since": null,
"max_credits": null,
"results_count": 20,
"credits": { "reserved": 50, "charged": 27 }, // charged is null until the search finishes
"created_at": "2026-09-11T06:40:43Z",
"started_at": "2026-09-11T06:41:02Z",
"completed_at": "2026-09-11T06:42:18Z",
"error": null
}
- queued: accepted, waiting to start or for credits. running: collecting. completed: results ready. failed: something went wrong, nothing charged for missing records. cancelled: cancelled before it started, reserved credits released.
errorisnullor carries acodeand amessage:
| status | error.code | Meaning |
|---|---|---|
| queued | insufficient_credits | Waiting for credits. Top up and the search starts by itself. |
| queued | waiting_for_other_searches | Your other searches hold the credits; it starts when they finish. |
| queued | service_unavailable | The service is temporarily unavailable; the search starts by itself when it resumes. |
| completed | no_results | Finished but found no businesses for that location and keyword. Nothing charged. |
| failed | service_unavailable | Stopped because the service was unavailable; all reserved credits refunded. Try again later. |
| failed | search_failed | The search failed; nothing charged for missing records. |
Results and formats
GET/v1/searches/{id}/results?format=json
Each record is a full business profile with 100+ fields: name, category, address, coordinates, phone, website, opening hours, rating and review count, price level, verified email, social profiles (Facebook, Instagram, LinkedIn, X, TikTok, YouTube and more), website technology signals and, when requested, the reviews and photos. See what's in a record.
| format | Available | Notes |
|---|---|---|
| json | completed | A JSON array of nested records. Default. |
| ndjson | while running and after | One record per line. Each line has _id (a cursor) and cid (the business's Google id). Pass the last _id as after_id to fetch only new records. |
| csv | completed | Flattened, one row per business, nested fields as columns. |
| xlsx | completed | Excel workbook, same layout as the dashboard export. |
| xml | completed | Nested XML. |
Choosing the columns: view, fields, unwind
| Parameter | Values | What it does |
|---|---|---|
view | full (default), simple | simple is the lead list: 38 flat columns — title, categoryName, address, street, city, postalCode, state, countryCode, phone, website, email, email_status, totalScore, reviewsCount, imagesCount, url, the 19 social profile fields (instagram, facebook, linkedin, x_twitter, youtube, tiktok, whatsapp, pinterest, threads, telegram, snapchat, discord, vimeo, twitch, medium, github, gitlab, behance, dribbble), has_meta_pixel, has_google_analytics, has_gtm. Works with every format. |
fields | comma-separated keys | Only these fields, in this order, e.g. fields=title,phone,website,location. Names are the record's own keys; a missing key gives null. In CSV and XLSX the headers are these names and nested values are JSON strings. Only with view=full. |
unwind | reviews | One row per review: the business fields repeat on every row and the review's own fields (stars, text, publishedAtDate, name, reviewUrl, ...) are added. A business without reviews keeps one row. Only with view=full. |
GET /v1/searches/{id}/results?format=csv&view=simple → the lead list
GET /v1/searches/{id}/results?format=json&fields=title,phone,email → three columns only
GET /v1/searches/{id}/results?format=xlsx&unwind=reviews&fields=title,stars,text → one row per review
Streaming a running search:
GET /v1/searches/{id}/results?format=ndjson&after_id=0 → first batch, remember the last _id
GET /v1/searches/{id}/results?format=ndjson&after_id=48213 → only records written since
Pricing and credits
The API uses the same credits as the dashboard, with no extra fee.
- 1 credit = 1 business record. All 100+ fields included.
- Reviews and photos: the first page is always included. Beyond it, 10 reviews = 1 credit and 10 photos = 1 credit, per business.
- No minimum per search. Ask for 5 businesses or 50,000.
- No results, no charge. Credits never expire.
Check your balance any time with GET /v1/account. Buy credits in the dashboard.
Errors
Errors use HTTP status codes and a JSON body:
{ "error": { "code": "insufficient_credits", "message": "Not enough credits to start a search. Available: 0. Buy credits in your dashboard." } }
| HTTP | code | Meaning |
|---|---|---|
| 401 | unauthorized, invalid_api_key, api_key_revoked | Missing, wrong or revoked key. |
| 402 | insufficient_credits | Not enough available credits to start a search. |
| 403 | account_disabled | The account is disabled. |
| 404 | not_found | No search with that id on your account. |
| 409 | search_not_finished, cannot_cancel | Results requested before completion, or cancelling a search that already started. |
| 422 | validation_error | A field is missing or invalid. details lists each field. |
| 429 | rate_limited | Slow down (see Retry-After). |
| 500 | server_error | Something broke on our side. Retry later; nothing is charged for missing records. |
Rate limits
- 60 requests per minute per API key. Above that you get
429 rate_limitedwith aRetry-Afterheader. Polling a search every 5 to 10 seconds stays far below the limit. - There is no limit on how many searches run at the same time, nor on the number of businesses or credits.
Code examples
Python
import requests, time
API = "https://api.businessmonster.co/v1"
H = {"Authorization": "Bearer bm_live_YOUR_KEY"}
search = requests.post(f"{API}/searches", headers=H, json={
"location": "Austin, TX, USA", "keyword": "dentist", "max_results": 500,
}).json()
while search["status"] in ("queued", "running"):
time.sleep(10)
search = requests.get(f"{API}/searches/{search['id']}", headers=H).json()
if search["status"] == "completed":
rows = requests.get(f"{API}/searches/{search['id']}/results",
headers=H, params={"format": "json"}).json()
print(len(rows), "businesses,", search["credits"]["charged"], "credits")
Node.js
const API = "https://api.businessmonster.co/v1";
const H = { Authorization: "Bearer bm_live_YOUR_KEY", "Content-Type": "application/json" };
let search = await (await fetch(`${API}/searches`, {
method: "POST", headers: H,
body: JSON.stringify({ location: "Berlin, Germany", keyword: "coffee shop", max_results: 300 }),
})).json();
while (["queued", "running"].includes(search.status)) {
await new Promise(r => setTimeout(r, 10000));
search = await (await fetch(`${API}/searches/${search.id}`, { headers: H })).json();
}
const rows = await (await fetch(`${API}/searches/${search.id}/results?format=json`, { headers: H })).json();
console.log(rows.length, "businesses");
Streaming while the search runs (Python)
import json
after = 0
while True:
r = requests.get(f"{API}/searches/{search['id']}/results",
headers=H, params={"format": "ndjson", "after_id": after}, stream=True)
for line in r.iter_lines():
rec = json.loads(line)
after = rec["_id"]
handle(rec) # your code
status = requests.get(f"{API}/searches/{search['id']}", headers=H).json()["status"]
if status not in ("queued", "running"):
break
time.sleep(10)
Questions? Write to us from the dashboard's Support page, or open the interactive reference to try every endpoint in the browser.