bizfileonline.sos.ca.gov

find-california-business

Installation

Adds this website's skill for your agents

browse skills add bizfileonline.sos.ca.gov/find-california-business-izbvm2
Summary

Search California's bizfileonline.sos.ca.gov portal for a registered business entity by name or entity number, returning status, entity type, formation date, jurisdiction, registered agent, and principal address.

SKILL.md
189 lines

California Secretary of State — Business Entity Search

Purpose

Search the California Secretary of State's bizfileonline.sos.ca.gov business filings portal for an entity by name (corporation, LLC, LP, foreign-qualified, etc.), and return the matching record(s) with entity number, exact registered name, status (ACTIVE / FTB SUSPENDED / CANCELED / DISSOLVED / SOS/FTB SUSPENDED / FORFEITED / MERGED OUT / SURRENDERED), entity type, formation/registration date, jurisdiction, registered agent for service of process, and principal/mailing address. Read-only — never click File / Order Documents / Pay buttons.

When to Use

  • Due-diligence / KYB lookups on California-registered entities.
  • Verifying a business's "good standing" (active vs. suspended/forfeited).
  • Resolving an entity number from a name, or vice-versa, before a downstream filing.
  • Pulling registered-agent info for service-of-process workflows.
  • Anywhere an agent would otherwise scrape the older businesssearch.sos.ca.gov site — bizfileonline is the current canonical search surface (the legacy site is read-only mirror data and stale on recent filings).

Workflow

The portal at https://bizfileonline.sos.ca.gov/search/business is a React SPA (single <div id="root">, all UI rendered client-side) served behind Imperva / Incapsula (CDN + JS-challenge anti-bot). The frontend talks to an ASP.NET Web API at /api/Records/businessSearch (POST, JSON). Drive the public page in a Verified + residential-proxy Browserbase session; the same call can also be made directly to the discovered API once the session has obtained Imperva cookies (see "Alternative: direct API call" at the end of this section).

1. Verified + residential-proxy session

SID=$(browse cloud sessions create --keep-alive --verified --proxies | jq -r .id)
export BROWSE_SESSION="$SID"

Both flags are mandatory. The page issues an Incapsula JS challenge (/_Incapsula_Resource?SWJIYLWA=... is loaded in the document body) and a bare session gets the Imperva interstitial instead of the React app. --verified (Verified browsers) clears the challenge; --proxies keeps the source IP US-residential. The portal also serves an opaque obfuscated script /ixt-the-Quarthy-Snard-a-Sept-tis-to-their-cert-a (filename varies per build) as a second-layer challenge — same flags handle it.

2. Open the search page and wait for hydration

browse open "https://bizfileonline.sos.ca.gov/search/business" --remote
browse wait load --remote
browse wait timeout 3000 --remote      # SPA hydrates ~1.5–3s after `load`
browse snapshot --remote

The snapshot should show: a "Search" combobox + the text "Search for Existing Business Records" header. If the snapshot returns only a "Request unsuccessful" / "Incapsula incident ID" block, the Verified challenge failed — release the session and retry from step 1.

3. Enter the search term and submit

The search box accepts: business name (partial-match), exact name, or 7-character entity number (e.g. C0806592 for a corp, 201312310123 for an LLC).

browse click @<search-combobox-ref> --remote
browse type "APPLE INC" --remote
browse press Enter --remote
browse wait timeout 2500 --remote      # results table renders progressively
browse snapshot --remote

Don't use browse fill for the search box — the React combobox's onChange handler debounces and fill can race the autosuggest dropdown. clicktypepress Enter is reliable.

4. Parse the results table

The results render as a virtualized table with columns: Entity Name · Entity Number · Initial Filing Date · Status · Entity Type · Jurisdiction · Agent for Service of Process.

Each row is a button/link ref — clicking it opens the entity detail drawer. Extract row text from the snapshot. For ambiguous queries (multiple APPLE INC rows from different states / FTB suspensions / merged-out entities), filter to the row with Status: ACTIVE and the earliest Initial Filing Date to identify the canonical current registration.

5. Drill into the entity detail drawer

browse click @<row-ref-for-target-entity> --remote
browse wait timeout 1500 --remote
browse snapshot --remote

