{
  "openapi": "3.0.1",
  "info": {
    "title": "BBB Scraper - Business Ratings, Reviews, Complaints & B2B Leads",
    "description": "Scrape bbb.org (Better Business Bureau, USA & Canada) business profiles: BBB rating, accreditation, contact and lead-gen data (decoded email, phone, website, socials, owner), address + coordinates, business type, years in business, licenses, hours, plus opt-in customer reviews and complaints..",
    "version": "1.0",
    "x-build-id": "LQPYc2ZRfSlhF7GdP"
  },
  "servers": [
    {
      "url": "https://api.apify.com/v2"
    }
  ],
  "paths": {
    "/acts/abotapi~bbb-org-scraper/run-sync-get-dataset-items": {
      "post": {
        "operationId": "run-sync-get-dataset-items-abotapi-bbb-org-scraper",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/acts/abotapi~bbb-org-scraper/runs": {
      "post": {
        "operationId": "runs-sync-abotapi-bbb-org-scraper",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor and returns information about the initiated run in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/runsResponseSchema"
                }
              }
            }
          }
        }
      }
    },
    "/acts/abotapi~bbb-org-scraper/run-sync": {
      "post": {
        "operationId": "run-sync-abotapi-bbb-org-scraper",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "inputSchema": {
        "type": "object",
        "required": [
          "mode"
        ],
        "properties": {
          "mode": {
            "title": "Mode",
            "enum": [
              "search",
              "url"
            ],
            "type": "string",
            "description": "Choose 'search' to run business-term + location searches with filters, or 'url' to scrape pasted BBB search-result pages and business-profile URLs (both are auto-detected and can be mixed).",
            "default": "search"
          },
          "searchTerms": {
            "title": "Business terms",
            "type": "array",
            "description": "Only used when mode = search — ignored in url mode. Business category or keyword terms to search on BBB, e.g. \"plumber\", \"roofing\", \"hvac\". Each term is combined with the location below and searched separately.",
            "items": {
              "type": "string"
            }
          },
          "search": {
            "title": "Search (alias)",
            "type": "string",
            "description": "API alias merged into Business terms."
          },
          "location": {
            "title": "Location",
            "type": "string",
            "description": "City + state (US) or city + province (Canada) to search within, e.g. \"Los Angeles, CA\" or \"Toronto, ON\". A ZIP / postal code also works. Leave empty for a nationwide search of the selected country. To search several places in one run, add them under \"More locations\" below."
          },
          "locations": {
            "title": "More locations",
            "type": "array",
            "description": "Optional. Extra cities, states/provinces or ZIP/postal codes to search in the same run, e.g. \"San Diego, CA\", \"90210\". Every business term is searched in the Location above AND in each of these; a business found by more than one search is returned (and billed) once. Search mode only.",
            "items": {
              "type": "string"
            }
          },
          "country": {
            "title": "Country",
            "enum": [
              "USA",
              "CAN"
            ],
            "type": "string",
            "description": "Country to search within. Covers the USA and Canada.",
            "default": "USA"
          },
          "sort": {
            "title": "Sort order",
            "enum": [
              "Relevance",
              "Rating",
              "Distance",
              "AToZ",
              "ZToA"
            ],
            "type": "string",
            "description": "Order of search results, applied by BBB itself.",
            "default": "Relevance"
          },
          "distance": {
            "title": "Search radius (miles)",
            "minimum": 1,
            "maximum": 500,
            "type": "integer",
            "description": "Limit results to businesses within this many miles of the location (BBB's own distance filter; needs a Location). Leave empty for BBB's default radius."
          },
          "accreditedOnly": {
            "title": "Accredited businesses only",
            "type": "boolean",
            "description": "Only return BBB-accredited businesses; non-accredited results are skipped and not billed. Applies to keyword searches (BBB filters those itself) and pasted search-result URLs; a directly pasted profile URL is always returned.",
            "default": false
          },
          "minRating": {
            "title": "Minimum BBB rating",
            "enum": [
              "A+",
              "A",
              "A-",
              "B+",
              "B",
              "B-",
              "C+",
              "C",
              "C-",
              "D+",
              "D",
              "D-",
              "F"
            ],
            "type": "string",
            "description": "Only return businesses rated at least this BBB letter grade (e.g. \"B\" keeps A+ through B, and drops B- and below). Businesses BBB has not rated (NR) are skipped. Leave empty for every rating. Applies to keyword searches and pasted search-result URLs; skipped businesses are not billed."
          },
          "category": {
            "title": "Category filter",
            "type": "string",
            "description": "Optional. Narrow each search to one BBB business category, by name (e.g. \"Commercial Plumber\", \"Drain Cleaning\") or BBB category code (e.g. \"10113-100\"). A name is matched to BBB's own category filter for that search; if BBB doesn't offer it there, each business's own categories are matched instead. Search mode only."
          },
          "stateFilter": {
            "title": "State / province filter",
            "pattern": "^$|^[A-Za-z]{2}$",
            "type": "string",
            "description": "Optional 2-letter state or province code (e.g. \"TX\", \"ON\") applied by BBB itself. Most useful with an empty Location (a nationwide search). Search mode only."
          },
          "requestQuoteOnly": {
            "title": "Only businesses that take quote requests",
            "type": "boolean",
            "description": "Only return businesses that accept \"Get a Quote\" requests through BBB (BBB's own filter). Search mode only.",
            "default": false
          },
          "servesMyAreaOnly": {
            "title": "Only businesses serving the location",
            "type": "boolean",
            "description": "Only return businesses BBB lists as serving the searched location, including ones based elsewhere (BBB's \"Serving my Area\" filter). Needs a Location. Search mode only.",
            "default": false
          },
          "onlyWithComplaints": {
            "title": "Only businesses with complaints",
            "type": "boolean",
            "description": "Only return businesses that have at least one complaint on file with BBB (useful for reputation research). BBB's search does not offer this filter, so each business's profile is opened to read its complaint count; businesses without complaints are skipped and not billed, so a run may return fewer than Max businesses. Works in Search and URL mode.",
            "default": false
          },
          "startUrls": {
            "title": "BBB URLs",
            "type": "array",
            "description": "Only used when mode = url — ignored in search mode. Paste BBB search-result pages (https://www.bbb.org/search?... , filters and page= all work) or direct business-profile URLs — USA (https://www.bbb.org/us/ca/los-angeles/profile/plumber/fords-plumbing-1216-100081178 ) or Canada (https://www.bbb.org/ca/on/toronto/profile/house-cleaning/custom-maids-incorporated-0107-1151951 ), with or without a trailing slash, a /customer-reviews or /complaints suffix, or tracking params. BBB category pages (https://www.bbb.org/us/ca/los-angeles/category/plumber , also state-, country- and Canada-level) work like a search. Search, category and profile URLs are auto-detected and can be mixed in one run.",
            "default": [
              "https://www.bbb.org/us/ca/los-angeles/profile/plumber/fords-plumbing-1216-100081178",
              "https://www.bbb.org/ca/on/toronto/profile/house-cleaning/custom-maids-incorporated-0107-1151951",
              "https://www.bbb.org/us/ca/los-angeles/profile/plumber/fords-plumbing-1216-100081178/customer-reviews",
              "https://www.bbb.org/us/ca/los-angeles/profile/plumber/fords-plumbing-1216-100081178/complaints",
              "https://www.bbb.org/search?find_country=USA&find_text=plumber&find_loc=Los+Angeles%2C+CA&page=1",
              "https://www.bbb.org/us/ca/los-angeles/category/plumber"
            ],
            "items": {
              "type": "string"
            }
          },
          "maxItems": {
            "title": "Max businesses",
            "minimum": 0,
            "maximum": 100000,
            "type": "integer",
            "description": "Hard cap on the number of business profiles returned across the whole run. This is the run's cap. Maximum 100,000; 0 = up to the maximum of 100,000.",
            "default": 20
          },
          "maxPagesPerQuery": {
            "title": "Max pages per search / URL",
            "minimum": 0,
            "type": "integer",
            "description": "Maximum result pages walked per search term / pasted search or category URL (15 businesses per page). Leave empty or use 0 for up to 100 pages: the run then stops at Max businesses, at BBB's own last page (BBB shows at most 15 pages per sort order; see Maximize coverage) or at 100 pages, whichever comes first. Values above 100 are treated as 100."
          },
          "maximizeCoverage": {
            "title": "Maximize coverage (beyond BBB's 225-result view)",
            "type": "boolean",
            "description": "BBB shows at most 15 pages (225 businesses) per search, whatever the total it reports (e.g. 7,000+ plumbers in Los Angeles). When a search or pasted search/category URL is cut off this way, also walk it in BBB's other sort orders (A-Z, Z-A, Rating, Distance) to reach businesses the first order never shows. Each business is still returned and billed once. Off by default.",
            "default": false
          },
          "scrapeReviews": {
            "title": "Scrape customer reviews",
            "type": "boolean",
            "description": "Also collect each business's customer reviews (rating, author, text, date, business response), paginated up to Max review pages. Adds the reviews & complaints surcharge per business. 💲 Turning this on charges the **detail-enrichment** event (billed as \"Reviews & complaints\") once per business returned — including businesses that turn out to have none — PLUS one more **detail-enrichment** event per started block of 10 reviews delivered on that business (1–10 reviews = 1, 11–20 = 2, 50 = 5). Limit it with Max reviews per business.",
            "default": false
          },
          "scrapeComplaints": {
            "title": "Scrape complaints",
            "type": "boolean",
            "description": "Also collect each business's complaints (type, status, date, text, response count), paginated up to Max complaint pages. Adds the reviews & complaints surcharge per business. 💲 Turning this on charges the **detail-enrichment** event (billed as \"Reviews & complaints\") once per business returned — including businesses that turn out to have none.",
            "default": false
          },
          "maxReviewPages": {
            "title": "Max review pages per business",
            "minimum": 0,
            "type": "integer",
            "description": "Cap on review pages pulled per business (~10 reviews per page). Only used when \"Scrape customer reviews\" is on. 0 = up to 100 pages; values above 100 are treated as 100. The walk still stops earlier at the last review page BBB reports.",
            "default": 5
          },
          "maxComplaintPages": {
            "title": "Max complaint pages per business",
            "minimum": 0,
            "type": "integer",
            "description": "Cap on complaint pages pulled per business (~10 complaints per page). Only used when \"Scrape complaints\" is on. 0 = up to 100 pages; values above 100 are treated as 100. The walk still stops earlier once every complaint BBB counts for the business has been collected.",
            "default": 5
          },
          "maxReviewsPerBusiness": {
            "title": "Max reviews per business",
            "minimum": 0,
            "maximum": 1000,
            "type": "integer",
            "description": "Hard cap on reviews collected per business, on top of \"Max review pages per business\" — whichever limit is hit first stops the walk. Only used when \"Scrape customer reviews\" is on. Default 20 (raise it for more reviews per business). Maximum 1,000; 0 = up to the maximum of 1,000 (page cap still applies). 💲 Reviews are billed in blocks of 10 delivered reviews: each started block of 10 charges one extra **detail-enrichment** event on top of the per-business charge (e.g. 50 reviews = 5 extra events), so this cap also caps the review charge.",
            "default": 20
          },
          "maxComplaintsPerBusiness": {
            "title": "Max complaints per business",
            "minimum": 0,
            "maximum": 1000,
            "type": "integer",
            "description": "Hard cap on complaints collected per business, on top of \"Max complaint pages per business\" — whichever limit is hit first stops the walk. Only used when \"Scrape complaints\" is on. Default 20 (raise it for more complaints per business). Maximum 1,000; 0 = up to the maximum of 1,000 (page cap still applies). Complaints are not billed per complaint.",
            "default": 20
          },
          "proxy": {
            "title": "Proxy configuration",
            "type": "object",
            "description": "Apify Proxy is used by default on a Residential connection, since bbb.org is unreliable on plain datacenter IPs. If Residential is refused for this account (e.g. a free plan), the run automatically attempts a backup connection — some or all pages may still be unavailable; check the run log.",
            "default": {
              "useApifyProxy": true,
              "apifyProxyGroups": [
                "RESIDENTIAL"
              ]
            }
          },
          "resumeFromRunId": {
            "title": "Resume from a previous run",
            "type": "string",
            "description": "Paste a previous run ID or dataset ID to continue a large crawl of businesses without returning or charging for businesses already collected there. Use this after an interrupted run, or when continuing a business list pull in another run. For recurring daily monitoring of the same search, use Incremental mode below instead."
          },
          "incrementalMode": {
            "title": "Incremental changes for scheduled runs",
            "type": "boolean",
            "description": "Turn this on for daily or recurring monitoring. The first run returns all matching businesses as NEW. Later runs normally return only NEW, UPDATED, and REAPPEARED businesses. Turn on \"Emit unchanged\" or \"Emit expired\" only when you also want those businesses returned (and billed). State is kept separately for each search/URL and reviews/complaints setup; use State key when you want to name or deliberately share a monitoring campaign. To continue one specific interrupted run instead, use Resume from a previous run above.",
            "default": false
          },
          "stateKey": {
            "title": "State key (optional, incremental mode only)",
            "type": "string",
            "description": "Optional. Name this monitoring campaign to keep its state stable, or to deliberately share state across differently-configured runs. Leave empty to let the actor derive a key automatically from the search/URL and detail settings — different searches then never mix state with each other."
          },
          "emitUnchanged": {
            "title": "Emit unchanged businesses (incremental mode only)",
            "type": "boolean",
            "description": "Off by default. Turn on to also return businesses that have not changed since the last run, marked UNCHANGED. This returns — and bills — extra rows you already have, so leave it off unless you specifically want the full snapshot every run.",
            "default": false
          },
          "emitExpired": {
            "title": "Emit expired businesses (incremental mode only)",
            "type": "boolean",
            "description": "Off by default. Turn on to also return businesses that were present in a previous run but are no longer found, marked EXPIRED. Only produced once a run has fully scanned the tracked search — not when Max businesses capped it, not when Max result pages capped it, and not when Resume was used. Leave BOTH \"Max businesses\" and \"Max result pages\" empty for the run that should detect expiries. This returns — and bills — extra synthetic rows, so leave it off unless you need expiry tracking.",
            "default": false
          },
          "ignoreFieldsForChanges": {
            "title": "Ignore fields when detecting changes (incremental mode only)",
            "type": "array",
            "description": "Output field names that should NOT make a business count as UPDATED (incremental mode only). Use a plain field name (e.g. \"logoUrl\") to ignore a whole top-level field, or a dotted path (e.g. \"complaints.respondedAt\") to ignore just that nested field inside every item of a list like reviews/complaints/categoryTree. Ignored fields are still returned in the output and never removed from it — this only controls what counts as a change. searchKeyword, searchLocation and localProfileUrl (which of your searches, and which local branch listing, found the business) never count as a change, so a business reached by a different term, location or branch is not reported as UPDATED.",
            "items": {
              "type": "string"
            }
          },
          "mcpConnectors": {
            "title": "Pipe results into your apps (optional)",
            "type": "array",
            "description": "Optionally send results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize one under Apify, Settings, API & Integrations, then select it here. Notion gets a rich page-per-item export; other connectors get a best-effort write/digest. Leave empty to skip; never changes the dataset output."
          },
          "notionParentPageUrl": {
            "title": "Notion parent page (Notion connector only)",
            "type": "string",
            "description": "URL or id of the Notion page under which item pages are created. Required to enable the Notion export; ignored by other connectors."
          },
          "maxNotifyListings": {
            "title": "Max items to export per connector",
            "minimum": 1,
            "maximum": 1000,
            "type": "integer",
            "description": "Cap on items written to each connector per run. Does not affect the dataset.",
            "default": 50
          }
        }
      },
      "runsResponseSchema": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "actId": {
                "type": "string"
              },
              "userId": {
                "type": "string"
              },
              "startedAt": {
                "type": "string",
                "format": "date-time",
                "example": "2025-01-08T00:00:00.000Z"
              },
              "finishedAt": {
                "type": "string",
                "format": "date-time",
                "example": "2025-01-08T00:00:00.000Z"
              },
              "status": {
                "type": "string",
                "example": "READY"
              },
              "meta": {
                "type": "object",
                "properties": {
                  "origin": {
                    "type": "string",
                    "example": "API"
                  },
                  "userAgent": {
                    "type": "string"
                  }
                }
              },
              "stats": {
                "type": "object",
                "properties": {
                  "inputBodyLen": {
                    "type": "integer",
                    "example": 2000
                  },
                  "rebootCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "restartCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "resurrectCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "computeUnits": {
                    "type": "integer",
                    "example": 0
                  }
                }
              },
              "options": {
                "type": "object",
                "properties": {
                  "build": {
                    "type": "string",
                    "example": "latest"
                  },
                  "timeoutSecs": {
                    "type": "integer",
                    "example": 300
                  },
                  "memoryMbytes": {
                    "type": "integer",
                    "example": 1024
                  },
                  "diskMbytes": {
                    "type": "integer",
                    "example": 2048
                  }
                }
              },
              "buildId": {
                "type": "string"
              },
              "defaultKeyValueStoreId": {
                "type": "string"
              },
              "defaultDatasetId": {
                "type": "string"
              },
              "defaultRequestQueueId": {
                "type": "string"
              },
              "buildNumber": {
                "type": "string",
                "example": "1.0.0"
              },
              "containerUrl": {
                "type": "string"
              },
              "usage": {
                "type": "object",
                "properties": {
                  "ACTOR_COMPUTE_UNITS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_WRITES": {
                    "type": "integer",
                    "example": 1
                  },
                  "KEY_VALUE_STORE_LISTS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_INTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_EXTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_SERPS": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_UNBLOCKER_UNITS": {
                    "type": "integer",
                    "example": 0
                  }
                }
              },
              "usageTotalUsd": {
                "type": "number",
                "example": 0.00005
              },
              "usageUsd": {
                "type": "object",
                "properties": {
                  "ACTOR_COMPUTE_UNITS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_WRITES": {
                    "type": "number",
                    "example": 0.00005
                  },
                  "KEY_VALUE_STORE_LISTS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_INTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_EXTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_SERPS": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_UNBLOCKER_UNITS": {
                    "type": "integer",
                    "example": 0
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}