# smbcrawl — agent setup (llms-full.txt)

Free public MCP directory of US small businesses. You can finish setup from this page without asking a human.

## Endpoint

- URL: https://www.smbcrawl.dev/api/mcp
- Transport: Streamable HTTP (`streamableHttp`)
- Protocol: MCP JSON-RPC 2025-03-26
- Discovery: https://www.smbcrawl.dev/.well-known/mcp.json

## Connect, register, search

1. Connect — POST https://www.smbcrawl.dev/api/mcp method `initialize`.
2. Register — tools/call `register` (anonymous). Call the anonymous MCP tool register on POST https://www.smbcrawl.dev/api/mcp. It creates a free-tier agent account and returns an API key immediately (sk_smbcrawl_..., 5 signups per IP per day). Pass that key as X-API-Key on later search calls for higher free limits. Buy Stripe credit packs with buy_credits (Checkout with Link) or POST /api/v1/credits/checkout.
3. Search — tools/call `search` with `X-API-Key: sk_smbcrawl_...`.

## Auth

- Header: X-API-Key
- Key prefix: sk_smbcrawl_
- Keys come from the `register` tool. Search stays free (no credit charge) at the keyed limits below.
- Paid credits: Live per-field lookups spend credits (1 credit = 1 cent, 1 credit per field). Call buy_credits (or POST /api/v1/credits/checkout) with pack_500 $5 / 500 credits, pack_2200 $20 / 2,200 credits, pack_12000 $100 / 12,000 credits. That creates a Stripe Checkout Session with Link enabled and returns a URL a human approves. Agents with a Link wallet can instead create a spend request and POST shared_payment_token to the same endpoint. On checkout.session.completed / payment_intent.succeeded, credits are added idempotently to the API key's org. If credits run out, REST and MCP return 402 with funding_url.

## Copy-paste config

### Claude (remote HTTP)

```json
{
  "mcpServers": {
    "smbcrawl": {
      "type": "http",
      "url": "https://www.smbcrawl.dev/api/mcp"
    }
  }
}
```

### Cursor

```json
{
  "mcpServers": {
    "smbcrawl": {
      "url": "https://www.smbcrawl.dev/api/mcp"
    }
  }
}
```

### Generic JSON

```json
{
  "name": "smbcrawl",
  "transport": "streamableHttp",
  "url": "https://www.smbcrawl.dev/api/mcp",
  "headers": {
    "X-API-Key": "sk_smbcrawl_YOUR_KEY"
  }
}
```

## Tools