The detail drawer shows: entity number, registered name, status, formation/qualification date, entity type, jurisdiction, principal address, mailing address, agent for service of process (name + street address — agent's address is not PO-box-allowed by CA statute, so it's always a physical address), and a filing-history table. Extract these into the output JSON.

6. Release the session

browse cloud sessions update "$SID" --status REQUEST_RELEASE

Alternative: direct API call

The portal frontend calls POST https://bizfileonline.sos.ca.gov/api/Records/businessSearch with a JSON body. Endpoint discovery confirmed via probe (Imperva-proxied GET returned 405 Method Not Allowed, Allow: POST); all other plausible controller paths (/api/Business, /api/BusinessSearch, /api/Filings, /api/Entities, /api/Records/EntityList, /api/Records/getentity, etc.) returned 404 No type was found that matches the controller named X — i.e. the .NET routing table has exactly one Records controller with one POST-only action businessSearch. The body schema observed in production traffic (verify in your run):

POST /api/Records/businessSearch HTTP/1.1
Host: bizfileonline.sos.ca.gov
Content-Type: application/json
Origin: https://bizfileonline.sos.ca.gov
Referer: https://bizfileonline.sos.ca.gov/search/business
Cookie: visid_incap_2299457=...; incap_ses_*=...; nlbi_2299457=...

{"SearchValue":"APPLE INC","SearchType":"All","SearchFilter":"keyword"}

Imperva cookies are mandatory — direct POST without visid_incap_2299457 / incap_ses_* / nlbi_2299457 cookies trips a JS challenge and the call 403s. The cookies are set when the SPA homepage loads in a Verified browser session. The practical pattern: load /search/business in the session, harvest cookies via browse eval "document.cookie", then either continue driving the UI (preferred) or replay the API call from a fetch() inside the page (browse eval) — same-origin avoids CORS preflight. Calling the endpoint from outside the session (curl + cookie jar) works in principle but the incap_ses_* cookie is IP-bound, so the call must originate from the same Browserbase proxy IP that loaded the page. Easier to just do the fetch() inside the session.

Access-Control-Allow-Origin: * is set on the response — so the API itself doesn't gate by origin; the gating is purely the Imperva edge.

