---
name: showfirst-prospecting
description: Find and research local businesses with ShowFirst. Search one kind of business in one place or across many places, wait for the search to finish, read or filter the results, and export CSVs for GoHighLevel or Instantly. Use when the user asks to find, list, export or look up local businesses, leads or prospects with ShowFirst.
---

# ShowFirst prospecting

ShowFirst finds local businesses of one kind in a place (roofers in Sarasota, dentists across five cities) and returns each one with its phone, email, website, owner name where known, reviews and a check of its website. You can use it two ways:

- **MCP tools**, if ShowFirst is connected as an MCP server: `find_location`, `search_businesses`, `search_status`, `get_results`, `export_results`, `create_campaign`, `start_campaign`, `pause_campaign`, `stop_campaign`, `get_campaign_leads`, `export_campaign`, `enrich_domains`, `check_credits`.
- **REST API**, with curl or any HTTP client: everything under `/api/prospecting/`.

The two do the same thing with the same lead credits. Use MCP tools when they are available; otherwise use the REST calls below.

## The rule that matters

Searches use the user's lead credits. Nothing is held or used until you confirm.

1. Send the search or campaign **without** `confirm` first. Nothing starts. The reply says how many lead credits it will hold (`lead_credits_held`) and the current balance.
2. Tell the user that number in plain words, for example "This search holds up to 300 lead credits. You have 1,400. Unused credits come back when it finishes."
3. Wait for a clear yes. Then send the same request with `"confirm": true`.

Never confirm on the user's behalf, and never raise `depth` or add places without asking.

## Setup

- **Base URL:** `https://showfirst.app`. If the user's dashboard is on their own domain, use that domain instead.
- **API key:** the user makes one in their ShowFirst dashboard under **API Keys** (type a label, click **+ Create New Key**). It starts with `lgen_`. Ask the user to put it in an environment variable rather than pasting it into chat:

```bash
export SHOWFIRST_API_KEY=lgen_...
```

Send it on every request as `Authorization: Bearer $SHOWFIRST_API_KEY`. Never write the key into a file in the project, a commit or a log.

- **MCP instead:** in Claude Code, `claude mcp add --transport http showfirst https://showfirst.app/api/mcp --header "Authorization: Bearer $SHOWFIRST_API_KEY"`. Other clients: see https://showfirst.app/docs/mcp.

Check the key works and see the balance:

```bash
curl -s https://showfirst.app/api/prospecting/credits \
  -H "Authorization: Bearer $SHOWFIRST_API_KEY"
# {"ok": true, "lead_credits": 1400}
```

`401` means the key is missing, wrong or revoked. `403` means the key is not linked to an account; ask the user to make a new one on the API Keys page.

## One place: search, poll, results, export

### 1. Find the place

```bash
curl -s "https://showfirst.app/api/prospecting/locations?q=Sarasota" \
  -H "Authorization: Bearer $SHOWFIRST_API_KEY"
```

Returns up to 8 matches, best first, each with `location_code` and `location_name` ("Sarasota,Florida,United States"). Pick the one the user means; if two could fit (Springfield), ask. If nothing matches, try "City, State" or a larger nearby city. You can also skip this and send `location` as text; the best match is used and named in the reply.

MCP: `find_location {query}`.

### 2. Plan the search

```bash
curl -s -X POST https://showfirst.app/api/prospecting/searches \
  -H "Authorization: Bearer $SHOWFIRST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"keyword": "roofing contractor", "location_code": 1015192, "depth": 300}'
```

- `keyword` (required): the kind of business, as a person would type it into Google Maps.
- `location_code` or `location` (one is required).
- `depth`: how many businesses to look through, `100` (default), `300`, `500` or `700`. Bigger towns and broad trades need more. Start at 100 or 300 unless the user wants more.
- `radius_miles` (optional): `10`, `25`, `50` or `100` keeps only businesses that close to the place.

The reply has `"requires_confirmation": true` and a `plan` with `lead_credits_held`. That is not an error. Tell the user and wait.

With 1 to 99 lead credits left, a depth 100 search holds the balance and returns at most that many businesses: the plan shows `lead_credits_held` and `max_leads`. Tell the user the cap.

MCP: `search_businesses {keyword, location_code | location, depth, radius_miles}`.

### 3. Start it

Same request plus `"confirm": true`. The reply has a `job_id`; keep it.

MCP: `search_businesses {..., confirm: true}`.

### 4. Poll until it is done

```bash
curl -s https://showfirst.app/api/prospecting/searches/$JOB_ID \
  -H "Authorization: Bearer $SHOWFIRST_API_KEY"
```

Poll every 10 to 20 seconds. Report `progress` (0 to 100) and `enriched_count` of `total_businesses` as it moves; don't promise a finish time. Stop when `status` is:

- `completed`: read the results.
- `no_results`: nothing matched. All held credits came back. Suggest a broader keyword or a bigger place.
- `failed`: tell the user. Unused credits came back.

Keep polling until one of these. The final check is what returns unused credits.

MCP: `search_status {job_id}`.

### 5. Read the results

```bash
curl -s "https://showfirst.app/api/prospecting/searches/$JOB_ID/results?limit=100" \
  -H "Authorization: Bearer $SHOWFIRST_API_KEY"
```

