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
This commit is contained in:
Theodore Li
2026-05-26 18:07:15 -04:00
committed by GitHub
parent 77c1d24b17
commit 2b8068c997
26 changed files with 453 additions and 15 deletions
+9
View File
@@ -62,6 +62,15 @@ API_ENCRYPTION_KEY=your_api_encryption_key # Use `openssl rand -hex 32` to gener
# 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.
# 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.
# Hosted tool API keys (Optional - lets Sim supply the key so users don't have to bring their own).
# Each provider reads `{PREFIX}_COUNT` then `{PREFIX}_1..N`, distributing requests round-robin across the keys.
# HUNTER_API_KEY_COUNT=2 # Number of Hunter.io keys for hosted Hunter blocks
# HUNTER_API_KEY_1= # Hunter.io API key #1
# HUNTER_API_KEY_2= # Hunter.io API key #2
# PEOPLEDATALABS_API_KEY_COUNT=2 # Number of People Data Labs keys for hosted PDL blocks
# PEOPLEDATALABS_API_KEY_1= # People Data Labs API key #1
# PEOPLEDATALABS_API_KEY_2= # People Data Labs API key #2
# Admin API (Optional - for self-hosted GitOps)
# ADMIN_API_KEY= # Use `openssl rand -hex 32` to generate. Enables admin API for workflow export/import.
# Usage: curl -H "x-admin-key: your_key" https://your-instance/api/v1/admin/workspaces
@@ -23,12 +23,14 @@ import {
FireworksIcon,
GeminiIcon,
GoogleIcon,
HunterIOIcon,
ImageIcon,
JinaAIIcon,
LinkupIcon,
MistralIcon,
OpenAIIcon,
ParallelIcon,
PeopleDataLabsIcon,
PerplexityIcon,
SerperIcon,
} from '@/components/icons'
@@ -156,6 +158,20 @@ const PROVIDERS: {
description: 'Brand assets, logos, colors, and company info',
placeholder: 'Enter your Brandfetch API key',
},
{
id: 'hunter',
name: 'Hunter',
icon: HunterIOIcon,
description: 'Email finder, verification, and domain search',
placeholder: 'Enter your Hunter.io API key',
},
{
id: 'peopledatalabs',
name: 'People Data Labs',
icon: PeopleDataLabsIcon,
description: 'Person and company enrichment, search, and identity',
placeholder: 'Enter your People Data Labs API key',
},
]
export function BYOK() {
+1
View File
@@ -256,6 +256,7 @@ Return ONLY the search query text - no explanations.`,
required: true,
placeholder: 'Enter your Hunter.io API key',
password: true,
hideWhenHosted: true,
},
],
tools: {
+1
View File
@@ -397,6 +397,7 @@ export const PeopleDataLabsBlock: BlockConfig<PdlPersonEnrichResponse> = {
placeholder: 'Enter your People Data Labs API key',
password: true,
required: true,
hideWhenHosted: true,
},
],
+2
View File
@@ -18,6 +18,8 @@ export const byokProviderIdSchema = z.enum([
'parallel_ai',
'brandfetch',
'cohere',
'hunter',
'peopledatalabs',
])
export const byokKeySchema = z.object({
+13
View File
@@ -1,4 +1,5 @@
import type { HunterEnrichmentParams, HunterEnrichmentResponse } from '@/tools/hunter/types'
import { HUNTER_API_KEY_PREFIX } from '@/tools/hunter/types'
import type { ToolConfig } from '@/tools/types'
export const companiesFindTool: ToolConfig<HunterEnrichmentParams, HunterEnrichmentResponse> = {
@@ -7,6 +8,18 @@ export const companiesFindTool: ToolConfig<HunterEnrichmentParams, HunterEnrichm
description: 'Enriches company data using domain name.',
version: '1.0.0',
hosting: {
envKeyPrefix: HUNTER_API_KEY_PREFIX,
apiKeyParam: 'apiKey',
byokProviderId: 'hunter',
// Companies Find (enrichment) is free on Hunter — no credits consumed.
pricing: { type: 'per_request', cost: 0 },
rateLimit: {
mode: 'per_request',
requestsPerMinute: 60,
},
},
params: {
domain: {
type: 'string',
+13 -1
View File
@@ -1,5 +1,5 @@
import type { HunterDiscoverParams, HunterDiscoverResponse } from '@/tools/hunter/types'
import { DISCOVER_RESULTS_OUTPUT } from '@/tools/hunter/types'
import { DISCOVER_RESULTS_OUTPUT, HUNTER_API_KEY_PREFIX } from '@/tools/hunter/types'
import type { ToolConfig } from '@/tools/types'
export const discoverTool: ToolConfig<HunterDiscoverParams, HunterDiscoverResponse> = {
@@ -8,6 +8,18 @@ export const discoverTool: ToolConfig<HunterDiscoverParams, HunterDiscoverRespon
description: 'Returns companies matching a set of criteria using Hunter.io AI-powered search.',
version: '1.0.0',
hosting: {
envKeyPrefix: HUNTER_API_KEY_PREFIX,
apiKeyParam: 'apiKey',
byokProviderId: 'hunter',
// Discover is free on Hunter — no credits consumed.
pricing: { type: 'per_request', cost: 0 },
rateLimit: {
mode: 'per_request',
requestsPerMinute: 30,
},
},
params: {
query: {
type: 'string',
+27 -1
View File
@@ -3,7 +3,11 @@ import type {
HunterDomainSearchResponse,
HunterEmail,
} from '@/tools/hunter/types'
import { EMAILS_OUTPUT } from '@/tools/hunter/types'
import {
EMAILS_OUTPUT,
HUNTER_API_KEY_PREFIX,
HUNTER_SEARCH_CREDIT_USD,
} from '@/tools/hunter/types'
import type { ToolConfig } from '@/tools/types'
export const domainSearchTool: ToolConfig<HunterDomainSearchParams, HunterDomainSearchResponse> = {
@@ -12,6 +16,28 @@ export const domainSearchTool: ToolConfig<HunterDomainSearchParams, HunterDomain
description: 'Returns all the email addresses found using one given domain name, with sources.',
version: '1.0.0',
hosting: {
envKeyPrefix: HUNTER_API_KEY_PREFIX,
apiKeyParam: 'apiKey',
byokProviderId: 'hunter',
pricing: {
type: 'custom',
getCost: (_params, output) => {
const emails = output.emails
if (!Array.isArray(emails)) {
throw new Error('Hunter domain search response missing emails, cannot determine cost')
}
// Hunter counts one search credit only when a call returns at least one result.
const cost = emails.length > 0 ? HUNTER_SEARCH_CREDIT_USD : 0
return { cost, metadata: { credits: cost > 0 ? 1 : 0, emails: emails.length } }
},
},
rateLimit: {
mode: 'per_request',
requestsPerMinute: 60,
},
},
params: {
domain: {
type: 'string',
+13 -1
View File
@@ -1,5 +1,5 @@
import type { HunterEmailCountParams, HunterEmailCountResponse } from '@/tools/hunter/types'
import { DEPARTMENT_OUTPUT, SENIORITY_OUTPUT } from '@/tools/hunter/types'
import { DEPARTMENT_OUTPUT, HUNTER_API_KEY_PREFIX, SENIORITY_OUTPUT } from '@/tools/hunter/types'
import type { ToolConfig } from '@/tools/types'
export const emailCountTool: ToolConfig<HunterEmailCountParams, HunterEmailCountResponse> = {
@@ -8,6 +8,18 @@ export const emailCountTool: ToolConfig<HunterEmailCountParams, HunterEmailCount
description: 'Returns the total number of email addresses found for a domain or company.',
version: '1.0.0',
hosting: {
envKeyPrefix: HUNTER_API_KEY_PREFIX,
apiKeyParam: 'apiKey',
byokProviderId: 'hunter',
// Email Count is free on Hunter — no credits consumed.
pricing: { type: 'per_request', cost: 0 },
rateLimit: {
mode: 'per_request',
requestsPerMinute: 60,
},
},
params: {
domain: {
type: 'string',
+25 -1
View File
@@ -1,5 +1,10 @@
import type { HunterEmailFinderParams, HunterEmailFinderResponse } from '@/tools/hunter/types'
import { SOURCES_OUTPUT, VERIFICATION_OUTPUT } from '@/tools/hunter/types'
import {
HUNTER_API_KEY_PREFIX,
HUNTER_SEARCH_CREDIT_USD,
SOURCES_OUTPUT,
VERIFICATION_OUTPUT,
} from '@/tools/hunter/types'
import type { ToolConfig } from '@/tools/types'
export const emailFinderTool: ToolConfig<HunterEmailFinderParams, HunterEmailFinderResponse> = {
@@ -9,6 +14,25 @@ export const emailFinderTool: ToolConfig<HunterEmailFinderParams, HunterEmailFin
'Finds the most likely email address for a person given their name and company domain.',
version: '1.0.0',
hosting: {
envKeyPrefix: HUNTER_API_KEY_PREFIX,
apiKeyParam: 'apiKey',
byokProviderId: 'hunter',
pricing: {
type: 'custom',
getCost: (_params, output) => {
// Hunter counts one search credit only when an email is found.
const found = typeof output.email === 'string' && output.email.length > 0
const cost = found ? HUNTER_SEARCH_CREDIT_USD : 0
return { cost, metadata: { credits: found ? 1 : 0 } }
},
},
rateLimit: {
mode: 'per_request',
requestsPerMinute: 60,
},
},
params: {
domain: {
type: 'string',
+23 -1
View File
@@ -1,5 +1,9 @@
import type { HunterEmailVerifierParams, HunterEmailVerifierResponse } from '@/tools/hunter/types'
import { SOURCES_OUTPUT } from '@/tools/hunter/types'
import {
HUNTER_API_KEY_PREFIX,
HUNTER_VERIFICATION_CREDIT_USD,
SOURCES_OUTPUT,
} from '@/tools/hunter/types'
import type { ToolConfig } from '@/tools/types'
export const emailVerifierTool: ToolConfig<HunterEmailVerifierParams, HunterEmailVerifierResponse> =
@@ -10,6 +14,24 @@ export const emailVerifierTool: ToolConfig<HunterEmailVerifierParams, HunterEmai
'Verifies the deliverability of an email address and provides detailed verification status.',
version: '1.0.0',
hosting: {
envKeyPrefix: HUNTER_API_KEY_PREFIX,
apiKeyParam: 'apiKey',
byokProviderId: 'hunter',
pricing: {
type: 'custom',
// The verifier always consumes one verification (0.5 credit), regardless of result.
getCost: () => ({
cost: HUNTER_VERIFICATION_CREDIT_USD,
metadata: { verifications: 1 },
}),
},
rateLimit: {
mode: 'per_request',
requestsPerMinute: 60,
},
},
params: {
email: {
type: 'string',
+19
View File
@@ -1,6 +1,25 @@
// Common types for Hunter.io tools
import type { OutputProperty, ToolResponse } from '@/tools/types'
/**
* Env var prefix for Sim-hosted Hunter.io keys.
* Resolved at runtime as `HUNTER_API_KEY_COUNT` + `HUNTER_API_KEY_1..N`.
*/
export const HUNTER_API_KEY_PREFIX = 'HUNTER_API_KEY'
/**
* Dollar cost per Hunter.io credit when billing hosted-key usage.
*
* Hunter does not report per-call credit consumption in its responses, so cost is
* derived from the response: a "search" credit is consumed only when a call returns
* at least one result; a verification consumes half a credit.
*
* Estimate based on the Growth plan ($149/mo for 10,000 credits ≈ $0.015/credit) —
* see https://hunter.io/pricing. Tune if Sim's plan rate changes.
*/
export const HUNTER_SEARCH_CREDIT_USD = 0.015
export const HUNTER_VERIFICATION_CREDIT_USD = 0.0075
/**
* Shared output property definitions for Hunter.io API responses.
* These are reusable across all Hunter tools to ensure consistency.
@@ -1,4 +1,5 @@
import type { PdlAutocompleteParams, PdlAutocompleteResponse } from '@/tools/peopledatalabs/types'
import { PEOPLEDATALABS_API_KEY_PREFIX } from '@/tools/peopledatalabs/types'
import { buildQueryString } from '@/tools/peopledatalabs/utils'
import type { OutputProperty, ToolConfig } from '@/tools/types'
@@ -19,6 +20,18 @@ export const autocompleteTool: ToolConfig<PdlAutocompleteParams, PdlAutocomplete
'Get autocomplete suggestions for a PDL field (title, skill, company, industry, location, school, major, role, sub_role).',
version: '1.0.0',
hosting: {
envKeyPrefix: PEOPLEDATALABS_API_KEY_PREFIX,
apiKeyParam: 'apiKey',
byokProviderId: 'peopledatalabs',
// Autocomplete is free on PDL (rate-limited only) — no credits consumed.
pricing: { type: 'per_request', cost: 0 },
rateLimit: {
mode: 'per_request',
requestsPerMinute: 60,
},
},
params: {
apiKey: {
type: 'string',
@@ -3,7 +3,8 @@ import type {
PdlBulkCompanyEnrichResponse,
PdlBulkCompanyResultItem,
} from '@/tools/peopledatalabs/types'
import { projectCompany } from '@/tools/peopledatalabs/utils'
import { PDL_CREDIT_USD, PEOPLEDATALABS_API_KEY_PREFIX } from '@/tools/peopledatalabs/types'
import { countBulkMatched, projectCompany } from '@/tools/peopledatalabs/utils'
import type { OutputProperty, ToolConfig } from '@/tools/types'
const BULK_COMPANY_RESULT_PROPERTIES = {
@@ -32,6 +33,31 @@ export const bulkCompanyEnrichTool: ToolConfig<
'Enrich up to 100 companies in a single call. Provide a JSON array of request objects, each with a `params` object. Reference: https://docs.peopledatalabs.com/docs/bulk-company-enrichment-api',
version: '1.0.0',
hosting: {
envKeyPrefix: PEOPLEDATALABS_API_KEY_PREFIX,
apiKeyParam: 'apiKey',
byokProviderId: 'peopledatalabs',
pricing: {
type: 'custom',
// PDL charges 1 credit per matched record; unmatched records in the batch are free.
getCost: (_params, output) => {
const matched = countBulkMatched(output)
return { cost: matched * PDL_CREDIT_USD, metadata: { credits: matched } }
},
},
rateLimit: {
mode: 'custom',
requestsPerMinute: 10,
dimensions: [
{
name: 'credits',
limitPerMinute: 600,
extractUsage: (_params, response) => countBulkMatched(response),
},
],
},
},
params: {
apiKey: {
type: 'string',
@@ -3,7 +3,8 @@ import type {
PdlBulkPersonEnrichResponse,
PdlBulkPersonResultItem,
} from '@/tools/peopledatalabs/types'
import { projectPerson } from '@/tools/peopledatalabs/utils'
import { PDL_CREDIT_USD, PEOPLEDATALABS_API_KEY_PREFIX } from '@/tools/peopledatalabs/types'
import { countBulkMatched, projectPerson } from '@/tools/peopledatalabs/utils'
import type { OutputProperty, ToolConfig } from '@/tools/types'
const BULK_PERSON_RESULT_PROPERTIES = {
@@ -32,6 +33,31 @@ export const bulkPersonEnrichTool: ToolConfig<
'Enrich up to 100 person records in a single call. Provide a JSON array of request objects, each with a `params` object (and optional `metadata` echoed back). Reference: https://docs.peopledatalabs.com/docs/bulk-person-enrichment-api',
version: '1.0.0',
hosting: {
envKeyPrefix: PEOPLEDATALABS_API_KEY_PREFIX,
apiKeyParam: 'apiKey',
byokProviderId: 'peopledatalabs',
pricing: {
type: 'custom',
// PDL charges 1 credit per matched record; unmatched records in the batch are free.
getCost: (_params, output) => {
const matched = countBulkMatched(output)
return { cost: matched * PDL_CREDIT_USD, metadata: { credits: matched } }
},
},
rateLimit: {
mode: 'custom',
requestsPerMinute: 10,
dimensions: [
{
name: 'credits',
limitPerMinute: 600,
extractUsage: (_params, response) => countBulkMatched(response),
},
],
},
},
params: {
apiKey: {
type: 'string',
+16 -1
View File
@@ -1,5 +1,8 @@
import type { PdlCleanCompanyParams, PdlCleanCompanyResponse } from '@/tools/peopledatalabs/types'
import { PDL_COMPANY_OUTPUT_PROPERTIES } from '@/tools/peopledatalabs/types'
import {
PDL_COMPANY_OUTPUT_PROPERTIES,
PEOPLEDATALABS_API_KEY_PREFIX,
} from '@/tools/peopledatalabs/types'
import { projectCompany } from '@/tools/peopledatalabs/utils'
import type { ToolConfig } from '@/tools/types'
@@ -10,6 +13,18 @@ export const cleanCompanyTool: ToolConfig<PdlCleanCompanyParams, PdlCleanCompany
'Normalize a company string into a canonical company record. Provide at least one of name, website, or profile (LinkedIn URL).',
version: '1.0.0',
hosting: {
envKeyPrefix: PEOPLEDATALABS_API_KEY_PREFIX,
apiKeyParam: 'apiKey',
byokProviderId: 'peopledatalabs',
// Cleaner APIs are free on PDL (rate-limited only) — no credits consumed.
pricing: { type: 'per_request', cost: 0 },
rateLimit: {
mode: 'per_request',
requestsPerMinute: 60,
},
},
params: {
apiKey: {
type: 'string',
@@ -1,5 +1,9 @@
import type { PdlCompanyEnrichParams, PdlCompanyEnrichResponse } from '@/tools/peopledatalabs/types'
import { PDL_COMPANY_OUTPUT_PROPERTIES } from '@/tools/peopledatalabs/types'
import {
PDL_COMPANY_OUTPUT_PROPERTIES,
PDL_CREDIT_USD,
PEOPLEDATALABS_API_KEY_PREFIX,
} from '@/tools/peopledatalabs/types'
import { buildQueryString, projectCompany } from '@/tools/peopledatalabs/utils'
import type { ToolConfig } from '@/tools/types'
@@ -10,6 +14,24 @@ export const companyEnrichTool: ToolConfig<PdlCompanyEnrichParams, PdlCompanyEnr
'Enrich a single company using People Data Labs. Match by name, website, LinkedIn URL, ticker, or PDL ID.',
version: '1.0.0',
hosting: {
envKeyPrefix: PEOPLEDATALABS_API_KEY_PREFIX,
apiKeyParam: 'apiKey',
byokProviderId: 'peopledatalabs',
pricing: {
type: 'custom',
// PDL charges 1 credit per matched request; misses (404) are free.
getCost: (_params, output) => {
const cost = output.matched === true ? PDL_CREDIT_USD : 0
return { cost, metadata: { credits: output.matched === true ? 1 : 0 } }
},
},
rateLimit: {
mode: 'per_request',
requestsPerMinute: 60,
},
},
params: {
apiKey: {
type: 'string',
@@ -1,5 +1,9 @@
import type { PdlCompanySearchParams, PdlCompanySearchResponse } from '@/tools/peopledatalabs/types'
import { PDL_COMPANY_OUTPUT_PROPERTIES } from '@/tools/peopledatalabs/types'
import {
PDL_COMPANY_OUTPUT_PROPERTIES,
PDL_CREDIT_USD,
PEOPLEDATALABS_API_KEY_PREFIX,
} from '@/tools/peopledatalabs/types'
import { projectCompany } from '@/tools/peopledatalabs/utils'
import type { ToolConfig } from '@/tools/types'
@@ -10,6 +14,35 @@ export const companySearchTool: ToolConfig<PdlCompanySearchParams, PdlCompanySea
'Search the People Data Labs company dataset using SQL or Elasticsearch DSL. Returns up to 100 matching companies per call.',
version: '1.0.0',
hosting: {
envKeyPrefix: PEOPLEDATALABS_API_KEY_PREFIX,
apiKeyParam: 'apiKey',
byokProviderId: 'peopledatalabs',
pricing: {
type: 'custom',
// PDL Search charges 1 credit per company returned (up to the requested size).
getCost: (_params, output) => {
const results = output.results
if (!Array.isArray(results)) {
throw new Error('PDL company search response missing results, cannot determine cost')
}
return { cost: results.length * PDL_CREDIT_USD, metadata: { credits: results.length } }
},
},
rateLimit: {
mode: 'custom',
requestsPerMinute: 30,
dimensions: [
{
name: 'credits',
limitPerMinute: 600,
extractUsage: (_params, response) =>
Array.isArray(response.results) ? response.results.length : 0,
},
],
},
},
params: {
apiKey: {
type: 'string',
@@ -1,5 +1,8 @@
import type { PdlCleanLocationParams, PdlCleanLocationResponse } from '@/tools/peopledatalabs/types'
import { PDL_LOCATION_OUTPUT_PROPERTIES } from '@/tools/peopledatalabs/types'
import {
PDL_LOCATION_OUTPUT_PROPERTIES,
PEOPLEDATALABS_API_KEY_PREFIX,
} from '@/tools/peopledatalabs/types'
import { projectLocation } from '@/tools/peopledatalabs/utils'
import type { ToolConfig } from '@/tools/types'
@@ -10,6 +13,18 @@ export const cleanLocationTool: ToolConfig<PdlCleanLocationParams, PdlCleanLocat
'Normalize a freeform location string into a structured locality/region/country record with coordinates.',
version: '1.0.0',
hosting: {
envKeyPrefix: PEOPLEDATALABS_API_KEY_PREFIX,
apiKeyParam: 'apiKey',
byokProviderId: 'peopledatalabs',
// Cleaner APIs are free on PDL (rate-limited only) — no credits consumed.
pricing: { type: 'per_request', cost: 0 },
rateLimit: {
mode: 'per_request',
requestsPerMinute: 60,
},
},
params: {
apiKey: {
type: 'string',
+23 -1
View File
@@ -1,5 +1,9 @@
import type { PdlPersonEnrichParams, PdlPersonEnrichResponse } from '@/tools/peopledatalabs/types'
import { PDL_PERSON_OUTPUT_PROPERTIES } from '@/tools/peopledatalabs/types'
import {
PDL_CREDIT_USD,
PDL_PERSON_OUTPUT_PROPERTIES,
PEOPLEDATALABS_API_KEY_PREFIX,
} from '@/tools/peopledatalabs/types'
import { buildQueryString, projectPerson } from '@/tools/peopledatalabs/utils'
import type { ToolConfig } from '@/tools/types'
@@ -10,6 +14,24 @@ export const personEnrichTool: ToolConfig<PdlPersonEnrichParams, PdlPersonEnrich
'Enrich a single person profile using People Data Labs. Match by email, phone, LinkedIn URL, or name + company/location. Returns work history, contact details, location, and skills.',
version: '1.0.0',
hosting: {
envKeyPrefix: PEOPLEDATALABS_API_KEY_PREFIX,
apiKeyParam: 'apiKey',
byokProviderId: 'peopledatalabs',
pricing: {
type: 'custom',
// PDL charges 1 credit per matched request; misses (404) are free.
getCost: (_params, output) => {
const cost = output.matched === true ? PDL_CREDIT_USD : 0
return { cost, metadata: { credits: output.matched === true ? 1 : 0 } }
},
},
rateLimit: {
mode: 'per_request',
requestsPerMinute: 60,
},
},
params: {
apiKey: {
type: 'string',
@@ -2,7 +2,11 @@ import type {
PdlPersonIdentifyParams,
PdlPersonIdentifyResponse,
} from '@/tools/peopledatalabs/types'
import { PDL_PERSON_OUTPUT_PROPERTIES } from '@/tools/peopledatalabs/types'
import {
PDL_CREDIT_USD,
PDL_PERSON_OUTPUT_PROPERTIES,
PEOPLEDATALABS_API_KEY_PREFIX,
} from '@/tools/peopledatalabs/types'
import { buildQueryString, projectPerson } from '@/tools/peopledatalabs/utils'
import type { OutputProperty, ToolConfig } from '@/tools/types'
@@ -28,6 +32,28 @@ export const personIdentifyTool: ToolConfig<PdlPersonIdentifyParams, PdlPersonId
'Return up to 20 candidate person matches with confidence scores. Useful when you want to see all plausible matches rather than the single best one. Reference: https://docs.peopledatalabs.com/docs/identify-api-quickstart',
version: '1.0.0',
hosting: {
envKeyPrefix: PEOPLEDATALABS_API_KEY_PREFIX,
apiKeyParam: 'apiKey',
byokProviderId: 'peopledatalabs',
pricing: {
type: 'custom',
// PDL Identify charges 1 credit per call that returns matches, regardless of count.
getCost: (_params, output) => {
const matches = output.matches
if (!Array.isArray(matches)) {
throw new Error('PDL identify response missing matches, cannot determine cost')
}
const cost = matches.length > 0 ? PDL_CREDIT_USD : 0
return { cost, metadata: { credits: matches.length > 0 ? 1 : 0 } }
},
},
rateLimit: {
mode: 'per_request',
requestsPerMinute: 60,
},
},
params: {
apiKey: {
type: 'string',
+34 -1
View File
@@ -1,5 +1,9 @@
import type { PdlPersonSearchParams, PdlPersonSearchResponse } from '@/tools/peopledatalabs/types'
import { PDL_PERSON_OUTPUT_PROPERTIES } from '@/tools/peopledatalabs/types'
import {
PDL_CREDIT_USD,
PDL_PERSON_OUTPUT_PROPERTIES,
PEOPLEDATALABS_API_KEY_PREFIX,
} from '@/tools/peopledatalabs/types'
import { projectPerson } from '@/tools/peopledatalabs/utils'
import type { ToolConfig } from '@/tools/types'
@@ -10,6 +14,35 @@ export const personSearchTool: ToolConfig<PdlPersonSearchParams, PdlPersonSearch
'Search the People Data Labs person dataset using SQL or Elasticsearch DSL. Returns up to 100 matching records per call.',
version: '1.0.0',
hosting: {
envKeyPrefix: PEOPLEDATALABS_API_KEY_PREFIX,
apiKeyParam: 'apiKey',
byokProviderId: 'peopledatalabs',
pricing: {
type: 'custom',
// PDL Search charges 1 credit per profile returned (up to the requested size).
getCost: (_params, output) => {
const results = output.results
if (!Array.isArray(results)) {
throw new Error('PDL person search response missing results, cannot determine cost')
}
return { cost: results.length * PDL_CREDIT_USD, metadata: { credits: results.length } }
},
},
rateLimit: {
mode: 'custom',
requestsPerMinute: 30,
dimensions: [
{
name: 'credits',
limitPerMinute: 600,
extractUsage: (_params, response) =>
Array.isArray(response.results) ? response.results.length : 0,
},
],
},
},
params: {
apiKey: {
type: 'string',
+16 -1
View File
@@ -1,5 +1,8 @@
import type { PdlCleanSchoolParams, PdlCleanSchoolResponse } from '@/tools/peopledatalabs/types'
import { PDL_SCHOOL_OUTPUT_PROPERTIES } from '@/tools/peopledatalabs/types'
import {
PDL_SCHOOL_OUTPUT_PROPERTIES,
PEOPLEDATALABS_API_KEY_PREFIX,
} from '@/tools/peopledatalabs/types'
import { projectSchool } from '@/tools/peopledatalabs/utils'
import type { ToolConfig } from '@/tools/types'
@@ -10,6 +13,18 @@ export const cleanSchoolTool: ToolConfig<PdlCleanSchoolParams, PdlCleanSchoolRes
'Normalize a school string into a canonical school record. Provide at least one of name, website, or profile (LinkedIn URL).',
version: '1.0.0',
hosting: {
envKeyPrefix: PEOPLEDATALABS_API_KEY_PREFIX,
apiKeyParam: 'apiKey',
byokProviderId: 'peopledatalabs',
// Cleaner APIs are free on PDL (rate-limited only) — no credits consumed.
pricing: { type: 'per_request', cost: 0 },
rateLimit: {
mode: 'per_request',
requestsPerMinute: 60,
},
},
params: {
apiKey: {
type: 'string',
+19
View File
@@ -1,5 +1,24 @@
import type { OutputProperty, ToolResponse } from '@/tools/types'
/**
* Env var prefix for Sim-hosted People Data Labs keys.
* Resolved at runtime as `PEOPLEDATALABS_API_KEY_COUNT` + `PEOPLEDATALABS_API_KEY_1..N`.
*/
export const PEOPLEDATALABS_API_KEY_PREFIX = 'PEOPLEDATALABS_API_KEY'
/**
* Dollar cost per People Data Labs credit when billing hosted-key usage.
*
* PDL does not report credits in its responses, so cost is derived from match counts:
* enrich/identify consume 1 credit per matched request, search consumes 1 credit per
* profile returned, and bulk consumes 1 credit per matched record. Misses (HTTP 404)
* are not charged. Cleaner and autocomplete endpoints are free (rate-limited only).
*
* Estimate based on the self-serve Pro plan (~$0.28/credit) — see
* https://www.peopledatalabs.com/pricing. Tune if Sim's plan rate changes.
*/
export const PDL_CREDIT_USD = 0.28
/**
* Output property definitions for People Data Labs API responses.
* Reference: https://docs.peopledatalabs.com/docs
+14
View File
@@ -5,6 +5,20 @@ import type {
PdlSchoolRecord,
} from '@/tools/peopledatalabs/types'
/**
* Count matched records in a bulk enrich output `results` array.
* PDL charges one credit per matched record, so this drives both hosted-key
* cost and the post-execution credit rate-limit dimension.
*/
export function countBulkMatched(output: Record<string, unknown>): number {
const results = output.results
if (!Array.isArray(results)) return 0
return results.reduce((count, item) => {
const matched = (item as { matched?: unknown }).matched
return matched === true ? count + 1 : count
}, 0)
}
/**
* Build a query string from non-empty params.
*/
+2
View File
@@ -19,6 +19,8 @@ export type BYOKProviderId =
| 'brandfetch'
| 'parallel_ai'
| 'cohere'
| 'hunter'
| 'peopledatalabs'
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD'