Skip to content

Commit 2b8068c

Browse files
feat(hosted-keys): add Hunter.io and People Data Labs hosted key support (#4742)
* fix(db): disable statement_timeout for migrations * fix(ci): route migration workflow through guarded migrate.ts * feat(hosted-keys): add Hunter.io and People Data Labs hosted key support
1 parent 77c1d24 commit 2b8068c

26 files changed

Lines changed: 453 additions & 15 deletions

apps/sim/.env.example

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,15 @@ API_ENCRYPTION_KEY=your_api_encryption_key # Use `openssl rand -hex 32` to gener
6262
# COHERE_API_KEY= # Cohere API key for the Knowledge block reranker (rerank-v4.0-pro/-fast, rerank-v3.5). Alternatively set COHERE_API_KEY_1/2/3 for rotation.
6363
# NEXT_PUBLIC_COHERE_CONFIGURED=true # Set when COHERE_API_KEY (or rotation keys) are pre-configured above. Hides the Cohere API Key field on the Knowledge block UI.
6464

65+
# Hosted tool API keys (Optional - lets Sim supply the key so users don't have to bring their own).
66+
# Each provider reads `{PREFIX}_COUNT` then `{PREFIX}_1..N`, distributing requests round-robin across the keys.
67+
# HUNTER_API_KEY_COUNT=2 # Number of Hunter.io keys for hosted Hunter blocks
68+
# HUNTER_API_KEY_1= # Hunter.io API key #1
69+
# HUNTER_API_KEY_2= # Hunter.io API key #2
70+
# PEOPLEDATALABS_API_KEY_COUNT=2 # Number of People Data Labs keys for hosted PDL blocks
71+
# PEOPLEDATALABS_API_KEY_1= # People Data Labs API key #1
72+
# PEOPLEDATALABS_API_KEY_2= # People Data Labs API key #2
73+
6574
# Admin API (Optional - for self-hosted GitOps)
6675
# ADMIN_API_KEY= # Use `openssl rand -hex 32` to generate. Enables admin API for workflow export/import.
6776
# Usage: curl -H "x-admin-key: your_key" https://your-instance/api/v1/admin/workspaces

apps/sim/app/workspace/[workspaceId]/settings/components/byok/byok.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,14 @@ import {
2323
FireworksIcon,
2424
GeminiIcon,
2525
GoogleIcon,
26+
HunterIOIcon,
2627
ImageIcon,
2728
JinaAIIcon,
2829
LinkupIcon,
2930
MistralIcon,
3031
OpenAIIcon,
3132
ParallelIcon,
33+
PeopleDataLabsIcon,
3234
PerplexityIcon,
3335
SerperIcon,
3436
} from '@/components/icons'
@@ -156,6 +158,20 @@ const PROVIDERS: {
156158
description: 'Brand assets, logos, colors, and company info',
157159
placeholder: 'Enter your Brandfetch API key',
158160
},
161+
{
162+
id: 'hunter',
163+
name: 'Hunter',
164+
icon: HunterIOIcon,
165+
description: 'Email finder, verification, and domain search',
166+
placeholder: 'Enter your Hunter.io API key',
167+
},
168+
{
169+
id: 'peopledatalabs',
170+
name: 'People Data Labs',
171+
icon: PeopleDataLabsIcon,
172+
description: 'Person and company enrichment, search, and identity',
173+
placeholder: 'Enter your People Data Labs API key',
174+
},
159175
]
160176

