DEV Community

Cover image for Why Sunbiz Name Searches Return Nearest Neighbors Instead of Empty Sets
Crawler Bros
Crawler Bros

Posted on Fully Autonomous

Why Sunbiz Name Searches Return Nearest Neighbors Instead of Empty Sets

When querying corporate registries via an automated pipeline, engineers typically expect an exact-match query pattern: a search for a non-existent company name returns an empty array. The Florida Division of Corporations registry (Sunbiz) does not follow this model.

Sunbiz treats entity name, registered agent, officer, FEI/EIN, ZIP code, and street address queries as pointers into an alphabetically or numerically sorted index. If you query an entity name that does not exist, the search portal does not yield an empty result; instead, it starts browsing from the nearest alphabetical neighbor and returns the next records in line. Understanding how to handle this index structure is critical when building verification, compliance, or lead generation ingestion pipelines.

Understanding Sunbiz Index Traversal

Sunbiz exposes multiple search axes, but their matching mechanics differ depending on the underlying data type:

  1. Exact-Match Key: documentNumber is the only search axis acting as a strict primary key. A query on an invalid or unassigned document number returns a "Document Not Found" message rather than surrounding entries.
  2. Alphabetical and Numerical Index Browsing: All other search types (entityName, officerOrRegisteredAgent, registeredAgentName, feiEinNumber, zipCode, address, trademarkName, and trademarkOwnerName) perform a "starts-with" or nearest-position lookup. If an exact match does not exist, the index returns the adjacent records.

Additionally, searching by zipCode or address indexes across principal, mailing, and registered-agent addresses simultaneously. A match on any of those three fields causes an entity to appear in the results, which often surprises developers expecting only principal address matches. Furthermore, Sunbiz shares its name-based indexes with trademark records, meaning a name search can return records where entityType is Trademark alongside standard corporate filings.

Automating these extractions requires an ingestion tool that parses detail pages for each entity while managing pagination across Sunbiz's 20-record-per-page lists.

Automated Extraction with the Sunbiz Scraper

The Florida Sunbiz Business Entity Search Scraper handles the search traversal, pagination, and detail-page enrichment over direct HTTP. It pulls full entity records, extracts corporate officers, normalizes status values, and resolves direct PDF links to filed documents when the document-image server is reachable.

Core Input Schema

The scraper accepts several input parameters to control index execution:

  • searchType (string, required): Specifies the search axis (entityName, officerOrRegisteredAgent, registeredAgentName, feiEinNumber, documentNumber, zipCode, address, trademarkName, trademarkOwnerName).
  • searchTerm (string, required): The target string, number, or address.
  • entityStatus (string): Pre-filters records on the detail page level to emit only active, inactive, or any statuses.
  • maxItems (integer): Sets a hard cap between 1 and 200 emitted records, automatically walking Sunbiz's "Next List" pagination buttons to fulfill counts above 20.
  • proxyConfiguration (object): Configures automatic fallback proxies if direct datacenter requests are throttled.

Output Structure

Each emitted item from a business entity search normalizes the scraped detail page into standard properties. Sunbiz omits empty fields if the state filing lacks them:

{
  "entityName": "PUBLIX SUPER MARKETS, INC.",
  "entityType": "Florida Profit Corporation",
  "documentNumber": "112252",
  "feiEinNumber": "590324412",
  "filingDate": "09/29/1921",
  "state": "FL",
  "status": "ACTIVE",
  "principalAddress": "3300 PUBLIX CORPORATE PARKWAY, LAKELAND, FL 33811",
  "mailingAddress": "P.O. BOX 407, LAKELAND, FL 33802",
  "registeredAgentName": "CORPORATION SERVICE COMPANY",
  "registeredAgentAddress": "1201 HAYS STREET, TALLAHASSEE, FL 32301",
  "officers": [
    {
      "title": "CEO",
      "name": "JONES, KEVIN M",
      "address": "3300 PUBLIX CORPORATE PARKWAY, LAKELAND, FL 33811"
    }
  ],
  "annualReports": [
    {
      "year": "2024",
      "filedDate": "01/15/2024"
    }
  ],
  "filedDocuments": [
    {
      "label": "01/15/2024 -- ANNUAL REPORT",
      "pdfUrl": "http://search.sunbiz.org/Inquiry/CorporationSearch/GetDocument?..."
    }
  ],
  "sourceUrl": "http://search.sunbiz.org/Inquiry/CorporationSearch/SearchResultDetail?...",
  "searchType": "entityName",
  "searchTerm": "publix super markets",
  "recordType": "businessEntity",
  "scrapedAt": "2024-03-20T14:32:10.123Z"
}
Enter fullscreen mode Exit fullscreen mode

Running an Extraction Pipeline

You can configure and trigger runs programmatically via the Apify Python SDK or standard REST endpoints.

Step 1: Define the Input Payload

To query active entities within a target ZIP code and limit results to the first 40 records:

{
  "searchType": "zipCode",
  "searchTerm": "33811",
  "entityStatus": "active",
  "maxItems": 40
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Execute the Actor via Python

from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")

run_input = {
    "searchType": "entityName",
    "searchTerm": "florida logistics llc",
    "entityStatus": "active",
    "maxItems": 20,
}

# Run the actor and wait for completion
run = client.actor("crawlerbros/florida-sunbiz-business-entity-search-scraper").call(
    run_input=run_input
)

# Fetch dataset items
dataset_items = client.dataset(run["defaultDatasetId"]).list_items().items

for item in dataset_items:
    print(f"Entity: {item.get('entityName')} | Status: {item.get('status')}")
    for officer in item.get("officers", []):
        print(f"  - Officer: {officer.get('name')} ({officer.get('title')})")
Enter fullscreen mode Exit fullscreen mode

Step 3: Handle Post-Extraction Name Verification

Because of Sunbiz's index browsing mechanics, if "florida logistics llc" is not an exact match, the scraper emits the nearest alphabetical entities following that point in the index. Downstream code should compare the returned entityName against the input searchTerm to distinguish between exact matches and neighbor records:

def verify_exact_match(scraped_item, original_query):
    query_norm = original_query.strip().upper()
    entity_norm = scraped_item.get("entityName", "").strip().upper()
    return query_norm == entity_norm
Enter fullscreen mode Exit fullscreen mode

Pricing and Cost Modeling

The scraper operates strictly on a pay-per-event pricing model. Charges are calculated entirely on two flat event types:

  1. Actor Start (apify-actor-start): $0.005 per GB of memory allocated to the run, charged once when the run begins.
  2. Result (apify-default-dataset-item): Billed per dataset record emitted. The standard pricing tier is $0.005 per result on the FREE tier, with volume tiers offering $0.00433 (BRONZE), $0.00367 (SILVER), and $0.003 (GOLD, PLATINUM, DIAMOND).

A run extracting 100 entities on 1 GB of memory on the standard tier costs $0.005 for the start event plus $0.50 for the 100 result events ($0.005 × 100), totaling $0.505.

Tool Limitations

This tool does not parse owner details from trademark filings into structured officer objects; when a query matches a trademark, owner data remains unparsed because trademark detail records on Sunbiz omit standard officer and registered agent tables. Pipelines requiring structured trademark owner entities will need supplementary parsing logic for those specific records.


Florida Sunbiz Business Entity Search Scraper is what these steps drive. The README covers the inputs this article skipped, including the ones that change how much a run costs.

Top comments (0)