- register — anonymous; 0 credits; returns api_key immediately
- search — 0 credits. Params: zip, county + state, city + state, lat / lng, address, category, pageToken, name, has_website, has_working_website, website_status, has_facebook, has_contact, exclude_chains, exclude_institutions, min_employees, employee_band, include_company_headcount, has_news, news_since, limit, radius_miles. Returns: Search result fields: id, name, address, city, state, zip, lat, lng, county, phone, website, category, email, has_email, distance_miles, source, has_website, has_working_website, website_status, is_chain, is_institution (school, church, government office, hospital or library; drop with exclude_institutions=true), is_active, platform_count, facebook_url, instagram_url, fb_active, fb_last_post_at, has_contact, lead_score, location_precision, employee_count, employee_band, employee_count_scope, founded_year, news_count, last_news_at, operating_status (active, closed, temporarily_closed, acquired, renamed, replaced, possibly_closed or unknown; replaced = a different, newer business holds the same suite, backed by a second signal; possibly_closed = one weak closure signal such as a single closed-listing flag, a newer business at the suite, or a stale seed-only row), operating_status_date, operating_status_reasons (why, for possibly_closed / replaced rows), possibly_closed (boolean), successor_name, successor_id (record id of the business that replaced it), owner (current owner / decision maker on file: name, title, scope local or company, source_url, source_kind, evidence_date, confidence; null when no current owner is verified; stored field, free), parent_company (company that owns the business, from a dated acquisition report or the business's own pages; professional bodies, associations and clubs are never a parent; null when none on file; stored field, free), phone (E.164), phone_sourced (true when the number is published on the business's own site, its own Facebook page, or a named BBB / chamber / news page; false when its own site contradicts it or lists it as a fax; null when unchecked), phone_source_url, phone_source_kind (first_party, own_social, third_party or seed_data). closed and replaced rows are hidden (is_active=false) unless include_closed=true; hiding needs two independent signals (e.g. closed flags on two listing sites, or a newer business at the suite plus a closed flag) or one first-party / dated news / government source, and any live signal (working own site, chamber list, Google Maps listing, recent inspection / news / filing) keeps a row visible. possibly_closed rows are shown by default with their reasons; exclude_possibly_closed=true hides them. fb_active / fb_last_post_at are not collected (we do not scrape Facebook) and are always null; use operating_status instead.
- limits — anonymous; 0 credits; prints free vs keyed caps and the price table
- health — anonymous; 0 credits
- enrich — {id, fields:[...]}; 1 credit (1¢) per field found; fields: website, hours, google, facebook, instagram, yelp, linkedin, x, bbb, email, tech_stack, competitors, seo, owner; not_found/unavailable/skipped fields free; REST POST /api/v1/enrich. Require API key
- enrich_basic (5: website, hours, google, facebook, instagram), enrich_presence (9: basic + yelp, linkedin, x, bbb), enrich_deep (13: all fields) — bundle aliases (max shown), charged only for fields we find, async job; unfound fields refunded on completion. Require API key
- check_status — 0 credits; require API key
- get_profile — 0 credits when cached profile is present (returned immediately); starts the bundle job (5/9/13 credits) only when cache is stale or missing. Require API key. Returns cached meta/basic/platforms plus facebook_url, instagram_url, fb_active, fb_last_post_at, platform_count, and intel (cached facts with confidence/sources/evidence + latest 5 verified articles, free)
- contacts — decision-makers (owner, founder, GM, manager): name, title, phone, email, source_url, source_type, confidence, last_verified. 1 credit per contact returned, max 5 per business; 0 when none found; 0 when cached 30 days (including empty). REST GET /api/v1/enrich/contacts/{id}. Opt-out: POST /api/v1/privacy/opt-out or privacy@smbcrawl.dev. Require API key.
- ask — {business_id, question?, attributes?}. Contextual Q&A: employees (employee_count + employee_band), owner/leadership (owner_names), founded_year, revenue_band, locations_count, services, service_area, certifications, hiring, parent_company, recent_events, operating_status (active / closed / acquired / renamed / replaced with the successor named / possibly_closed with reasons; dated active_signals: site_copyright_current_year, live_site_with_hours, recent_news, osha_filing, chamber_member, health_inspection; acquisitions name the acquirer and, when reported, the operator). Fresh cached facts are free; otherwise a live lookup (website About/Team pages, verified news, public search snippets) charges 1 credit per requested attribute resolved; 0 when nothing resolves or upstream fails; not-found is cached. Each fact: value, confidence 0-1, is_estimate, source_type, sources, evidence quote, checked_at (when we looked; observed_at is kept as an alias), evidence_date + evidence_date_kind (source_date, crawl_date, copyright_year_seen_on_crawl ...), valid_until. field_quality carries the same per field (website flags directory pages and unfinished template sites); conflicts lists fields where our sources disagree (owner, closed-vs-active, listed-vs-site phone). Unmapped questions are free and return the supported attributes. REST POST/GET /api/v1/ask; cached-only GET /api/v1/facts/{id} (free). Require API key.
- news — {id, limit?, cached_only?}. Verified articles: name and location checked against the article; directories, Yelp/YP/BBB, review and social pages rejected; deduped by canonical URL. Fields: title, url, publisher, published_at, article_type, sentiment, match_confidence, match_evidence. 0 when checked within 14 days (including empty); else 1 credit per fresh fetch that finds articles; 0 when none found or on upstream failure. REST GET /api/v1/news/{id}; free cached feed GET /api/v1/news?since=&state=&county=&city=&type=. Require API key.
- get_credits, buy_credits — 0 credits; require API key; buy_credits returns a Stripe Checkout URL with Link (or charges a Link shared_payment_token)