Site-Specific Gotchas

  • READ-ONLY. Never click File Document, Order Documents, Pay, Save to Cart, or any drawer button labeled with a verb. Browsing entity records is free; filing actions cost money and require login.
  • Imperva / Incapsula is mandatory-Verified. X-Cdn: Imperva header and the /_Incapsula_Resource?SWJIYLWA=<token> script in the document body confirm Incapsula edge. A bare Browserbase session (no --verified) returns the Incapsula interstitial HTML instead of the React app — the snapshot will show "Request unsuccessful. Incapsula incident ID: <id>". Always run with --verified --proxies.
  • Static assets 301 to the Azure origin (which 403s). /static/js/main.<hash>.js returns a 301 to https://app-be-web-prod.azurewebsites.net/wwwroot/... and the origin then 403s direct access. This is normal — Imperva inlines the JS for real browsers via cookied requests. Out-of-band asset fetches will fail; that's not a bug, don't waste a turn debugging it.
  • Backend identity leakage. Response headers leak Server: Microsoft-IIS/10.0, X-Aspnet-Version: 4.0.30319, X-Powered-By: ASP.NET, and Request-Context: appId=cid-v1:88d59fd1-... (the Application Insights app ID for the Azure resource). The backend hostname app-be-web-prod.azurewebsites.net appears in 405 / 404 error bodies. This is just orientation — not a security issue to fix, just useful for identifying the surface.
  • Allow: POST on the search endpoint. GET to /api/Records/businessSearch returns 405 Method Not Allowed with Allow: POST — confirmation that the route exists and accepts only POST. Don't try GET-with-querystring; it 405s every time.
  • Only one API controller / action confirmed. Probing 20+ candidate paths (/api/Records/getentity, /api/Records/EntityTypes, /api/Records/Filings, /api/Filing/business, /api/Records/FilingDetail, /api/BusinessSearch, etc.) all returned 404 No type was found that matches the controller named X or No action was found on the controller 'Records'. Entity-detail data appears to come from the same businessSearch POST with a different SearchType / SearchFilter, or is included in the search response payload — verify by inspecting the search response shape in your run. Don't waste iterations probing for getEntity / getRecord — they don't exist.
  • Access-Control-Allow-Origin: * — the backend doesn't gate by Origin/Referer. All gating is at the Imperva edge via JS challenge + IP-bound incap_ses_* cookies.
  • incap_ses_* cookie is IP-bound. Replaying the cookie from a different IP than the one that earned it returns 403. If calling the API from outside the browser session, the curl/fetch must originate from the same Browserbase proxy IP.
  • Entity number formats vary by type. Corporations: C + 7 digits (e.g. C0806592). LLCs / LPs: 12-digit number (e.g. 201312310123 — first 6 are YYYYMM of filing). Foreign entities sometimes have alpha prefixes (B, F). The search box accepts all formats interchangeably with name-based search.
  • Status values are an enum. Observed: ACTIVE, FTB SUSPENDED, SOS SUSPENDED, SOS/FTB SUSPENDED, FORFEITED, DISSOLVED, CANCELED, MERGED OUT, SURRENDERED, TERMINATED. "FTB" = Franchise Tax Board (delinquent tax filings). A SUSPENDED status entity still has a record but cannot legally transact.
  • Initial filing date vs. registration date for foreign entities. For a foreign-qualified entity (formed elsewhere, registered to do business in CA), the "Initial Filing Date" column is the CA qualification date, not the entity's home-jurisdiction formation date. The home jurisdiction is shown in the Jurisdiction column.
  • Agent for service of process is sometimes "1505 corporate agent". California allows a registered corporate agent (CSC, CT Corporation, Cogency Global, etc.) instead of a named individual. When the agent column reads e.g. "C T CORPORATION SYSTEM", the address field will be the agent's CA street address, not the entity's actual office.
  • Search is prefix/contains, case-insensitive. Searching APPLE returns dozens of entities (Apple Inc., Apple Hospitality REIT, Big Apple Bagels, etc.). For a known target, search by full registered name or entity number for a deterministic single result.
  • No pagination cursor in URL. The results table is virtualized; scrolling triggers more rows from the same POST response (the API returns the full list in one shot for most queries — confirmed by the lack of a paged endpoint). For high-cardinality queries (SMITH), expect 1,000+ results.
  • Legacy businesssearch.sos.ca.gov is a different system. It mirrors older data and lags on recent filings by days-to-weeks. Always use bizfileonline.sos.ca.gov for current data; the legacy site is not a fallback.

Expected Output

{
  "success": true,
  "query": "APPLE INC",
  "results_count": 1,
  "entities": [
    {
      "entity_number": "C0806592",
      "name": "APPLE INC.",
      "status": "ACTIVE",
      "entity_type": "STOCK CORPORATION - OUT OF STATE - STOCK",
      "initial_filing_date": "1977-01-03",
      "jurisdiction": "CALIFORNIA",
      "agent_for_service": {
        "name": "C T CORPORATION SYSTEM",
        "address": "330 N BRAND BLVD STE 700, GLENDALE, CA 91203"
      },
      "principal_address": "ONE APPLE PARK WAY, CUPERTINO, CA 95014",
      "mailing_address": "ONE APPLE PARK WAY, CUPERTINO, CA 95014"
    }
  ],
  "error_reasoning": null
}

Outcome shapes:

// Single canonical match
{ "success": true, "results_count": 1, "entities": [ { ... } ] }

// Multiple matches (ambiguous query, suspended duplicates, foreign-qualifications, etc.)
{ "success": true, "results_count": 12, "entities": [ { "status": "ACTIVE", ... }, { "status": "FTB SUSPENDED", ... }, ... ] }

// No results
{ "success": false, "results_count": 0, "entities": [], "error_reasoning": "no_results" }

// Anti-bot wall (Incapsula challenge failed)
{ "success": false, "entities": [], "error_reasoning": "anti_bot_wall: Incapsula incident ID <id>" }

// Entity number malformed / out of range
{ "success": false, "results_count": 0, "entities": [], "error_reasoning": "invalid_entity_number_format" }