161177
export function BYOK() {

apps/sim/blocks/blocks/hunter.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ Return ONLY the search query text - no explanations.`,
256256
required: true,
257257
placeholder: 'Enter your Hunter.io API key',
258258
password: true,
259+
hideWhenHosted: true,
259260
},
260261
],
261262
tools: {

apps/sim/blocks/blocks/peopledatalabs.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,7 @@ export const PeopleDataLabsBlock: BlockConfig<PdlPersonEnrichResponse> = {
397397
placeholder: 'Enter your People Data Labs API key',
398398
password: true,
399399
required: true,
400+
hideWhenHosted: true,
400401
},
401402
],
402403

apps/sim/lib/api/contracts/byok-keys.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ export const byokProviderIdSchema = z.enum([
1818
'parallel_ai',
1919
'brandfetch',
2020
'cohere',
21+
'hunter',
22+
'peopledatalabs',
2123
])
2224

2325
export const byokKeySchema = z.object({

apps/sim/tools/hunter/companies_find.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { HunterEnrichmentParams, HunterEnrichmentResponse } from '@/tools/hunter/types'
2+
import { HUNTER_API_KEY_PREFIX } from '@/tools/hunter/types'
23
import type { ToolConfig } from '@/tools/types'
34

45
export const companiesFindTool: ToolConfig<HunterEnrichmentParams, HunterEnrichmentResponse> = {
@@ -7,6 +8,18 @@ export const companiesFindTool: ToolConfig<HunterEnrichmentParams, HunterEnrichm
78
description: 'Enriches company data using domain name.',
89
version: '1.0.0',
910

11+
hosting: {
12+
envKeyPrefix: HUNTER_API_KEY_PREFIX,
13+
apiKeyParam: 'apiKey',
14+
byokProviderId: 'hunter',
15+
// Companies Find (enrichment) is free on Hunter — no credits consumed.
16+
pricing: { type: 'per_request', cost: 0 },
17+
rateLimit: {
18+
mode: 'per_request',
19+
requestsPerMinute: 60,
20+
},
21+
},
22+
1023
params: {
1124
domain: {
1225
type: 'string',

apps/sim/tools/hunter/discover.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { HunterDiscoverParams, HunterDiscoverResponse } from '@/tools/hunter/types'
2-
import { DISCOVER_RESULTS_OUTPUT } from '@/tools/hunter/types'
2+
import { DISCOVER_RESULTS_OUTPUT, HUNTER_API_KEY_PREFIX } from '@/tools/hunter/types'
33
import type { ToolConfig } from '@/tools/types'
44

55
export const discoverTool: ToolConfig<HunterDiscoverParams, HunterDiscoverResponse> = {
@@ -8,6 +8,18 @@ export const discoverTool: ToolConfig<HunterDiscoverParams, HunterDiscoverRespon
88
description: 'Returns companies matching a set of criteria using Hunter.io AI-powered search.',
99
version: '1.0.0',
1010

11+
hosting: {
12+
envKeyPrefix: HUNTER_API_KEY_PREFIX,
13+
apiKeyParam: 'apiKey',
14+
byokProviderId: 'hunter',
15+
// Discover is free on Hunter — no credits consumed.
16+
pricing: { type: 'per_request', cost: 0 },
17+
rateLimit: {
18+
mode: 'per_request',
19+
requestsPerMinute: 30,
20+
},
21+
},
22+
1123
params: {
1224
query: {
1325
type: 'string',

apps/sim/tools/hunter/domain_search.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ import type {
33
HunterDomainSearchResponse,
44
HunterEmail,
55
} from '@/tools/hunter/types'
6-
import { EMAILS_OUTPUT } from '@/tools/hunter/types'
6+
import {
7+
EMAILS_OUTPUT,
8+
HUNTER_API_KEY_PREFIX,
9+
HUNTER_SEARCH_CREDIT_USD,
10+
} from '@/tools/hunter/types'
711
import type { ToolConfig } from '@/tools/types'
812

913
export const domainSearchTool: ToolConfig<HunterDomainSearchParams, HunterDomainSearchResponse> = {
@@ -12,6 +16,28 @@ export const domainSearchTool: ToolConfig<HunterDomainSearchParams, HunterDomain
1216
description: 'Returns all the email addresses found using one given domain name, with sources.',
1317
version: '1.0.0',
1418

19+
hosting: {
20+
envKeyPrefix: HUNTER_API_KEY_PREFIX,
21+
apiKeyParam: 'apiKey',
22+
byokProviderId: 'hunter',
23+
pricing: {
24+
type: 'custom',
25+
getCost: (_params, output) => {
26+
const emails = output.emails
27+
if (!Array.isArray(emails)) {
28+
throw new Error('Hunter domain search response missing emails, cannot determine cost')
29+
}
30+
// Hunter counts one search credit only when a call returns at least one result.
31+
const cost = emails.length > 0 ? HUNTER_SEARCH_CREDIT_USD : 0
32+
return { cost, metadata: { credits: cost > 0 ? 1 : 0, emails: emails.length } }
33+
},
34+
},
35+
rateLimit: {
36+
mode: 'per_request',
37+
requestsPerMinute: 60,
38+
},
39+
},
40+
1541
params: {
1642
domain: {
1743
type: 'string',

apps/sim/tools/hunter/email_count.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { HunterEmailCountParams, HunterEmailCountResponse } from '@/tools/hunter/types'
2-
import { DEPARTMENT_OUTPUT, SENIORITY_OUTPUT } from '@/tools/hunter/types'
2+
import { DEPARTMENT_OUTPUT, HUNTER_API_KEY_PREFIX, SENIORITY_OUTPUT } from '@/tools/hunter/types'
33
import type { ToolConfig } from '@/tools/types'
44

55
export const emailCountTool: ToolConfig<HunterEmailCountParams, HunterEmailCountResponse> = {
@@ -8,6 +8,18 @@ export const emailCountTool: ToolConfig<HunterEmailCountParams, HunterEmailCount
88
description: 'Returns the total number of email addresses found for a domain or company.',
99
version: '1.0.0',
1010

11+
hosting: {
12+
envKeyPrefix: HUNTER_API_KEY_PREFIX,
13+
apiKeyParam: 'apiKey',
14+
byokProviderId: 'hunter',
15+
// Email Count is free on Hunter — no credits consumed.
16+
pricing: { type: 'per_request', cost: 0 },
17+
rateLimit: {
18+
mode: 'per_request',
19+
requestsPerMinute: 60,
20+
},
21+
},
22+
1123
params: {
1224
domain: {
1325
type: 'string',

apps/sim/tools/hunter/email_finder.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import type { HunterEmailFinderParams, HunterEmailFinderResponse } from '@/tools/hunter/types'
2-
import { SOURCES_OUTPUT, VERIFICATION_OUTPUT } from '@/tools/hunter/types'
2+
import {
3+
HUNTER_API_KEY_PREFIX,
4+
HUNTER_SEARCH_CREDIT_USD,
5+
SOURCES_OUTPUT,
6+
VERIFICATION_OUTPUT,
7+
} from '@/tools/hunter/types'
38
import type { ToolConfig } from '@/tools/types'
49

510
export const emailFinderTool: ToolConfig<HunterEmailFinderParams, HunterEmailFinderResponse> = {
@@ -9,6 +14,25 @@ export const emailFinderTool: ToolConfig<HunterEmailFinderParams, HunterEmailFin
914
'Finds the most likely email address for a person given their name and company domain.',
1015
version: '1.0.0',
1116

17+
hosting: {
18+
envKeyPrefix: HUNTER_API_KEY_PREFIX,
19+
apiKeyParam: 'apiKey',
20+
byokProviderId: 'hunter',
21+
pricing: {
22+
type: 'custom',
23+
getCost: (_params, output) => {
24+
// Hunter counts one search credit only when an email is found.
25+
const found = typeof output.email === 'string' && output.email.length > 0
26+
const cost = found ? HUNTER_SEARCH_CREDIT_USD : 0
27+
return { cost, metadata: { credits: found ? 1 : 0 } }
28+
},
29+
},
30+
rateLimit: {
31+
mode: 'per_request',
32+
requestsPerMinute: 60,
33+
},
34+
},
35+
1236
params: {
1337
domain: {
1438
type: 'string',

0 commit comments

Comments
 (0)