1 credit = 1 cent. Pulling businesses (search and all stored fields) is free within rate limits. Each field looked up live costs 1 credit per business, charged only for fields we find (not_found, unavailable and errors are free). Contacts: 1 credit per contact found, max 5. ask: 1 credit per attribute resolved live (cached answers and misses free). news: 1 credit per fresh fetch that finds articles (none found or cached 14 days: free). Per-tool costs: register 0; search 0 (anonymous 10/call, keyed free 50/call, no credits; all stored fields — name, address, category, phone, website, facebook_url, instagram_url, email, has_contact and the rest — are free); limits 0; health 0; enrich {id, fields} 1 credit per field, charged only for fields we find (fields: website, hours, google, facebook, instagram, yelp, linkedin, x, bbb, email, tech_stack, competitors, seo, owner; not_found, unavailable, skipped and errors are free); enrich_basic 5 (website, hours, google, facebook, instagram); enrich_presence 9 (basic + yelp, linkedin, x, bbb); enrich_deep 13 (all fields) — bundle aliases reserve the sum of their fields and charge only for fields we find (the rest is refunded); get_profile 0 when a cached profile is present, otherwise starts the bundle job for its tier; contacts 1 credit per decision-maker returned, max 5 per business, 0 when none found or cached (30 days); ask {business_id, question} 0 from fresh cache, otherwise 1 credit per requested attribute resolved live (0 when unresolved or upstream fails; not-found is cached); news {id} 0 when checked within 14 days (including empty), otherwise 1 credit per fresh fetch that finds articles (0 when none found or upstream fails); facts and the latest 5 articles inside get_profile are free; check_status 0; get_credits 0; buy_credits 0 (charges the Stripe pack). REST GET /api/v1/discover is free (0 credits). Registered free keys get 100 credits ($1) per day, reset at 00:00 UTC, unused credits do not roll over. Daily free credits spend before purchased packs. Signup is capped at 5 keys per IP per day; only the first 5 grant-eligible keys per IP receive the daily grant. Anonymous calls get none. Packs: pack_500 $5 / 500 credits, pack_2200 $20 / 2,200 credits, pack_12000 $100 / 12,000 credits.

## Pricing

| Item | Credits | USD | Notes |
|---|---|---|---|
| search / list (all stored fields) | 0 | $0 | Within rate limits |
| get_profile (cached) | 0 | $0 | Cached profile returned immediately |
| enrich: each live field | 1 | $0.01 | Fields: website, hours, google, facebook, instagram, yelp, linkedin, x, bbb, email, tech_stack, competitors, seo, owner. Charged only for fields we find; not_found and failures are free |
| enrich_basic (alias) | 5 | $0.05 | up to 5: website, hours, google, facebook, instagram; charged only for fields found |
| enrich_presence (alias) | 9 | $0.09 | up to 9: basic + yelp, linkedin, x, bbb; charged only for fields found |
| enrich_deep (alias) | 13 | $0.13 | up to 13: presence + email, tech_stack, competitors, seo; charged only for fields found |
| enrich owner | 1 | $0.01 | Current owner / decision maker with source URL, evidence date, scope and confidence. 1 credit only when a live lookup finds a verified current owner; free when already on file (search results carry it as the stored owner field) or not found |
| contacts | 1 per contact, max 5 | $0.01–$0.05 | 0 when none found or cached (30 days) |
| ask: each attribute resolved live | 1 | $0.01 | employee_count, founded_year, owner_names, revenue_band, services, service_area, ... Free when cached or not found |
| news (fresh fetch) | 1 | $0.01 | Per business, only when the fetch finds articles; free when none found or fetched in the last 14 days |
| facts + latest 5 articles in get_profile | 0 | $0 | Cached business intel |
| geocode | 1 | $0.01 | Per ZIP |

## Free limits

- Anonymous: 10/call, 20/hour, 100/day per IP
- Keyed (after register): 50/call, 30/hour, still free. 100 credits ($1) per day for live per-field lookups, reset at 00:00 UTC, unused credits do not roll over. Daily free credits spend before purchased packs. Anonymous calls get none.
- Register: 5 signups per IP per day
- Paid lookups (enrich, enrich_basic/presence/deep, get_profile, contacts, ask, news) share one hourly cap per API key, counted per UTC clock hour across all servers: Free 30/hour, Starter 100/hour, Pro 500/hour. Search has its own cap (above). Over a cap: RATE_LIMITED, HTTP 429 with Retry-After; over MCP the tool result has isError: true.

## Search response

