Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 26 additions & 47 deletions typescript/business-lookup/README.md
Original file line number Diff line number Diff line change
@@ -1,62 +1,41 @@
# Stagehand + Browserbase: Business Lookup with Agent
# Stagehand + Browserbase: Business Lookup

## AT A GLANCE
Look up a business in San Francisco's Registered Business Lookup and read its published registration details using Stagehand V3 browser locators.

- Goal: Automate business registry searches using an autonomous AI agent with computer-use capabilities.
- Uses Stagehand Agent in CUA mode to navigate complex UI elements, apply filters, and extract structured business data.
- Demonstrates extraction with Zod schema validation for consistent data retrieval.
- Docs → https://docs.stagehand.dev/basics/agent
## How it works

## GLOSSARY
1. Open the registry in a Browserbase session.
2. Find the business in the DBA Name filter, select its checkbox, and apply the filter.
3. Wait for the filtered row and require exactly one matching location.
4. Read the labeled grid cells and require the requested DBA name, a business account number, and a location ID.
5. Scroll the registry's virtualized columns and read the dates, neighborhood, license description, and self-reported NAICS code directly from their cells.
6. Print the record as JSON and close the session.

- agent: create an autonomous AI agent that can execute complex multi-step tasks
Docs → https://docs.stagehand.dev/basics/agent#what-is-agent
- extract: extract structured data from web pages using natural language instructions
Docs → https://docs.stagehand.dev/basics/extract
Filtering and record retrieval use the registry's DOM controls and labeled grid cells. The template makes no model calls; `BROWSERBASE_API_KEY` is the only credential required.

## QUICKSTART
## Running the template

1. npm install
2. cp .env.example .env
3. Add required API keys/IDs to .env
4. npm start
Use a TypeScript environment with Stagehand V3 (`@browserbasehq/stagehand@3.6.0`), `dotenv`, and a TypeScript runner such as `tsx`. Provide `BROWSERBASE_API_KEY` through your environment or secret manager, then run `index.ts` with your TypeScript runner.

## EXPECTED OUTPUT
Set `businessName` near the top of `index.ts` to the business's DBA name. The default is `Jalebi Street`.

- Initializes Stagehand session with Browserbase
- Displays live session link for monitoring
- Navigates to SF Business Registry search page
- Agent searches for business using DBA Name filter
- Agent completes search and opens business details
- Extracts structured business information (DBA Name, Account Number, NAICS Code, etc.)
- Outputs extracted data as JSON
- Closes session cleanly
In Playground, choose **Search business registries for KYC** and run the template. Playground supplies the session and Browserbase credentials.

## COMMON PITFALLS
## Expected output

- Dependency install errors: ensure npm install completed
- Missing credentials: verify .env contains BROWSERBASE_API_KEY and GOOGLE_API_KEY
- Google API access: ensure you have access to Google's gemini-2.5-computer-use-preview-10-2025 model
- Agent failures: check that the business name exists in the registry and that maxSteps is sufficient for complex searches
- Find more information on your Browserbase dashboard -> https://www.browserbase.com/sign-in
The JSON record includes the DBA and ownership names, business account number, location ID, street address, business start/end dates, neighborhood, license code description, and self-reported NAICS code.

## USE CASES
Blank registry cells are returned as `null`. For example, a business can have a license description but no self-reported NAICS code. The template reports those fields separately and does not infer an industry code from the license description.

• Business verification: Automate registration status checks, license validation, and compliance verification for multiple businesses.
• Data enrichment: Collect structured business metadata (NAICS codes, addresses, ownership) for research or CRM updates.
• Due diligence: Streamline background checks by autonomously searching and extracting business registration details from public registries.
## Troubleshooting

## NEXT STEPS
- If the business is not available in the DBA filter or the results do not update, the template times out with a description of the missing control or result.
- If several locations share the DBA name, narrow the registry filters before extracting one record. This template deliberately rejects multiple matches.
- If the row does not match the requested DBA name or lacks an account number or location ID, the template reports an error. A missing column also causes an error; only rendered blank cells become `null`.
- Browser and lookup failures include their error message. Check that your Browserbase credentials are valid and the registry is available.

