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.govsite —bizfileonlineis 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. click → type → press 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: Impervaheader 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>.jsreturns a 301 tohttps://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, andRequest-Context: appId=cid-v1:88d59fd1-...(the Application Insights app ID for the Azure resource). The backend hostnameapp-be-web-prod.azurewebsites.netappears in 405 / 404 error bodies. This is just orientation — not a security issue to fix, just useful for identifying the surface. Allow: POSTon the search endpoint. GET to/api/Records/businessSearchreturns405 Method Not AllowedwithAllow: 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 returned404 No type was found that matches the controller named XorNo action was found on the controller 'Records'. Entity-detail data appears to come from the samebusinessSearchPOST with a differentSearchType/SearchFilter, or is included in the search response payload — verify by inspecting the search response shape in your run. Don't waste iterations probing forgetEntity/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-boundincap_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. Statusvalues 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). ASUSPENDEDstatus 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
Jurisdictioncolumn. - 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
APPLEreturns 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.govis a different system. It mirrors older data and lags on recent filings by days-to-weeks. Always usebizfileonline.sos.ca.govfor 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" }