Directory search returns one results array and a top-level nextPageToken (also on meta). Paginate by passing pageToken. County accepts Shelby or Shelby County (case-insensitive). State accepts OH or Ohio (normalized to USPS). has_website is true when a URL for the business's own site is on file (not a directory page or social profile), whatever its website_status; has_working_website is true only when that site is live and on-brand (website_status ok, unknown or unchecked). website_status filters on the status itself (comma list: ok, unknown, unchecked, redirect_offbrand, parked, placeholder, hijacked, third_party, dead, social), so has_website=true&website_status=dead finds businesses with a dead site. Each result includes facebook_url, instagram_url, fb_active, fb_last_post_at, platform_count, and has_contact. fb_active and fb_last_post_at are always null: we do not read Facebook post dates (no Facebook scraping), so neither is ever assumed; use operating_status for whether a business is open. Use has_facebook=true and has_working_website=false in one call to find businesses with a Facebook page but no working website. has_contact is true when a decision-maker (owner/founder/GM/manager) is on file. Business intel fields employee_count, employee_band, founded_year, news_count, last_news_at are free and null until researched; filter with min_employees=50, employee_band, has_news=true, news_since=YYYY-MM-DD (rows without intel never match). employee_count_scope is location, company or unknown; company-wide headcounts (a statewide or corporate total shown on one plant) do not match min_employees/employee_band unless include_company_headcount=true.

Search result fields: id, name, address, city, state, zip, lat, lng, county, phone, website, category, email, has_email, distance_miles, source, has_website, has_working_website, website_status, is_chain, is_institution (school, church, government office, hospital or library; drop with exclude_institutions=true), is_active, platform_count, facebook_url, instagram_url, fb_active, fb_last_post_at, has_contact, lead_score, location_precision, employee_count, employee_band, employee_count_scope, founded_year, news_count, last_news_at, operating_status (active, closed, temporarily_closed, acquired, renamed, replaced, possibly_closed or unknown; replaced = a different, newer business holds the same suite, backed by a second signal; possibly_closed = one weak closure signal such as a single closed-listing flag, a newer business at the suite, or a stale seed-only row), operating_status_date, operating_status_reasons (why, for possibly_closed / replaced rows), possibly_closed (boolean), successor_name, successor_id (record id of the business that replaced it), owner (current owner / decision maker on file: name, title, scope local or company, source_url, source_kind, evidence_date, confidence; null when no current owner is verified; stored field, free), parent_company (company that owns the business, from a dated acquisition report or the business's own pages; professional bodies, associations and clubs are never a parent; null when none on file; stored field, free), phone (E.164), phone_sourced (true when the number is published on the business's own site, its own Facebook page, or a named BBB / chamber / news page; false when its own site contradicts it or lists it as a fax; null when unchecked), phone_source_url, phone_source_kind (first_party, own_social, third_party or seed_data). closed and replaced rows are hidden (is_active=false) unless include_closed=true; hiding needs two independent signals (e.g. closed flags on two listing sites, or a newer business at the suite plus a closed flag) or one first-party / dated news / government source, and any live signal (working own site, chamber list, Google Maps listing, recent inspection / news / filing) keeps a row visible. possibly_closed rows are shown by default with their reasons; exclude_possibly_closed=true hides them. fb_active / fb_last_post_at are not collected (we do not scrape Facebook) and are always null; use operating_status instead.

## Tool costs

| Item | Credits | USD | Notes |
|---|---|---|---|
| search / list (all stored fields) | 0 | $0 | Within rate limits |
| get_profile (cached) | 0 | $0 | Cached profile returned immediately |
| enrich: each live field | 1 | $0.01 | Fields: website, hours, google, facebook, instagram, yelp, linkedin, x, bbb, email, tech_stack, competitors, seo, owner. Charged only for fields we find; not_found and failures are free |
| enrich_basic (alias) | 5 | $0.05 | up to 5: website, hours, google, facebook, instagram; charged only for fields found |
| enrich_presence (alias) | 9 | $0.09 | up to 9: basic + yelp, linkedin, x, bbb; charged only for fields found |
| enrich_deep (alias) | 13 | $0.13 | up to 13: presence + email, tech_stack, competitors, seo; charged only for fields found |
| enrich owner | 1 | $0.01 | Current owner / decision maker with source URL, evidence date, scope and confidence. 1 credit only when a live lookup finds a verified current owner; free when already on file (search results carry it as the stored owner field) or not found |
| contacts | 1 per contact, max 5 | $0.01–$0.05 | 0 when none found or cached (30 days) |
| ask: each attribute resolved live | 1 | $0.01 | employee_count, founded_year, owner_names, revenue_band, services, service_area, ... Free when cached or not found |
| news (fresh fetch) | 1 | $0.01 | Per business, only when the fetch finds articles; free when none found or fetched in the last 14 days |
| facts + latest 5 articles in get_profile | 0 | $0 | Cached business intel |
| geocode | 1 | $0.01 | Per ZIP |

