Contact Enrichers (BYOK)
CronDB integrates with three contact-data vendors out of the box. Connect your own keys and the engine pulls real firmographics — employee count, founded year, total funding, tech stack, HQ, social handles — for any domain in your pipeline. Same shape as Email Verifiers: cheapest-first fallback chain, vendor-agnostic canonical result, per-user-per-domain cache to avoid re-paying for the same lookup.
Native CronDB enrichment already provides industry, summary, country, business type, and tech-stack-from-website-scraping. The third-party vendors here add hard data — exact employee counts, real funding totals, LinkedIn-sourced HQ, etc. — that closes {{employee_count}}, {{founded_year}}, and {{total_funding_usd}} template tokens in sequences.
Why multi-vendor (and why per-user cache)
The same arguments as Email Verifiers apply: choice, resilience, no vendor lock-in. Two extra notes specific to contact enrichment:
- Vendors complement each other. Clearbit has the broadest field set; PDL has the deepest funding history; Apollo has the most accurate org charts. Connecting two means you fall through to a complementary dataset on the second call.
- Cache is per-user, not canonical. Because keys are BYOK and each user pays their vendor directly, a cached result for user A's
stripe.comlookup is not made available to user B — that would let B free-ride on A's contracted rate. Each user maintains their own 30-day cache.
Supported vendors
| Provider | List rate per lookup | Notes |
|---|---|---|
apollo | $0.005 | Cheapest; prioritised first in the chain. Strong on org-charts. |
peopledatalabs | $0.01 | Bulk-friendly, deepest funding/firmographic history. |
clearbit | $0.05 | Most-quoted brand; broadest field set. Now operated by HubSpot. |
Add an Enricher Key
Validates by hitting the vendor's health-check endpoint before saving. Invalid keys never persist.
Endpoint
POST /v1/contact-enrichers/providers
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
provider | string | Yes | One of apollo, peopledatalabs, clearbit. |
api_key | string | Yes | Vendor key. 8–512 chars. |
label | string | No | Free-text label. |
Example Request
curl -X POST \
-H "Authorization: Bearer cdb_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"provider": "peopledatalabs",
"api_key": "pdl_xxxxxxxxxxxxxxxx",
"label": "main"
}' \
"https://api.crondb.com/v1/contact-enrichers/providers"
Response
{
"key": {
"id": 3,
"provider": "peopledatalabs",
"label": "main",
"is_active": true,
"key_preview": "pdl_…xxxx",
"credits_remaining": null,
"last_validated_at": "2026-05-05T08:57:00Z",
"created_at": "2026-05-05T08:57:00Z",
"updated_at": "2026-05-05T08:57:00Z"
},
"validation": {
"provider": "peopledatalabs",
"credits_remaining": null
}
}
credits_remaining is null for vendors that don't expose a credits endpoint — the validation succeeded if no error was raised.
List Keys
GET /v1/contact-enrichers/providers
Fallback chain
When you have multiple active keys, the engine tries them in this order: apollo → peopledatalabs → clearbit. Cheapest first; the next is tried only when the previous returns a transient error (network, 429, 5xx). Set is_active=false to take a key out of rotation without deleting it.
Update / Rotate
PUT /v1/contact-enrichers/providers/{key_id}
| Body Field | Description |
|---|---|
label | Rename. |
is_active | Toggle active. |
api_key | Provide to rotate. Omit to keep existing. |
revalidate | Set true to re-run the health check. |
Delete
DELETE /v1/contact-enrichers/providers/{key_id}
Enrich a Domain
Pull fresh enrichment through the BYOK chain. Cache hit short-circuits to the cached payload.
Endpoint
POST /v1/contact-enrichers/enrich
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
domain | string | Yes | Company domain (stripe.com). Protocol/path stripped automatically. |
force_refresh | boolean | No | Skip the 30-day cache and re-fetch. Default false. |
Example Request
curl -X POST \
-H "Authorization: Bearer cdb_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{"domain": "stripe.com"}' \
"https://api.crondb.com/v1/contact-enrichers/enrich"
Canonical Response Shape
Every vendor adapter normalises into this shape. Vendor-specific extras land in raw.
{
"domain": "stripe.com",
"provider": "peopledatalabs",
"name": "Stripe",
"legal_name": "Stripe, Inc.",
"description": "Online payment processing for internet businesses.",
"founded_year": 2010,
"employee_count": 8000,
"employee_range": "5001-10000",
"annual_revenue_usd": null,
"total_funding_usd": 8200000000,
"latest_funding_stage": "series_i",
"industry": "Financial Services",
"subindustry": null,
"hq_country": "United States",
"hq_city": "South San Francisco",
"linkedin_url": "https://linkedin.com/company/stripe",
"twitter_handle": "stripe",
"technologies": ["Cloudflare", "Stripe", "Google Analytics", "..."],
"latency_ms": 1284,
"cost_estimate_usd": 0.01,
"raw": { "...": "vendor-native response" },
"_cache_hit": false
}
Response Fields
| Field | Description |
|---|---|
name / legal_name | Display name + legal entity (when vendor supplies one). |
founded_year | Year only — most vendors don't supply month/day. |
employee_count | Exact when vendor reports it; null when only a range is available. |
employee_range | Bucket like "51-200". Always present alongside employee_count. |
total_funding_usd | Sum of disclosed funding rounds. |
latest_funding_stage | series_a, series_b, seed, ipo, etc. Vendor-specific naming. |
technologies | Array of detected tech stack components. |
cost_estimate_usd | List-rate cost of the call. 0.0 on cache hit. |
_cache_hit | true when served from cache. |
raw | Vendor-native JSON for fields not in the canonical set. |
null values mean "vendor didn't return this field" — different vendors populate different fields.
Returns 400 when no enricher keys are configured; 502 when every active vendor returned an error.
Bulk Enrichment
Enrich many domains in parallel. Chunks of 5 concurrent vendor calls — contact enrichers have stricter per-second rate limits than email verifiers.
Endpoint
POST /v1/contact-enrichers/enrich-bulk
Body
{
"domains": ["stripe.com", "globex.com", "acme.com"],
"force_refresh": false
}
domains accepts 1-200 entries. force_refresh: true bypasses the 30-day cache.
Response
{
"count": 3,
"results": [
{
"domain": "stripe.com",
"provider": "peopledatalabs",
"name": "Stripe",
"employee_count": 8000,
"...": "...",
"_cache_hit": true
},
{
"domain": "globex.com",
"error": "All configured contact enrichers failed: peopledatalabs: HTTP 429"
}
]
}
For a 1000-domain pipeline, expect ~3-5 minutes wall-clock uncached (200 chunks × ~1.5s each via Apollo). Pre-running this overnight before sending campaigns is the recommended flow — keeps the sequence worker hot path untouched and gives you predictable cost.
Cache Inspection
Read cached entry
GET /v1/contact-enrichers/cache/{domain}
Returns the freshest non-expired cache row (across any vendor) for (user, domain) without making a vendor call. 404 when nothing's cached.
{
"domain": "stripe.com",
"provider": "peopledatalabs",
"name": "Stripe",
"...": "...",
"_cache_hit": true,
"_cached_at": "2026-04-15T10:00:00Z",
"_expires_at": "2026-05-15T10:00:00Z"
}
Evict a domain
DELETE /v1/contact-enrichers/cache/{domain}
Removes every cached row across providers for (user, domain). Next enrich call hits a fresh vendor. Returns {evicted: int, domain: string}.
There is no global cache-flush endpoint — eviction is per-domain to avoid accidentally re-paying the entire pipeline.
Sequence Template Integration
The sequence worker reads cached enrichment at send time and exposes its fields as template tokens. To populate {{employee_count}}, {{founded_year}}, {{total_funding_usd}}, etc. in your step body:
- Connect at least one enricher key (POST above).
- Pre-enrich the domains in your sequence via
POST /v1/contact-enrichers/enrichfor each. Bulk loop a CSV through this endpoint before activating the sequence — the worker is cache-only at send time to avoid blocking the send pipeline on a 1-3 second vendor call. - Activate + enroll. The worker pulls the canonical payload from the cache and substitutes tokens.
Tokens added to the template scope
{{name}} # Stripe
{{legal_name}} # Stripe, Inc.
{{description}} # Online payment processing for ...
{{founded_year}} # 2010
{{employee_count}} # 8000
{{employee_range}} # 5001-10000
{{annual_revenue_usd}} # null when unknown
{{total_funding_usd}} # 8200000000
{{latest_funding_stage}} # series_i
{{industry}} # Financial Services
{{hq_country}} # United States
{{hq_city}} # South San Francisco
{{linkedin_url}} # https://linkedin.com/company/stripe
{{twitter_handle}} # stripe
{{technologies}} # comma-joined string of top 20
{{technologies_list}} # raw array (use in workflow conditions)
{{company_size_estimate}} # alias for employee_range || employee_count
When a token has no value (cache miss or vendor returned null), the renderer leaves the literal {{token}} in place — same fail-mode as the existing template engine. Pre-enriching covers the common case; for the long tail, design templates that don't fall over without these fields.
Why cache-only at send time?
The sequence worker processes thousands of sends per minute. Adding a 1-3-second vendor call per send would crater throughput and leave sends queueing. The trade-off — pre-enrich first, send second — keeps the hot path fast and gives the user explicit control over when vendor credits are spent.
Public Helper
GET /v1/contact-enrichers/known-providers
No-auth helper for UI dropdowns. Returns the list of supported vendors and per-lookup list rates.
Usage & Cost
GET /v1/contact-enrichers/usage?days=30
{
"days": 30,
"since": "2026-04-05T08:57:00Z",
"total_calls": 412,
"total_cost_usd": 1.940,
"cache_hits": 1842,
"cache_hit_rate_pct": 81.7,
"by_provider": [
{ "provider": "apollo", "calls": 287, "cost_usd": 0.860 },
{ "provider": "peopledatalabs", "calls": 122, "cost_usd": 1.080 },
{ "provider": "clearbit", "calls": 3, "cost_usd": 0.000 }
],
"by_action_type": [
{ "action_type": "manual", "calls": 296, "cost_usd": 1.624 },
{ "action_type": "sequence_enrollment", "calls": 116, "cost_usd": 0.316 }
]
}
cache_hits count the lookups served from cache without spending credits — cache_hit_rate_pct of 80%+ is normal once a user has been enriching for a few weeks. Watch this number to confirm the cache is doing its job.
Notes
- Encrypted at rest via the same Fernet/SECRET_KEY path as every other BYOK key.
- Cache TTL is 30 days. For domains where company data shifts faster (post-IPO, fresh funding round), pass
force_refresh=trueon the enrich call to bypass the cache. - Vendor latency: Apollo is fastest (~600ms typical); PDL ~800-1500ms; Clearbit 1-3s. Pre-enriching a 1000-row pipeline overnight via the API + Apollo costs about $5 and takes ~10 minutes.
- "Not found" responses (vendor doesn't index the domain) are not cached — the next call retries fresh, accepting that the same domain may turn up tomorrow.
- The Clearbit Discovery API and the Reveal API are separate products from the basic Companies API used here. This integration uses the Companies API.
Next Steps
- Email Verifiers — Same BYOK shape for verifying recipient deliverability.
- Sequences — Where the enrichment fields surface as template tokens.
- AI Providers — BYOK shape for LLM provider keys.