Skip to main content

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.

tip

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.com lookup 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

ProviderList rate per lookupNotes
apollo$0.005Cheapest; prioritised first in the chain. Strong on org-charts.
peopledatalabs$0.01Bulk-friendly, deepest funding/firmographic history.
clearbit$0.05Most-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

ParameterTypeRequiredDescription
providerstringYesOne of apollo, peopledatalabs, clearbit.
api_keystringYesVendor key. 8–512 chars.
labelstringNoFree-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 FieldDescription
labelRename.
is_activeToggle active.
api_keyProvide to rotate. Omit to keep existing.
revalidateSet 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

ParameterTypeRequiredDescription
domainstringYesCompany domain (stripe.com). Protocol/path stripped automatically.
force_refreshbooleanNoSkip 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

FieldDescription
name / legal_nameDisplay name + legal entity (when vendor supplies one).
founded_yearYear only — most vendors don't supply month/day.
employee_countExact when vendor reports it; null when only a range is available.
employee_rangeBucket like "51-200". Always present alongside employee_count.
total_funding_usdSum of disclosed funding rounds.
latest_funding_stageseries_a, series_b, seed, ipo, etc. Vendor-specific naming.
technologiesArray of detected tech stack components.
cost_estimate_usdList-rate cost of the call. 0.0 on cache hit.
_cache_hittrue when served from cache.
rawVendor-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:

  1. Connect at least one enricher key (POST above).
  2. Pre-enrich the domains in your sequence via POST /v1/contact-enrichers/enrich for 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.
  3. 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=true on 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.