• Parameterize search: Accept business names as command-line arguments or from a CSV file for batch processing.
• Expand extraction: Add support for additional fields like tax status, licenses, or historical registration changes.
• Multi-registry support: Extend agent to search across multiple city or state business registries with routing logic.
## Resources

## HELPFUL RESOURCES

📚 Stagehand Docs: https://docs.stagehand.dev/v3/first-steps/introduction
🎮 Browserbase: https://www.browserbase.com
💡 Try it out: https://www.browserbase.com/playground
🔧 Templates: https://www.browserbase.com/templates
📧 Need help? support@browserbase.com
💬 Discord: http://stagehand.dev/discord
- [Stagehand V3 documentation](https://docs.stagehand.dev/v3/first-steps/introduction)
- [San Francisco Registered Business Lookup](https://data.sf.gov/stories/s/Registered-Business-Lookup/k6sk-2y6w/)
- [Browserbase Playground](https://www.browserbase.com/playground)
151 changes: 89 additions & 62 deletions typescript/business-lookup/index.ts
Original file line number Diff line number Diff line change
@@ -1,90 +1,117 @@
// Business Lookup with Agent - See README.md for full documentation
// Business Lookup with Stagehand - See README.md for full documentation

import "dotenv/config";
import { Stagehand } from "@browserbasehq/stagehand";
import { z } from "zod";

// Business search variables
const businessName = "Jalebi Street";

async function main() {
// Initialize Stagehand with Browserbase for cloud-based browser automation.
const stagehand = new Stagehand({
env: "BROWSERBASE",
verbose: 1,
model: "openai/gpt-4.1",
});

try {
// Initialize browser session to start automation.
await stagehand.init();
console.log("Stagehand initialized successfully!");
console.log(
`Live View Link: https://browserbase.com/sessions/${stagehand.browserbaseSessionId}`,
);

const page = stagehand.context.pages()[0];
await page.goto("https://data.sf.gov/stories/s/Registered-Business-Lookup/k6sk-2y6w/");

// Navigate to SF Business Registry search page.
console.log(`Navigating to SF Business Registry...`);
await page.goto("https://data.sfgov.org/stories/s/Registered-Business-Lookup/k6sk-2y6w/");

// Create agent with computer use capabilities for autonomous business search.
const agent = stagehand.agent({
cua: true, // Enable Computer Use Agent mode
model: {
modelName: "google/gemini-2.5-computer-use-preview-10-2025",
apiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY,
},
systemPrompt:
"You are a helpful assistant that can use a web browser to search for business information.",
});
async function waitFor(
check: () => Promise<boolean | number>,
description: string,
): Promise<void> {
const deadline = Date.now() + 20000;
while (Date.now() < deadline) {
if (await check()) return;
await new Promise((resolve) => setTimeout(resolve, 250));
}
throw new Error(`Timed out waiting for ${description}`);
}

console.log(`Searching for business: ${businessName}`);
const result = await agent.execute({
instruction: `Find and look up the business "${businessName}" in the SF Business Registry. Use the DBA Name filter to search for "${businessName}", apply the filter, and click on the business row to view detailed information. Scroll towards the right to see the NAICS code.`,
maxSteps: 30,
});
// Click the checkbox itself: clicking its adjacent text does not select it.
const filter = '[role="button"][aria-label="Filter: DBA Name - Select..."]';
await waitFor(() => page.locator(filter).count(), "the DBA Name filter");
await page.locator(filter).click();
await waitFor(
() => page.locator("#search-text-filter-input").count(),
"the business-name search input",
);
await page.locator("#search-text-filter-input").fill(businessName);
const checkbox = `forge-checkbox[aria-label=${JSON.stringify(businessName)}]`;
await waitFor(() => page.locator(checkbox).count(), `the ${businessName} option`);
await page.locator(checkbox).click();
await waitFor(() => page.locator(checkbox).isChecked(), "the selected business checkbox");
await page.locator('[data-testid="apply-filter-button"]').click();

if (!result.success) {
throw new Error("Agent failed to complete the search");
// Wait for the results grid to replace the unfiltered first page.
await waitFor(
() =>
page.evaluate((name: string) => {
const cells = [...document.querySelectorAll('[role="gridcell"][col-id="dba_name"]')];
return cells.length > 0 && cells.every((cell) => cell.textContent!.trim() === name);
}, businessName),
`results matching ${businessName}`,
);
if ((await page.locator('[role="gridcell"][col-id="dba_name"]').count()) !== 1) {
throw new Error(
`Multiple locations found for ${businessName}; narrow the registry filters before extracting one record.`,
);
}
// Read the labeled grid cells directly; missing cells must fail rather
// than being confused with an empty value published by the registry.
async function readCell(column: string): Promise<string | null> {
const text = await page.locator(`[role="gridcell"][col-id="${column}"]`).innerText();
return text.trim() || null;
}
const businessInfo = {
dbaName: await readCell("dba_name"),
ownershipName: await readCell("ownership_name"),
businessAccountNumber: await readCell("certificate_number"),
locationId: await readCell("ttxid"),
streetAddress: await readCell("full_business_address"),
};
if (
businessInfo.dbaName !== businessName ||
!businessInfo.businessAccountNumber ||
!businessInfo.locationId
) {
throw new Error("The filtered registry row is missing the requested business identity.");
}

// Extract comprehensive business information after agent completes the search.
console.log("Extracting business information...");
const businessInfo = await stagehand.extract(
"Extract all visible business information including DBA Name, Ownership Name, Business Account Number, Location Id, Street Address, Business Start Date, Business End Date, Neighborhood, NAICS Code, and NAICS Code Description",
z.object({
dbaName: z.string(),
ownershipName: z.string().optional(),
businessAccountNumber: z.string(),
locationId: z.string().optional(),
streetAddress: z.string().optional(),
businessStartDate: z.string().optional(),
businessEndDate: z.string().optional(),
neighborhood: z.string().optional(),
naicsCode: z.string(),
naicsCodeDescription: z.string().optional(),
}),
{ page },
// The registry virtualizes its columns; scroll to render the remaining fields.
await page.evaluate(() => {
const scrollbar = document.querySelector<HTMLElement>(".ag-body-horizontal-scroll-viewport")!;
scrollbar.scrollLeft = scrollbar.scrollWidth;
});
await waitFor(
() => page.locator('[role="gridcell"][col-id="self_reported_naics_code"]').count(),
"the Self-Reported NAICS Code column",
);

// Preserve blank published values instead of inferring from nearby cells.
const details = {
businessStartDate: await readCell("dba_start_date"),
businessEndDate: await readCell("dba_end_date"),
neighborhood: await readCell("neighborhoods_analysis_boundaries"),
licenseCodeDescription: await readCell("lic_code_description"),
naicsCode: await readCell("self_reported_naics_code"),
};
console.log("Business Information:");
console.log(JSON.stringify(businessInfo, null, 2));
console.log(JSON.stringify({ ...businessInfo, ...details }, null, 2));
if (!details.naicsCode)
console.log("The registry does not list a Self-Reported NAICS Code for this business.");
} catch (error) {
console.error("Error during business lookup:", error);
const errorMessage =
error !== null &&
typeof error === "object" &&
"message" in error &&
typeof error.message === "string"
? error.message
: String(error);
console.error("Error during business lookup:", errorMessage);
throw error;
} finally {
// Always close session to release resources and clean up.
await stagehand.close();
console.log("Session closed successfully");
}
}

main().catch((err) => {
console.error("Error in business lookup:", err);
console.error("Common issues:");
console.error(" - Check .env file has BROWSERBASE_API_KEY");
console.error(" - Verify GOOGLE_API_KEY is set for the agent");
console.error("Docs: https://docs.stagehand.dev/v3/first-steps/introduction");
process.exit(1);
});
await main();