SF Registered Business Registry Lookup
Purpose
Look up businesses in San Francisco's official tax registry by DBA name or owner/legal name and return each matching location's registry record: legal/owner name, DBA, full address, NAICS code + description, status (active / closed / administratively closed), and registration (business start) date. Backed by the City & County of San Francisco "Registered Business Locations - San Francisco" open dataset (g8m3-pdis, ~362,000 rows, refreshed daily). Read-only — this only queries public open data; it never writes or registers anything.
When to Use
- Verify whether a business is registered with the SF Treasurer & Tax Collector and whether it's currently active.
- Resolve a DBA / trade name to its registered legal owner (and vice-versa).
- Pull a business's NAICS classification, registered address(es), and registration date for due-diligence, enrichment, or compliance checks.
- Enumerate all locations of a multi-location business (each location is a separate row sharing one Business Account Number).
- Anywhere you'd otherwise scrape the DataSF data-grid UI — the SODA API is faster, cheaper, and structurally reliable.
Workflow
data.sfgov.org is a Socrata open-data portal. The data-grid web UI is a thin, JS-heavy client over a public SODA (Socrata Open Data API) endpoint. Query the API directly — it needs no auth, no API token/app-token, no cookies, no residential proxy, and no anti-bot stealth (a bare GET returns 200). The browser grid (documented as a fallback at the end) loads 46+ XHRs and is meaningfully slower for no benefit.
Base endpoint: https://data.sfgov.org/resource/g8m3-pdis.json
-
Pick a search mode and build a SoQL query:
- DBA name (substring, case-insensitive) — the most common case:
GET /resource/g8m3-pdis.json ?$where=upper(dba_name) like '%PHILZ COFFEE%' &$limit=200 - Owner / legal name (substring, case-insensitive):
?$where=upper(ownership_name) like '%STARBUCKS%'&$limit=200 - Free-text across all columns (use when you don't know if the term is a DBA or owner):
?$q=Philz Coffee&$limit=200. This is what the grid's search box does. - Exact DBA match (fast equality, case-sensitive):
?dba_name=Philz Coffeeas a simple query param.
URL-encode the whole
$wherevalue. SoQL string literals use single quotes; escape an embedded apostrophe by doubling it (O''Reilly). - DBA name (substring, case-insensitive) — the most common case:
-
Trim the payload with
$select(optional but recommended) to just the fields you need:&$select=certificate_number,ownership_name,dba_name,full_business_address,city,state,business_zip,naic_code,naic_code_description,dba_start_date,dba_end_date,location_end_date,administratively_closedA
$selectreferencing a column that doesn't exist returns HTTP 400 (a JSON error object, not an array) — use the exactfieldNames in Expected Output below. -
Filter to active records (optional). A row is active when it has no end date and is not administratively closed:
&$where=... AND dba_end_date IS NULL AND location_end_date IS NULL AND administratively_closed IS NULL -
Parse the JSON array. Each element is one business location. Map fields:
ownership_name→ registered owner / legal namedba_name→ DBA / trade namefull_business_address+city+state+business_zip→ addressnaic_code+naic_code_description→ NAICSdba_start_date→ registration date (ISOYYYY-MM-DDT00:00:00.000; take the date part)- status:
administratively_closedpresent →administratively_closed; elsedba_end_dateorlocation_end_datepresent →closed; elseactive
-
Handle multiplicity. A single business commonly returns many rows — one per location and per registration period — all sharing the same
certificate_number(Business Account Number). De-duplicate oncertificate_numberif you want one row per business, or keep all rows to enumerate locations. -
Paginate if
$limitis hit: append&$offset=N(default page size is 1000). For exact counts, query?$select=count(*)&$where=....
Browser fallback
Only if the API is unreachable. Open https://data.sfgov.org/Economy-and-Community/Registered-Business-Locations-San-Francisco/g8m3-pdis/about_data, click the "Data" tab (the /data URL itself redirects to the About tab), wait for the grid to render, type the query into the grid's Search box (top-right) and press Enter. The grid does full-text matching (equivalent to $q) and shows a result count ("1 to 27 of 27"). Read values from the row cells. Note: the grid is heavy JS and accessibility snapshots can be flaky mid-render — wait for the row count to settle before extracting. Under the hood the grid calls the internal https://data.sfgov.org/api/v3/views/g8m3-pdis/query.json SoQL endpoint; the public /resource/g8m3-pdis.json path above is the stable equivalent.
Site-Specific Gotchas
- No proxy / no stealth needed. The SODA API returned
200from a bare (no-proxy) request. Don't waste a residential proxy or--verifiedsession on it. An app token is optional and only raises rate limits. ownership_nameis the owner and the legal name. This dataset has no separate "legal entity name" column distinct from the owner —ownership_name(e.g. "Philz Coffee Inc", "Greg Matt Inc") serves as both.dba_nameis the trade name (e.g. "Philz Coffee", "Philz Coffee Truck").- Multiple rows per business is normal. Each location is its own row; one business (one
certificate_number) can have dozens of rows. "Philz Coffee" returns 27 rows across owners "Philz Coffee Inc" and "Greg Matt Inc" (a franchise/truck operator). De-dupe oncertificate_numberif you need one record per business. - Status is derived from three columns, not one.
administratively_closedholds the literal string"***Administratively Closed"(note the***prefix) for ~42,775 rows, or is absent/NULL for the ~319,170 others. Per the dataset docs, "Administratively Closed" means the business hasn't filed/communicated with the Tax Collector for 3 years (or was flagged closed by another City dept). Separately,dba_end_datemarks the business as ended andlocation_end_datemarks a specific location closed (the business may still be active elsewhere). There is no single boolean "active" field — compute it. - Don't filter
administratively_closed != 'Yes'. The value is never "Yes"; it's"***Administratively Closed"or NULL. Filter withadministratively_closed IS NULLfor active-only. $selectof a non-existent column 400s. A generated query that selectsstreet_address(wrong) instead offull_business_address(correct) returns an HTTP 400 JSON error object, which then failsArray.isArray(). Use the exact field names. Other easy-to-get-wrong names: registration date isdba_start_date(notbusiness_start_date); ZIP isbusiness_zip(notsource_zipcode/zip_code).naic_code_descriptioncan be empty for some rows even whennaic_codeis present. Don't assume both are populated.- Exact-match
dba_name=query param is case-sensitive; SoQL$where ... likeis not (when wrapped inupper()). Prefer theupper(...) like '%...%'form for user-supplied queries. $qfull-text matches across all columns, so it can return owner-name hits when you intended a DBA search (and vice-versa). For precise DBA-only or owner-only results, use a column-scoped$where.- Dates are floating timestamps returned as
YYYY-MM-DDT00:00:00.000(no timezone). Split onTfor the calendar date. /dataURL redirects to the About tab. In the browser fallback you must click the "Data" tab to reach the grid; landing on/datashows the dataset description, not rows.- Dataset scope is broader than "San Francisco". Despite the title,
full_business_address/citycan be outside SF (e.g. "Palo Alto", "Seattle") — these are SF-registered taxpayers whose physical/mailing location is elsewhere. Filter oncityif you need SF-only locations.
Expected Output
A list of matched business locations. Example for a DBA search dba_name like '%PHILZ COFFEE%':
{
"query": { "mode": "dba_name", "term": "Philz Coffee" },
"total_matches": 27,
"results": [
{
"certificate_number": "0415140",
"legal_name": "Philz Coffee Inc",
"dba_name": "Philz Coffee",
"address": "549 Castro St, San Francisco, CA 94114",
"naics_code": "722515",
"naics_description": "Limited-service restaurants",
"status": "active",
"registration_date": "2007-04-01"
},
{
"certificate_number": "0484044",
"legal_name": "Greg Matt Inc",
"dba_name": "Philz Coffee Truck",
"address": "500 Marina Blvd, San Francisco, CA 94123",
"naics_code": "722330",
"naics_description": "Mobile food services",
"status": "active",
"registration_date": "2013-10-18"
}
]
}
A closed / administratively-closed location:
{
"certificate_number": "0123456",
"legal_name": "Example Holdings LLC",
"dba_name": "Old Corner Store",
"address": "100 Market St, San Francisco, CA 94105",
"naics_code": "445110",
"naics_description": "Supermarkets and other grocery (except convenience) stores",
"status": "administratively_closed",
"registration_date": "2009-06-15",
"dba_end_date": null,
"location_end_date": "2018-03-07"
}
No matches:
{
"query": { "mode": "dba_name", "term": "Nonexistent Biz Xyz" },
"total_matches": 0,
"results": []
}