orangeslice 2.1.2 → 2.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -3
- package/dist/api.js +1 -1
- package/dist/cli.js +102 -22
- package/docs/integrations/hubspot/getList.md +52 -0
- package/docs/integrations/hubspot/index.md +5 -1
- package/docs/lookalike-search/index.md +154 -0
- package/docs/prospecting/index.md +77 -67
- package/docs/prospecting/linkedin_data/QUICK_REF.md +3 -1
- package/docs/prospecting/linkedin_data/index.md +33 -12
- package/docs/services/builtWith/index.md +6 -6
- package/docs/services/builtWith/lookupDomain.ts +1 -1
- package/docs/services/builtWith/relationships.ts +1 -1
- package/docs/services/builtWith/searchByTech.ts +1 -1
- package/docs/services/company/getEmployeesFromLinkedin.md +26 -12
- package/docs/services/company/linkedin/enrich.md +2 -0
- package/docs/services/company/linkedin/search.md +4 -2
- package/docs/services/company/revenue.md +74 -0
- package/docs/services/googleMaps/scrape.ts +1 -1
- package/docs/services/ocean/search/companies.ts +130 -0
- package/docs/services/ocean/search/people.ts +120 -0
- package/docs/services/person/contact/get.ts +2 -2
- package/docs/services/person/linkedin/findUrl.md +2 -2
- package/docs/services/person/linkedin/search.md +4 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,13 +6,16 @@ Orangeslice provides a `services.*` API for B2B research, enrichment, scraping,
|
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
npx orangeslice
|
|
9
|
+
bunx orangeslice
|
|
10
|
+
pnpm dlx orangeslice
|
|
11
|
+
yarn dlx orangeslice
|
|
9
12
|
```
|
|
10
13
|
|
|
11
14
|
The CLI copies docs to `./orangeslice-docs`, creates `./orangeslice-docs/AGENTS.md`, initializes `package.json` when missing, installs `orangeslice` in the current directory, opens browser auth, and stores your API key in `~/.config/orangeslice/config.json`.
|
|
12
15
|
|
|
13
16
|
### Auth behavior
|
|
14
17
|
|
|
15
|
-
- `npx
|
|
18
|
+
- Any supported runner (`npx`, `bunx`, `pnpm dlx`, `yarn dlx`) uses browser-based device auth and auto-provisions an API key.
|
|
16
19
|
- SDK credential precedence:
|
|
17
20
|
1. `configure({ apiKey })`
|
|
18
21
|
2. `ORANGESLICE_API_KEY` env var
|
|
@@ -21,23 +24,32 @@ The CLI copies docs to `./orangeslice-docs`, creates `./orangeslice-docs/AGENTS.
|
|
|
21
24
|
### CLI auth commands
|
|
22
25
|
|
|
23
26
|
```bash
|
|
24
|
-
# One-time setup (also runs on plain `npx orangeslice` when unauthenticated)
|
|
27
|
+
# One-time setup (also runs on plain `npx orangeslice` / `bunx orangeslice` when unauthenticated)
|
|
25
28
|
npx orangeslice login
|
|
29
|
+
bunx orangeslice login
|
|
30
|
+
pnpm dlx orangeslice login
|
|
31
|
+
yarn dlx orangeslice login
|
|
26
32
|
|
|
27
33
|
# Force re-auth and replace local stored key
|
|
28
34
|
npx orangeslice login --force
|
|
35
|
+
bunx orangeslice login --force
|
|
29
36
|
|
|
30
37
|
# Remove locally stored key
|
|
31
38
|
npx orangeslice logout
|
|
39
|
+
bunx orangeslice logout
|
|
32
40
|
|
|
33
41
|
# Show current auth source (env or config file)
|
|
34
42
|
npx orangeslice auth status
|
|
43
|
+
bunx orangeslice auth status
|
|
35
44
|
```
|
|
36
45
|
|
|
37
46
|
Install as a dependency when writing app code:
|
|
38
47
|
|
|
39
48
|
```bash
|
|
40
49
|
npm install orangeslice
|
|
50
|
+
bun add orangeslice
|
|
51
|
+
pnpm add orangeslice
|
|
52
|
+
yarn add orangeslice
|
|
41
53
|
```
|
|
42
54
|
|
|
43
55
|
## Public API (services-first)
|
|
@@ -101,7 +113,7 @@ All service calls go through `post()` in `src/api.ts`.
|
|
|
101
113
|
|
|
102
114
|
## Docs installed by CLI
|
|
103
115
|
|
|
104
|
-
After
|
|
116
|
+
After running the CLI bootstrap command, you should have:
|
|
105
117
|
|
|
106
118
|
```text
|
|
107
119
|
orangeslice-docs/
|
package/dist/api.js
CHANGED
|
@@ -137,7 +137,7 @@ async function post(endpoint, payload) {
|
|
|
137
137
|
const apiKey = resolveApiKey();
|
|
138
138
|
if (!apiKey) {
|
|
139
139
|
throw new Error("[orangeslice] No API key configured. " +
|
|
140
|
-
"Run `npx orangeslice`, set ORANGESLICE_API_KEY in your environment, or call configure({ apiKey: 'osk_...' }).");
|
|
140
|
+
"Run `npx orangeslice`, `bunx orangeslice`, `pnpm dlx orangeslice`, or `yarn dlx orangeslice`, set ORANGESLICE_API_KEY in your environment, or call configure({ apiKey: 'osk_...' }).");
|
|
141
141
|
}
|
|
142
142
|
const url = `${baseUrl}${endpoint}`;
|
|
143
143
|
const body = JSON.stringify({ ...payload, inlineWaitMs: DEFAULT_INLINE_WAIT_MS });
|
package/dist/cli.js
CHANGED
|
@@ -47,6 +47,7 @@ const CONFIG_DIR = path.join(os.homedir(), ".config", "orangeslice");
|
|
|
47
47
|
const CONFIG_PATH = path.join(CONFIG_DIR, "config.json");
|
|
48
48
|
const AGENTS_IMPORT_LINE = "@orangeslice-docs/AGENTS.md";
|
|
49
49
|
const CLAUDE_IMPORT_LINE = "@orangeslice-docs/CLAUDE.md";
|
|
50
|
+
const SUPPORTED_RUNNERS = ["npm", "bun", "pnpm", "yarn"];
|
|
50
51
|
function isDir(p) {
|
|
51
52
|
try {
|
|
52
53
|
return fs.statSync(p).isDirectory();
|
|
@@ -71,6 +72,71 @@ function resolveDocsDir() {
|
|
|
71
72
|
}
|
|
72
73
|
return LEGACY_DOCS_DIR;
|
|
73
74
|
}
|
|
75
|
+
function hasFile(cwd, fileName) {
|
|
76
|
+
return fs.existsSync(path.join(cwd, fileName));
|
|
77
|
+
}
|
|
78
|
+
function readPackageManagerFromPackageJson(cwd) {
|
|
79
|
+
const packageJsonPath = path.join(cwd, "package.json");
|
|
80
|
+
if (!fs.existsSync(packageJsonPath))
|
|
81
|
+
return null;
|
|
82
|
+
try {
|
|
83
|
+
const parsed = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
84
|
+
const packageManager = parsed.packageManager?.toLowerCase() || "";
|
|
85
|
+
if (packageManager.startsWith("bun@"))
|
|
86
|
+
return "bun";
|
|
87
|
+
if (packageManager.startsWith("pnpm@"))
|
|
88
|
+
return "pnpm";
|
|
89
|
+
if (packageManager.startsWith("yarn@"))
|
|
90
|
+
return "yarn";
|
|
91
|
+
if (packageManager.startsWith("npm@"))
|
|
92
|
+
return "npm";
|
|
93
|
+
}
|
|
94
|
+
catch { }
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
function detectPackageManager(cwd) {
|
|
98
|
+
const fromPackageJson = readPackageManagerFromPackageJson(cwd);
|
|
99
|
+
if (fromPackageJson)
|
|
100
|
+
return fromPackageJson;
|
|
101
|
+
if (hasFile(cwd, "bun.lock") || hasFile(cwd, "bun.lockb"))
|
|
102
|
+
return "bun";
|
|
103
|
+
if (hasFile(cwd, "pnpm-lock.yaml"))
|
|
104
|
+
return "pnpm";
|
|
105
|
+
if (hasFile(cwd, "yarn.lock"))
|
|
106
|
+
return "yarn";
|
|
107
|
+
if (hasFile(cwd, "package-lock.json") || hasFile(cwd, "npm-shrinkwrap.json"))
|
|
108
|
+
return "npm";
|
|
109
|
+
const userAgent = process.env.npm_config_user_agent?.toLowerCase() || "";
|
|
110
|
+
if (userAgent.includes("bun/"))
|
|
111
|
+
return "bun";
|
|
112
|
+
if (userAgent.includes("pnpm/"))
|
|
113
|
+
return "pnpm";
|
|
114
|
+
if (userAgent.includes("yarn/"))
|
|
115
|
+
return "yarn";
|
|
116
|
+
return "npm";
|
|
117
|
+
}
|
|
118
|
+
function getRunnerCommand(packageManager, suffix = "") {
|
|
119
|
+
const command = packageManager === "bun"
|
|
120
|
+
? "bunx orangeslice"
|
|
121
|
+
: packageManager === "pnpm"
|
|
122
|
+
? "pnpm dlx orangeslice"
|
|
123
|
+
: packageManager === "yarn"
|
|
124
|
+
? "yarn dlx orangeslice"
|
|
125
|
+
: "npx orangeslice";
|
|
126
|
+
return suffix ? `${command} ${suffix}` : command;
|
|
127
|
+
}
|
|
128
|
+
function getInstallCommand(packageManager) {
|
|
129
|
+
if (packageManager === "bun")
|
|
130
|
+
return "bun add orangeslice";
|
|
131
|
+
if (packageManager === "pnpm")
|
|
132
|
+
return "pnpm add orangeslice";
|
|
133
|
+
if (packageManager === "yarn")
|
|
134
|
+
return "yarn add orangeslice";
|
|
135
|
+
return "npm install orangeslice";
|
|
136
|
+
}
|
|
137
|
+
function getSupportedRunnerExamples() {
|
|
138
|
+
return SUPPORTED_RUNNERS.map((packageManager) => getRunnerCommand(packageManager));
|
|
139
|
+
}
|
|
74
140
|
function copyDirSync(src, dest) {
|
|
75
141
|
fs.mkdirSync(dest, { recursive: true });
|
|
76
142
|
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
@@ -105,7 +171,7 @@ Use these docs as the source of truth. If there is any conflict between your pri
|
|
|
105
171
|
- \`import { services } from "orangeslice"\`
|
|
106
172
|
- \`import { configure, services } from "orangeslice"\` when setting API key programmatically
|
|
107
173
|
- Do NOT use \`import { services } from "./orangeslice"\` unless the user explicitly has a local wrapper file at that path.
|
|
108
|
-
- \`npx orangeslice\`
|
|
174
|
+
- The orangeslice bootstrap commands (\`npx orangeslice\`, \`bunx orangeslice\`, \`pnpm dlx orangeslice\`, \`yarn dlx orangeslice\`) sync docs, install the package, and handle auth. They do NOT execute user app scripts.
|
|
109
175
|
|
|
110
176
|
## Runtime Requirements
|
|
111
177
|
- If writing standalone scripts that use top-level \`await\`, use ESM:
|
|
@@ -194,12 +260,21 @@ function ensurePackageJson(cwd) {
|
|
|
194
260
|
const packageJsonPath = path.join(cwd, "package.json");
|
|
195
261
|
if (fs.existsSync(packageJsonPath))
|
|
196
262
|
return;
|
|
197
|
-
console.log(" No package.json found.
|
|
198
|
-
|
|
263
|
+
console.log(" No package.json found. Creating one...");
|
|
264
|
+
const defaultName = path
|
|
265
|
+
.basename(cwd)
|
|
266
|
+
.toLowerCase()
|
|
267
|
+
.replace(/[^a-z0-9._-]+/g, "-")
|
|
268
|
+
.replace(/^-+|-+$/g, "") || "orangeslice-app";
|
|
269
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify({
|
|
270
|
+
name: defaultName,
|
|
271
|
+
version: "1.0.0",
|
|
272
|
+
private: true
|
|
273
|
+
}, null, 2) + "\n", "utf8");
|
|
199
274
|
}
|
|
200
|
-
function installOrangeslice(cwd) {
|
|
201
|
-
console.log(
|
|
202
|
-
(0, child_process_1.execSync)(
|
|
275
|
+
function installOrangeslice(cwd, packageManager) {
|
|
276
|
+
console.log(` Installing orangeslice with ${packageManager}...`);
|
|
277
|
+
(0, child_process_1.execSync)(getInstallCommand(packageManager), { stdio: "inherit", cwd });
|
|
203
278
|
}
|
|
204
279
|
function readConfigFile() {
|
|
205
280
|
try {
|
|
@@ -312,13 +387,17 @@ async function setupApiKey(force = false) {
|
|
|
312
387
|
saveConfigFile({ apiKey });
|
|
313
388
|
console.log(` ✓ API key saved to ${CONFIG_PATH}\n`);
|
|
314
389
|
}
|
|
315
|
-
function printHelp() {
|
|
390
|
+
function printHelp(packageManager) {
|
|
316
391
|
console.log("Usage:");
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
console.log("
|
|
321
|
-
console.log("
|
|
392
|
+
for (const command of getSupportedRunnerExamples()) {
|
|
393
|
+
console.log(` ${command}`);
|
|
394
|
+
}
|
|
395
|
+
console.log("");
|
|
396
|
+
console.log("Commands:");
|
|
397
|
+
console.log(` ${getRunnerCommand(packageManager, "login [--force]")}`);
|
|
398
|
+
console.log(` ${getRunnerCommand(packageManager, "logout")}`);
|
|
399
|
+
console.log(` ${getRunnerCommand(packageManager, "auth <API_KEY>")}`);
|
|
400
|
+
console.log(` ${getRunnerCommand(packageManager, "auth status")}\n`);
|
|
322
401
|
}
|
|
323
402
|
async function runLogin(args) {
|
|
324
403
|
const force = args.includes("--force");
|
|
@@ -336,16 +415,16 @@ function runLogout() {
|
|
|
336
415
|
console.log(" Note: ORANGESLICE_API_KEY env var is still set in this shell.");
|
|
337
416
|
}
|
|
338
417
|
}
|
|
339
|
-
function runAuthSet(apiKey) {
|
|
418
|
+
function runAuthSet(apiKey, packageManager) {
|
|
340
419
|
const trimmed = apiKey.trim();
|
|
341
420
|
if (!trimmed) {
|
|
342
|
-
console.log(
|
|
421
|
+
console.log(` Usage: ${getRunnerCommand(packageManager, "auth <API_KEY>")}`);
|
|
343
422
|
return;
|
|
344
423
|
}
|
|
345
424
|
saveConfigFile({ apiKey: trimmed });
|
|
346
425
|
console.log(` ✓ API key saved to ${CONFIG_PATH} (${maskKey(trimmed)})`);
|
|
347
426
|
}
|
|
348
|
-
function runAuthStatus() {
|
|
427
|
+
function runAuthStatus(packageManager) {
|
|
349
428
|
const envKey = process.env.ORANGESLICE_API_KEY?.trim() || "";
|
|
350
429
|
if (envKey) {
|
|
351
430
|
console.log(` Authenticated via env var: ${maskKey(envKey)}`);
|
|
@@ -356,11 +435,13 @@ function runAuthStatus() {
|
|
|
356
435
|
console.log(` Authenticated via config file (${CONFIG_PATH}): ${maskKey(fileKey)}`);
|
|
357
436
|
return;
|
|
358
437
|
}
|
|
359
|
-
console.log(
|
|
438
|
+
console.log(` Not authenticated. Run \`${getRunnerCommand(packageManager, "login")}\`.`);
|
|
360
439
|
}
|
|
361
440
|
async function main() {
|
|
362
441
|
const args = process.argv.slice(2);
|
|
363
442
|
const command = args[0];
|
|
443
|
+
const cwd = process.cwd();
|
|
444
|
+
const packageManager = detectPackageManager(cwd);
|
|
364
445
|
if (command === "login") {
|
|
365
446
|
await runLogin(args.slice(1));
|
|
366
447
|
return;
|
|
@@ -372,18 +453,18 @@ async function main() {
|
|
|
372
453
|
if (command === "auth") {
|
|
373
454
|
const subcommand = args[1];
|
|
374
455
|
if (subcommand === "status") {
|
|
375
|
-
runAuthStatus();
|
|
456
|
+
runAuthStatus(packageManager);
|
|
376
457
|
return;
|
|
377
458
|
}
|
|
378
459
|
if (subcommand && subcommand !== "--help" && subcommand !== "-h") {
|
|
379
|
-
runAuthSet(subcommand);
|
|
460
|
+
runAuthSet(subcommand, packageManager);
|
|
380
461
|
return;
|
|
381
462
|
}
|
|
382
|
-
printHelp();
|
|
463
|
+
printHelp(packageManager);
|
|
383
464
|
return;
|
|
384
465
|
}
|
|
385
466
|
if (command && (command === "--help" || command === "-h" || command === "help")) {
|
|
386
|
-
printHelp();
|
|
467
|
+
printHelp(packageManager);
|
|
387
468
|
return;
|
|
388
469
|
}
|
|
389
470
|
console.log("\norangeslice\n");
|
|
@@ -396,11 +477,10 @@ async function main() {
|
|
|
396
477
|
writeClaudeGuide(TARGET_DIR);
|
|
397
478
|
console.log(` ✓ Docs (${path.basename(docsDir)}) → ./orangeslice-docs/\n`);
|
|
398
479
|
// Always set up package installation in the current directory.
|
|
399
|
-
const cwd = process.cwd();
|
|
400
480
|
ensureAgentsImport(cwd);
|
|
401
481
|
ensureClaudeImport(cwd);
|
|
402
482
|
ensurePackageJson(cwd);
|
|
403
|
-
installOrangeslice(cwd);
|
|
483
|
+
installOrangeslice(cwd, packageManager);
|
|
404
484
|
console.log(" ✓ Package installed in current directory\n");
|
|
405
485
|
// API key setup
|
|
406
486
|
await setupApiKey();
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# getList
|
|
2
|
+
|
|
3
|
+
Get a specific HubSpot list by ID.
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
// Fetch a list by ID
|
|
7
|
+
const list = await integrations.hubspot.getList("123456");
|
|
8
|
+
|
|
9
|
+
// Include the filter definition in the response
|
|
10
|
+
const detailedList = await integrations.hubspot.getList("123456", {
|
|
11
|
+
includeFilters: true
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
console.log(list.name); // "Target Accounts"
|
|
15
|
+
console.log(list.processingType); // "DYNAMIC"
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Input
|
|
19
|
+
|
|
20
|
+
| Parameter | Type | Description |
|
|
21
|
+
| ------------------------ | --------- | --------------------------------------------------------------------- |
|
|
22
|
+
| `listId` | `string` | The HubSpot list ID to retrieve |
|
|
23
|
+
| `options.includeFilters` | `boolean` | Include the list's filter definition in the response (default: false) |
|
|
24
|
+
|
|
25
|
+
## Output
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
{
|
|
29
|
+
listId: string;
|
|
30
|
+
listVersion: number;
|
|
31
|
+
name: string;
|
|
32
|
+
objectTypeId: string;
|
|
33
|
+
processingStatus: string;
|
|
34
|
+
processingType: string;
|
|
35
|
+
createdAt?: string;
|
|
36
|
+
updatedAt?: string;
|
|
37
|
+
deletedAt?: string;
|
|
38
|
+
createdById?: string;
|
|
39
|
+
updatedById?: string;
|
|
40
|
+
filtersUpdatedAt?: string;
|
|
41
|
+
size?: number;
|
|
42
|
+
membershipSettings?: Record<string, unknown>;
|
|
43
|
+
listPermissions?: Record<string, unknown>;
|
|
44
|
+
filterBranch?: Record<string, unknown>;
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Notes
|
|
49
|
+
|
|
50
|
+
- Set `includeFilters: true` when you need the list's filter definition
|
|
51
|
+
- `objectTypeId` identifies the CRM object type the list contains, such as contacts or companies
|
|
52
|
+
- `processingType` commonly indicates whether the list is dynamic, manual, or snapshot-based
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: HubSpot CRM - contacts, companies, deals, workflows
|
|
2
|
+
description: HubSpot CRM - contacts, companies, deals, lists, workflows
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
# HubSpot Integration
|
|
@@ -41,6 +41,10 @@ Typed functions for HubSpot CRM operations.
|
|
|
41
41
|
- `integrations.hubspot.updateFlow(flowId, input)` - Update an existing workflow
|
|
42
42
|
- `integrations.hubspot.deleteFlow(flowId)` - Delete a workflow
|
|
43
43
|
|
|
44
|
+
## Lists
|
|
45
|
+
|
|
46
|
+
- `integrations.hubspot.getList(listId, options?)` - Get list by ID
|
|
47
|
+
|
|
44
48
|
## Properties
|
|
45
49
|
|
|
46
50
|
- `integrations.hubspot.listProperties(objectType, options?)` - List all property definitions for an object type
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: lookalike-search
|
|
3
|
+
description: Find companies and people similar to a user's best customers or seed list using Ocean.io. Mandatory read for any "find me companies like X" or "lookalike" request.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Mode: Lookalike Search
|
|
7
|
+
|
|
8
|
+
**Goal:** Find companies and people that are similar to a user's existing customers, seed domains, or ideal customer profile — using Ocean.io's lookalike engine.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## When to Use Lookalike Search
|
|
13
|
+
|
|
14
|
+
Use Ocean.io lookalike search when the user:
|
|
15
|
+
|
|
16
|
+
- Has **example companies** (domains) and wants to find more like them
|
|
17
|
+
- Says "companies like", "similar to", "lookalike", "companies that look like my customers"
|
|
18
|
+
- Wants to **expand a seed list** of known-good accounts into a larger pipeline
|
|
19
|
+
- Needs **account-based prospecting** starting from a handful of reference accounts
|
|
20
|
+
- Wants to find the **right people** at companies matching a lookalike profile
|
|
21
|
+
|
|
22
|
+
**Not a good fit when:**
|
|
23
|
+
|
|
24
|
+
- User wants keyword/niche discovery ("AI CRM companies") → use `web.search`
|
|
25
|
+
- User wants funding-based filtering → use `crunchbase.search`
|
|
26
|
+
- User wants a specific company lookup → use LinkedIn B2B DB
|
|
27
|
+
- User only has descriptions, not example domains → use `web.search` or `crunchbase.search` first to build a seed list
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## The Two Endpoints
|
|
32
|
+
|
|
33
|
+
### 1. `services.ocean.search.companies` — Find Lookalike Companies
|
|
34
|
+
|
|
35
|
+
Provide seed domains and filters, get back similar companies with rich firmographic data.
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
const results = await services.ocean.search.companies({
|
|
39
|
+
companiesFilters: {
|
|
40
|
+
lookalikeDomains: ["stripe.com", "brex.com", "ramp.com"],
|
|
41
|
+
companySizes: ["51-200", "201-500"],
|
|
42
|
+
countries: ["us"]
|
|
43
|
+
},
|
|
44
|
+
size: 50
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// results.companies → array of { company, relevance }
|
|
48
|
+
// results.total → total matches
|
|
49
|
+
// results.searchAfter → cursor for next page
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Key filters:**
|
|
53
|
+
|
|
54
|
+
| Filter | Example | Notes |
|
|
55
|
+
| ------------------ | -------------------------------- | ------------------------------------------------ |
|
|
56
|
+
| `lookalikeDomains` | `["stripe.com", "plaid.com"]` | Core input — seed domains to find lookalikes for |
|
|
57
|
+
| `companySizes` | `["51-200", "201-500"]` | Filter by employee count range |
|
|
58
|
+
| `countries` | `["us", "gb", "de"]` | Two-letter ISO country codes |
|
|
59
|
+
| `industries` | `["SaaS", "Fintech"]` | Industry category names |
|
|
60
|
+
| `technologies` | `["React", "Salesforce"]` | Filter by tech stack |
|
|
61
|
+
| `revenueRanges` | `["1M-10M", "10M-50M"]` | Revenue band filter |
|
|
62
|
+
| `keywords` | `["payments", "infrastructure"]` | Keyword filter |
|
|
63
|
+
| `ecommerce` | `true` | E-commerce companies only |
|
|
64
|
+
| `minScore` | `0.5` | Minimum similarity score (0–1) |
|
|
65
|
+
|
|
66
|
+
**Pagination:** Use `searchAfter` from the previous response for efficient cursor-based pagination. Max `size` per request is 100.
|
|
67
|
+
|
|
68
|
+
### 2. `services.ocean.search.people` — Find People at Companies
|
|
69
|
+
|
|
70
|
+
Combine company filters with people filters to find the right contacts.
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
const results = await services.ocean.search.people({
|
|
74
|
+
companiesFilters: {
|
|
75
|
+
lookalikeDomains: ["stripe.com", "brex.com"]
|
|
76
|
+
},
|
|
77
|
+
peopleFilters: {
|
|
78
|
+
seniorities: ["C-Level", "VP"],
|
|
79
|
+
departments: ["Engineering", "Product"]
|
|
80
|
+
},
|
|
81
|
+
size: 50,
|
|
82
|
+
enableEmailSearch: true
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// results.people → array of OceanPersonResult
|
|
86
|
+
// Each person has: name, jobTitle, linkedinUrl, email, phone, company info
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**People filters:**
|
|
90
|
+
|
|
91
|
+
| Filter | Example | Notes |
|
|
92
|
+
| -------------------- | -------------------------------- | ------------------------------------------------ |
|
|
93
|
+
| `seniorities` | `["C-Level", "VP", "Director"]` | Job seniority levels |
|
|
94
|
+
| `departments` | `["Engineering", "Sales"]` | Department targeting |
|
|
95
|
+
| `jobTitleKeywords` | `["CTO", "Head of Engineering"]` | Title keyword matching |
|
|
96
|
+
| `countries` | `["us"]` | People location filter |
|
|
97
|
+
| `lookalikePeopleIds` | `["id1", "id2"]` | Find people similar to these Ocean.io person IDs |
|
|
98
|
+
|
|
99
|
+
**Contact data:**
|
|
100
|
+
|
|
101
|
+
- Set `enableEmailSearch: true` to get verified email addresses
|
|
102
|
+
- Set `enablePhoneSearch: true` to get verified phone numbers
|
|
103
|
+
- Both cost additional Ocean.io credits
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Standard Workflow
|
|
108
|
+
|
|
109
|
+
### Company-first (most common)
|
|
110
|
+
|
|
111
|
+
1. **Collect seed domains** — from user input, an existing sheet column, or a prior prospecting step
|
|
112
|
+
2. **Search lookalike companies** — `services.ocean.search.companies` with seed domains + filters
|
|
113
|
+
3. **Add to sheet** — populate a "Companies" sheet with domain, name, size, industry, etc.
|
|
114
|
+
4. **Qualify** — add enrichment columns (LinkedIn enrich, tech stack, etc.) to score/filter
|
|
115
|
+
5. **Find people** — add a column using `services.ocean.search.people` or `services.company.getEmployeesFromLinkedin`
|
|
116
|
+
|
|
117
|
+
### People-direct (when you already know the company profile)
|
|
118
|
+
|
|
119
|
+
1. **Search people directly** — `services.ocean.search.people` with company + people filters
|
|
120
|
+
2. **Add to sheet** — populate with name, title, email, LinkedIn URL, company
|
|
121
|
+
3. **Qualify** — add verification/enrichment columns
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Pagination Pattern
|
|
126
|
+
|
|
127
|
+
Ocean.io returns up to `size` results per call (max 100). For larger result sets, paginate with `searchAfter`:
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
let allCompanies = [];
|
|
131
|
+
let searchAfter = undefined;
|
|
132
|
+
|
|
133
|
+
for (let page = 0; page < 5; page++) {
|
|
134
|
+
const results = await services.ocean.search.companies({
|
|
135
|
+
companiesFilters: { lookalikeDomains: ["stripe.com"] },
|
|
136
|
+
size: 100,
|
|
137
|
+
searchAfter
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
allCompanies.push(...results.companies);
|
|
141
|
+
searchAfter = results.searchAfter;
|
|
142
|
+
|
|
143
|
+
if (!searchAfter || allCompanies.length >= results.total) break;
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Tips
|
|
150
|
+
|
|
151
|
+
- **More seed domains = better results.** 3-5 seeds produce much better lookalikes than a single domain.
|
|
152
|
+
- **Combine with enrichment.** Ocean.io returns firmographic data, but you can enrich further with LinkedIn, BuiltWith, or PredictLeads.
|
|
153
|
+
- **Use `minScore`** to control result quality — higher = more similar but fewer results.
|
|
154
|
+
- **Credits:** 5 credits per result. A search returning 50 companies = 250 credits. Reserve is based on the requested `size`.
|