Returns `total`, `offset`, `limit`, `next_offset` and `businesses`. Pages are 25 by default, 100 at most; keep calling with `offset=next_offset` until `next_offset` is `null`. Each business has 28 fields, including `name`, `category`, `website`, `email`, `phone`, `mobile_number`, `owner`, `greeting_name` (the name that is safe to greet them with, empty when unsure), `contact` (address, city, state, all emails, all phones), `reputation` (rating, reviews, claimed), `web` (tech, socials) and `site_report` (the website check).

Filters, on results and exports: `has_website`, `has_email`, `has_phone`, `has_mobile_phone`, `has_verified_email`, each `true` or `false`. "Businesses with no website" is `has_website=false`. "Ones I can email" is `has_email=true`. "Verified emails only" is `has_verified_email=true` (the email passed the mailbox check; catch-all, unknown and unchecked emails are left out).

Asking before the search is completed gets `409`. Keep polling.

MCP: `get_results {job_id, offset, limit, has_*}`.

### 6. Export

Add `format` to get a CSV file:

| `format` | For |
|---|---|
| `simple` | A readable sheet with the main columns. |
| `advanced` | Every column. |
| `ghl` | Importing into GoHighLevel. |
| `instantly` | Importing into Instantly (rows without an email are left out). |

```bash
curl -s -o roofers-sarasota.csv \
  "https://showfirst.app/api/prospecting/searches/$JOB_ID/results?format=simple&has_email=true" \
  -H "Authorization: Bearer $SHOWFIRST_API_KEY"
```

Ask where to save the file if the user hasn't said, and tell them the path afterwards.

MCP: `export_results {job_id, format, has_*}` returns the CSV text.

## Many places: campaigns

A campaign runs one keyword across a list of places.

1. **Plan** (nothing starts):

```bash
curl -s -X POST https://showfirst.app/api/prospecting/campaigns \
  -H "Authorization: Bearer $SHOWFIRST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"keyword": "plumber", "locations": ["Tampa, Florida", "Sarasota, Florida", "Bradenton, Florida"], "depth_per_location": 100, "max_leads": 250}'
```

Send `location_codes` (up to 200) or `locations` as text. `max_leads` caps the total. The reply has the lead estimate, about how long it takes, and `lead_credits_held` (places x `depth_per_location`, or `max_leads` if lower). Tell the user and wait.

2. **Start**: the same request with `"confirm": true`. Add `"idempotency_key": "<a UUID>"` so a retry can't start it twice. Keep the `campaign_id`.

3. **Poll and read**: `GET /api/prospecting/campaigns/$CAMPAIGN_ID/results` about once a minute. It returns `campaign` (with `status` and `leads_found`) and a page of the businesses found so far, with the same paging, filters and `format` as a search. **A campaign only moves on to its next places when it is checked**, so keep polling until `status` is `completed`, `stopped` or `failed`.

4. **Control**: `POST /api/prospecting/campaigns/$CAMPAIGN_ID/pause`, `/resume` or `/stop`. Stop is final; businesses found so far stay available and unused credits come back. `needs_attention` means it paused because lead credits ran low; tell the user, and resume once they have added credits.

MCP: `create_campaign` (plan, or `confirm: true`), `start_campaign` (start new, or resume by `campaign_id`), `get_campaign_leads`, `pause_campaign`, `stop_campaign`, `export_campaign`.

## Looking up websites the user already has

For 1 to 10 websites the user already has, without a search:

```bash
curl -s -X POST https://showfirst.app/api/prospecting/enrich \
  -H "Authorization: Bearer $SHOWFIRST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"domains": ["gulfcoastroofing.com", "example-plumbing.com"]}'
```

This uses 1 lead credit per website as soon as it starts, with no plan step, so tell the user the count and get a yes first. It returns a `job_id`; poll and read it exactly like a search. More than 10 websites: split into calls of 10 and confirm the total first.

MCP: `enrich_domains {domains}`.

## Errors

Every error has `"ok": false` and an `error` sentence you can show the user; some add `details`.

| Status | What to do |
|---|---|
| 400 | Fix the field named in `error`/`details` (for example, `depth` must be 100, 300, 500 or 700). |
| 401 | The key is missing, wrong or revoked. Ask the user to check `SHOWFIRST_API_KEY`. |
| 402 | Not enough lead credits. Nothing started. Tell the user `balance` and `required`; offer a smaller `depth` or fewer places. |
| 403 | The key has no account. The user makes a new key on the API Keys page. |
| 404 | Unknown `job_id`/`campaign_id` (or another account's), or no place matched `location`. |
| 409 | The search hasn't finished. Keep polling. |
| 429 | The search service is busy. Wait 10 seconds and retry, at most 3 times. |
| 502, 503 | The search service had a problem. Wait and retry once; any hold for a request that didn't start came back. |

Don't retry a confirmed search or campaign blindly after a timeout: check its status (or the user's balance) first, so it doesn't run twice.

## Good habits

- Restate what you are about to run (keyword, place, depth) when you quote the lead credits.
- Summarise results before dumping them: how many found, how many have email, phone or a website.
- Respect the filters the user asks for instead of filtering by hand.
- Docs: https://showfirst.app/docs/api-prospecting and https://showfirst.app/docs/mcp.