1 credit = 1 cent. Pulling businesses (search and all stored fields) is free within rate limits. Each field looked up live costs 1 credit per business, charged only for fields we find (not_found, unavailable and errors are free). Contacts: 1 credit per contact found, max 5. ask: 1 credit per attribute resolved live (cached answers and misses free). news: 1 credit per fresh fetch that finds articles (none found or cached 14 days: free). Per-tool costs: register 0; search 0 (anonymous 10/call, keyed free 50/call, no credits; all stored fields — name, address, category, phone, website, facebook_url, instagram_url, email, has_contact and the rest — are free); limits 0; health 0; enrich {id, fields} 1 credit per field, charged only for fields we find (fields: website, hours, google, facebook, instagram, yelp, linkedin, x, bbb, email, tech_stack, competitors, seo, owner; not_found, unavailable, skipped and errors are free); enrich_basic 5 (website, hours, google, facebook, instagram); enrich_presence 9 (basic + yelp, linkedin, x, bbb); enrich_deep 13 (all fields) — bundle aliases reserve the sum of their fields and charge only for fields we find (the rest is refunded); get_profile 0 when a cached profile is present, otherwise starts the bundle job for its tier; contacts 1 credit per decision-maker returned, max 5 per business, 0 when none found or cached (30 days); ask {business_id, question} 0 from fresh cache, otherwise 1 credit per requested attribute resolved live (0 when unresolved or upstream fails; not-found is cached); news {id} 0 when checked within 14 days (including empty), otherwise 1 credit per fresh fetch that finds articles (0 when none found or upstream fails); facts and the latest 5 articles inside get_profile are free; check_status 0; get_credits 0; buy_credits 0 (charges the Stripe pack). REST GET /api/v1/discover is free (0 credits). Registered free keys get 100 credits ($1) per day, reset at 00:00 UTC, unused credits do not roll over. Daily free credits spend before purchased packs. Signup is capped at 5 keys per IP per day; only the first 5 grant-eligible keys per IP receive the daily grant. Anonymous calls get none. Packs: pack_500 $5 / 500 credits, pack_2200 $20 / 2,200 credits, pack_12000 $100 / 12,000 credits.

## Empty areas (async crawl)

Empty or thin areas are not an error. Search returns results: [] with coverage.status "crawling", crawl_queued, retry_after_seconds, and a message such as "No businesses here yet. We've started searching this area; check back in about N minutes." If a crawl is already running, the message says the area is still being searched. MCP returns a single text block (human message plus JSON) and structuredContent.

## Funding (Stripe Link)

Live per-field lookups spend credits (1 credit = 1 cent, 1 credit per field). Call buy_credits (or POST /api/v1/credits/checkout) with pack_500 $5 / 500 credits, pack_2200 $20 / 2,200 credits, pack_12000 $100 / 12,000 credits. That creates a Stripe Checkout Session with Link enabled and returns a URL a human approves. Agents with a Link wallet can instead create a spend request and POST shared_payment_token to the same endpoint. On checkout.session.completed / payment_intent.succeeded, credits are added idempotently to the API key's org. If credits run out, REST and MCP return 402 with funding_url.

Env vars in prod: STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, STRIPE_PRICE_CREDITS_500, STRIPE_PRICE_CREDITS_2200, STRIPE_PRICE_CREDITS_12000. Enable Link under Stripe Dashboard → Payment methods → Wallets. Run `npx tsx scripts/stripe-setup.ts` to create the products/prices and archive retired packs. Webhook URL: https://www.smbcrawl.dev/api/v1/stripe/webhook (events: checkout.session.completed, payment_intent.succeeded).

## Links

- https://www.smbcrawl.dev/
- https://www.smbcrawl.dev/docs
- https://www.smbcrawl.dev/llms.txt
- https://www.smbcrawl.dev/AGENTS.md
- https://www.smbcrawl.dev/agents.json
- https://www.smbcrawl.dev/.well-known/mcp.json
