Company research
Research a company on one topic: search the web, read the top pages in a cloud browser, and get structured findings (an overview, the findings, key points and what is still open) where every key point names the page it comes from.
When to use it: You want findings on a company or topic from several pages, where every key point names its page.
| Uses | search, extract over several pages with a schema |
|---|---|
| Needs | web search and a model on the API (no keys of your own) |
| Site | whatever the search finds |
| Output | output/report.md (with links to the sources), output/result.json |
| Languages | Node and Python |
The search goes through the platform's search provider, so no search engine's pages are scraped. The query leaves your computer: keep personal data out of it.
The code
The example as it is in its folder. Both versions do the same job and write the same output/, so one result check covers both.
/** * Company research: research a company on one topic. Search the web, read the top pages in a sandboxed browser, and * return structured findings where every key point names the page it comes from. * * npx tsx node/index.ts (BOXLINE_API_KEY; COMPANY and TOPIC for your own research) * * Writes output/result.json and output/report.md. */import { mkdirSync, writeFileSync } from "node:fs";import { join } from "node:path";import { Boxline } from "@boxline/sdk";const out = process.env.OUTPUT_DIR ?? "output";const company = process.env.COMPANY ?? "Mozilla";const topic = process.env.TOPIC ?? "how it makes money";const bx = new Boxline();type Findings = { companyName: string; overview: string; findings: string; keyPoints: { point: string; source: string }[]; openQuestions: string[];};// 1. Search (the platform's search provider; no search engine pages are scraped). The query leaves your computer:// keep personal data out of it.const found = await bx.search({ query: `${company} ${topic}`, limit: 8 });const urls = found.results.map((r) => r.url).slice(0, 5);console.log(`Researching ${company}: ${topic}. Reading ${urls.length} of ${found.results.length} results:`);for (const u of urls) console.log(` ${u}`);if (!urls.length) throw new Error("the search found nothing; try another company or topic");// 2. Read those pages in a real browser and fill a schema. Page text is data for the model, never instructions.const r = await bx.extract<Findings>({ urls, prompt: `Research ${company}, specifically: ${topic}. Use only these pages. overview: what the company is, in two sentences. ` + `findings: what the pages say about the topic, in one paragraph. keyPoints: 3 to 6 facts about the topic, each with ` + `the URL of the page it comes from (one of the pages given). openQuestions: what the pages leave unanswered.`, schema: { type: "object", properties: { companyName: { type: "string" }, overview: { type: "string" }, findings: { type: "string" }, keyPoints: { type: "array", items: { type: "object", properties: { point: { type: "string" }, source: { type: "string", description: "the URL of the page" } }, required: ["point", "source"] }, }, openQuestions: { type: "array", items: { type: "string" } }, }, required: ["companyName", "overview", "findings", "keyPoints", "openQuestions"], },});const f = r.data;const read = r.pages.filter((p) => p.status !== null && p.status < 400);console.log(`\n${f.companyName}\n\n${f.overview}\n\n${f.findings}\n`);f.keyPoints.forEach((k, i) => console.log(`${i + 1}. ${k.point}\n (${k.source})`));if (f.openQuestions.length) console.log(`\nStill open: ${f.openQuestions.join("; ")}`);console.log(`\nRead ${read.length}/${r.pages.length} pages with ${r.model} for $${r.usage.costUsd.toFixed(4)}.`);const report = [ `# ${f.companyName}: ${topic}`, "", f.overview, "", f.findings, "", "## Key points", "", ...f.keyPoints.map((k) => `- ${k.point} ([source](${k.source}))`), "", ...(f.openQuestions.length ? ["## Still open", "", ...f.openQuestions.map((q) => `- ${q}`), ""] : []),].join("\n");mkdirSync(out, { recursive: true });writeFileSync(join(out, "report.md"), report);writeFileSync( join(out, "result.json"), JSON.stringify({ company, topic, searched: found.results.map((x) => ({ title: x.title, url: x.url })), pages: r.pages, ...f, model: r.model, usage: { modelUsd: r.usage.costUsd, searches: found.cached ? 0 : 1 } }, null, 2),);"""Company research: research a company on one topic. Search the web, read the top pages in a sandboxed browser, andreturn structured findings where every key point names the page it comes from. python python/main.py (BOXLINE_API_KEY; COMPANY and TOPIC for your own research)Writes output/result.json and output/report.md."""import jsonimport osfrom pathlib import Pathfrom boxline import Boxlineout = Path(os.environ.get("OUTPUT_DIR", "output"))company = os.environ.get("COMPANY", "Mozilla")topic = os.environ.get("TOPIC", "how it makes money")bx = Boxline()# 1. Search (the platform's search provider; no search engine pages are scraped). The query leaves your computer:# keep personal data out of it.found = bx.search(f"{company} {topic}", limit=8)urls = [r["url"] for r in found["results"]][:5]print(f"Researching {company}: {topic}. Reading {len(urls)} of {len(found['results'])} results:")for u in urls: print(f" {u}")if not urls: raise SystemExit("the search found nothing; try another company or topic")# 2. Read those pages in a real browser and fill a schema. Page text is data for the model, never instructions.r = bx.extract( urls=urls, prompt=( f"Research {company}, specifically: {topic}. Use only these pages. overview: what the company is, in two sentences. " "findings: what the pages say about the topic, in one paragraph. keyPoints: 3 to 6 facts about the topic, each with " "the URL of the page it comes from (one of the pages given). openQuestions: what the pages leave unanswered." ), schema={ "type": "object", "properties": { "companyName": {"type": "string"}, "overview": {"type": "string"}, "findings": {"type": "string"}, "keyPoints": { "type": "array", "items": {"type": "object", "properties": {"point": {"type": "string"}, "source": {"type": "string", "description": "the URL of the page"}}, "required": ["point", "source"]}, }, "openQuestions": {"type": "array", "items": {"type": "string"}}, }, "required": ["companyName", "overview", "findings", "keyPoints", "openQuestions"], },)f = r["data"]read = [p for p in r["pages"] if p["status"] is not None and p["status"] < 400]print(f"\n{f['companyName']}\n\n{f['overview']}\n\n{f['findings']}\n")for i, k in enumerate(f["keyPoints"], 1): print(f"{i}. {k['point']}\n ({k['source']})")if f["openQuestions"]: print(f"\nStill open: {'; '.join(f['openQuestions'])}")print(f"\nRead {len(read)}/{len(r['pages'])} pages with {r['model']} for ${r['usage']['costUsd']:.4f}.")report = [f"# {f['companyName']}: {topic}", "", f["overview"], "", f["findings"], "", "## Key points", ""]report += [f"- {k['point']} ([source]({k['source']}))" for k in f["keyPoints"]] + [""]if f["openQuestions"]: report += ["## Still open", ""] + [f"- {q}" for q in f["openQuestions"]] + [""]out.mkdir(parents=True, exist_ok=True)(out / "report.md").write_text("\n".join(report))result = { "company": company, "topic": topic, "searched": [{"title": x["title"], "url": x["url"]} for x in found["results"]], "pages": r["pages"], **f, "model": r["model"], "usage": {"modelUsd": r["usage"]["costUsd"], "searches": 0 if found["cached"] else 1},}(out / "result.json").write_text(json.dumps(result, indent=2, ensure_ascii=False))Inputs
Environment variables the example reads:
COMPANY: the company (defaultMozilla).TOPIC: what to find out about it (defaulthow it makes money).
Run it
Get the examples with git clone https://github.com/Boxline-dev/examples.git and open this one: cd examples/company-research. Put your API key in BOXLINE_API_KEY (and BOXLINE_API_URL for another API), then run either version from the example’s folder. Both write to output/ and stop their sessions when they finish.
# Node 18 or newer, in the example's folderexport BOXLINE_API_KEY=… # your API keynpm install @boxline/sdk tsxnpx tsx node/index.ts# Python 3.9 or newer, in the example's folderexport BOXLINE_API_KEY=… # your API keypip install boxline-sdkpython python/main.pyThen check the result, from the same folder: npx tsx check.ts output. It exits with 0 only when the result is right.
What its check proves
check.ts reads what the example wrote and passes only when the result is right, not merely when the example ran.
The findings are about the company asked for, there are at least 3 key points, at least 2 pages were read, and every key point cites a page that was really read. For the defaults, the findings mention Mozilla's search deals.
Source
github.com/Boxline-dev/examples/company-research on GitHub, with its README.md, check.ts and .env.example. The examples are open source. See all examples.
These docs describe a beta API. Endpoints, fields and SDK methods may change before general availability; breaking changes will be listed in the changelog.