This guide provides step-by-step instructions for migrating from Clearbit's Combined API (Person + Company) to Tomba's /combined/find endpoint.
API Comparison
Feature Clearbit Combined API Tomba Combined API Endpoint person.clearbit.com/v2/combined/findapi.tomba.io/v1/combined/findInput Email Email Authentication Bearer token API Key + Secret headers Rate Limit 600 req/min 300 req/min Person Data ✅ ✅ Enhanced Company Data ✅ ✅ Enhanced Email Verification ❌ ✅ FREE Phone Numbers Limited ✅ Validated Technology Stack Basic ✅ 6000+ technologies Similar Companies ❌ ✅ API Calls 1 1 (same efficiency) Cost $999/10k $119/10k (88% savings) Free Trial 50 calls 50 searches + 500 verifications
Endpoint Migration
Clearbit Combined API
GET https://person.clearbit.com/v2/combined/find?email=john@stripe.com
Authorization: Bearer sk_clearbit_xxx
Tomba Combined API
GET https://api.tomba.io/v1/combined/find?email=john@stripe.com
X-Tomba-Key: your-api-key
X-Tomba-Secret: your-secret-key
Why Use Combined API?
The Combined API returns both person and company data in a single request, giving you:
✅ Reduced API calls - 1 call instead of 2
✅ Lower latency - Get both datasets instantly
✅ Cost savings - Pay for 1 request, not 2
✅ Simplified code - One endpoint, one response
Response Format Comparison
Clearbit Response
{
"person" : {
"id" : "d54c54ad-40be-4305-8a34-0ab44710b90d" ,
"name" : {
"fullName" : "John Doe" ,
"givenName" : "John" ,
"familyName" : "Doe"
},
"email" : "john@stripe.com" ,
"location" : "San Francisco, CA, US" ,
"employment" : {
"domain" : "stripe.com" ,
"name" : "Stripe" ,
"title" : "Senior Software Engineer" ,
"role" : "engineering" ,
"seniority" : "senior"
},
"linkedin" : {
"handle" : "in/johndoe"
},
"twitter" : {
"handle" : "johndoe" ,
"followers" : 1000
}
},
"company" : {
"id" : "027b0d40-016c-40ea-8925-a076fa640992" ,
"name" : "Stripe" ,
"domain" : "stripe.com" ,
"description" : "Online payment processing for internet businesses" ,
"location" : "San Francisco, CA, US" ,
"logo" : "https://logo.clearbit.com/stripe.com" ,
"metrics" : {
"employees" : 7000 ,
"employeesRange" : "1001-5000" ,
"estimatedAnnualRevenue" : "$1B-$10B"
},
"category" : {
"industry" : "Financial Services"
}
}
}
Tomba Response
{
"data" : {
"person" : {
"email" : "john@stripe.com" ,
"first_name" : "John" ,
"last_name" : "Doe" ,
"full_name" : "John Doe" ,
"gender" : null ,
"phone_number" : "+1-415-555-0123" ,
"position" : "Senior Software Engineer" ,
"department" : "Engineering" ,
"seniority" : "senior" ,
"twitter" : "johndoe" ,
"linkedin" : "https://www.linkedin.com/in/johndoe" ,
"location" : {
"city" : "San Francisco" ,
"state" : "California" ,
"country" : "United States" ,
"country_code" : "US"
},
"verification" : {
"status" : "valid" ,
"result" : "deliverable" ,
"score" : 98 ,
"smtp_check" : true ,
"disposable" : false
}
},
"company" : {
"organization" : "Stripe" ,
"domain" : "stripe.com" ,
"website" : "https://stripe.com" ,
"email_count" : 347 ,
"phone_number" : "+1-888-926-2289" ,
"country" : "US" ,
"state" : "California" ,
"city" : "San Francisco" ,
"postal_code" : "94103" ,
"street" : "510 Townsend Street" ,
"revenue" : "$1B-$10B" ,
"employees" : "1001-5000" ,
"founded" : 2010 ,
"industry" : "Financial Services" ,
"description" : "Online payment processing for internet businesses" ,
"logo" : "https://d12h6n0d1mbsct.cloudfront.net/logos/stripe-com.jpg" ,
"twitter" : "stripe" ,
"linkedin" : "stripe" ,
"facebook" : "stripe" ,
"technologies" : [
{
"name" : "Stripe" ,
"category" : "Payment Processing"
},
{
"name" : "AWS" ,
"category" : "Cloud Infrastructure"
}
],
"similar_domains" : [ "square.com" , "paypal.com" , "adyen.com" ]
}
}
}
Field Mapping
Person Fields
Clearbit Field Tomba Field Notes person.name.fullNameperson.full_nameFull name person.name.givenNameperson.first_nameFirst name person.name.familyNameperson.last_nameLast name person.emailperson.emailEmail address person.employment.titleperson.positionJob title person.employment.roleperson.departmentDepartment person.employment.seniorityperson.senioritySeniority person.locationperson.location.*Location object person.linkedin.handleperson.linkedinLinkedIn URL person.twitter.handleperson.twitterTwitter handle N/A person.phone_numberNew: Phone number N/A person.verificationNew: Email verification
Company Fields
Clearbit Field Tomba Field Notes company.namecompany.organizationCompany name company.domaincompany.domainDomain company.descriptioncompany.descriptionDescription company.locationcompany.city, company.state, company.countryLocation fields company.logocompany.logoLogo URL company.metrics.employeescompany.employeesEmployee count company.metrics.estimatedAnnualRevenuecompany.revenueRevenue estimate company.category.industrycompany.industryIndustry N/A company.email_countNew: Available emails N/A company.phone_numberNew: Company phone N/A company.technologiesNew: Tech stack N/A company.similar_domainsNew: Competitors
Code Examples
Node.js / JavaScript
Before (Clearbit):
const clearbit = require ( "clearbit" )( "sk_clearbit_xxx" );
async function getCombined ( email ) {
try {
const combined = await clearbit.Combined. find ({ email });
return {
person: {
name: combined.person?.name?.fullName,
position: combined.person?.employment?.title,
location: combined.person?.location,
},
company: {
name: combined.company?.name,
industry: combined.company?.category?.industry,
employees: combined.company?.metrics?.employees,
},
};
} catch (error) {
console. error ( "Clearbit error:" , error);
throw error;
}
}
After (Tomba):
const axios = require ( "axios" );
async function getCombined ( email ) {
try {
const response = await axios. get (
"https://api.tomba.io/v1/combined/find" ,
{
params: { email },
headers: {
"X-Tomba-Key" : process.env. TOMBA_API_KEY ,
"X-Tomba-Secret" : process.env. TOMBA_API_SECRET ,
},
},
);
const data = response.data.data;
return {
person: {
name: data.person.full_name,
position: data.person.position,
location: `${ data . person . location ?. city }, ${ data . person . location ?. country }` ,
// Bonus fields
phone: data.person.phone_number,
emailValid: data.person.verification?.status === "valid" ,
emailScore: data.person.verification?.score,
},
company: {
name: data.company.organization,
industry: data.company.industry,
employees: data.company.employees,
// Bonus fields
revenue: data.company.revenue,
emailCount: data.company.email_count,
technologies: data.company.technologies?. map (( t ) => t.name),
competitors: data.company.similar_domains,
},
};
} catch (error) {
console. error ( "Tomba error:" , error);
throw error;
}
}
// Usage
getCombined ( "john@stripe.com" ). then (console.log);
Python
Before (Clearbit):
import clearbit
clearbit.key = 'sk_clearbit_xxx'
def get_combined (email):
try :
combined = clearbit.Combined.find( email = email)
return {
'person' : {
'name' : combined.get( 'person' , {}).get( 'name' , {}).get( 'fullName' ),
'position' : combined.get( 'person' , {}).get( 'employment' , {}).get( 'title' ),
'location' : combined.get( 'person' , {}).get( 'location' )
},
'company' : {
'name' : combined.get( 'company' , {}).get( 'name' ),
'industry' : combined.get( 'company' , {}).get( 'category' , {}).get( 'industry' ),
'employees' : combined.get( 'company' , {}).get( 'metrics' , {}).get( 'employees' )
}
}
except clearbit.errors.NotFoundError:
return None
After (Tomba):
import requests
import os
def get_combined (email):
try :
response = requests.get(
'https://api.tomba.io/v1/combined/find' ,
params = { 'email' : email},
headers = {
'X-Tomba-Key' : os.getenv( 'TOMBA_API_KEY' ),
'X-Tomba-Secret' : os.getenv( 'TOMBA_API_SECRET' )
}
)
response.raise_for_status()
data = response.json()[ 'data' ]
return {
'person' : {
'name' : data[ 'person' ].get( 'full_name' ),
'position' : data[ 'person' ].get( 'position' ),
'location' : f " { data[ 'person' ].get( 'location' , {}).get( 'city' ) } , { data[ 'person' ].get( 'location' , {}).get( 'country' ) } " ,
# Bonus fields
'phone' : data[ 'person' ].get( 'phone_number' ),
'email_valid' : data[ 'person' ].get( 'verification' , {}).get( 'status' ) == 'valid' ,
'email_score' : data[ 'person' ].get( 'verification' , {}).get( 'score' )
},
'company' : {
'name' : data[ 'company' ].get( 'organization' ),
'industry' : data[ 'company' ].get( 'industry' ),
'employees' : data[ 'company' ].get( 'employees' ),
# Bonus fields
'revenue' : data[ 'company' ].get( 'revenue' ),
'email_count' : data[ 'company' ].get( 'email_count' ),
'technologies' : [t[ 'name' ] for t in data[ 'company' ].get( 'technologies' , [])],
'competitors' : data[ 'company' ].get( 'similar_domains' , [])
}
}
except requests.exceptions.HTTPError as e:
if e.response.status_code == 404 :
return None
raise
# Usage
combined = get_combined( 'john@stripe.com' )
print (combined)
PHP
Before (Clearbit):
<? php
require 'vendor/autoload.php' ;
use Clearbit\Clearbit ;
Clearbit :: setApiKey ( 'sk_clearbit_xxx' );
function getCombined ($email) {
try {
$combined = Clearbit :: combined () -> find ([ 'email' => $email]);
return [
'person' => [
'name' => $combined -> person -> name -> fullName ?? null ,
'position' => $combined -> person -> employment -> title ?? null ,
'location' => $combined -> person -> location ?? null
],
'company' => [
'name' => $combined -> company -> name ?? null ,
'industry' => $combined -> company -> category -> industry ?? null ,
'employees' => $combined -> company -> metrics -> employees ?? null
]
];
} catch ( \Clearbit\Errors\NotFoundError $e) {
return null ;
}
}
After (Tomba):
<? php
require 'vendor/autoload.php' ;
use GuzzleHttp\Client ;
function getCombined ($email) {
$client = new Client ();
try {
$response = $client -> get ( 'https://api.tomba.io/v1/combined/find' , [
'query' => [ 'email' => $email],
'headers' => [
'X-Tomba-Key' => getenv ( 'TOMBA_API_KEY' ),
'X-Tomba-Secret' => getenv ( 'TOMBA_API_SECRET' )
]
]);
$data = json_decode ($response -> getBody ()) -> data;
return [
'person' => [
'name' => $data -> person -> full_name ?? null ,
'position' => $data -> person -> position ?? null ,
'location' => "{ $data -> person -> location -> city }, { $data -> person -> location -> country }" ,
// Bonus fields
'phone' => $data -> person -> phone_number ?? null ,
'email_valid' => ($data -> person -> verification -> status ?? '' ) === 'valid' ,
'email_score' => $data -> person -> verification -> score ?? 0
],
'company' => [
'name' => $data -> company -> organization ?? null ,
'industry' => $data -> company -> industry ?? null ,
'employees' => $data -> company -> employees ?? null ,
// Bonus fields
'revenue' => $data -> company -> revenue ?? null ,
'email_count' => $data -> company -> email_count ?? 0 ,
'technologies' => array_map ( fn ($t) => $t -> name, $data -> company -> technologies ?? []),
'competitors' => $data -> company -> similar_domains ?? []
]
];
} catch ( \GuzzleHttp\Exception\ClientException $e) {
if ($e -> getResponse () -> getStatusCode () === 404 ) {
return null ;
}
throw $e;
}
}
// Usage
$combined = getCombined ( 'john@stripe.com' );
print_r ($combined);
Use Cases
Sales Intelligence
Get everything you need about a prospect in one call:
const prospect = await getCombined ( "decision.maker@target-company.com" );
console. log ( "Prospect Profile:" );
console. log ( `Name: ${ prospect . person . name }` );
console. log ( `Position: ${ prospect . person . position }` );
console. log ( `Phone: ${ prospect . person . phone }` );
console. log ( `Email Valid: ${ prospect . person . emailValid ? "Yes" : "No"}` );
console. log ( " \n Company Profile:" );
console. log ( `Company: ${ prospect . company . name }` );
console. log ( `Industry: ${ prospect . company . industry }` );
console. log ( `Size: ${ prospect . company . employees } employees` );
console. log ( `Revenue: ${ prospect . company . revenue }` );
console. log ( `Technologies: ${ prospect . company . technologies . join ( ", " ) }` );
console. log ( `Competitors: ${ prospect . company . competitors . join ( ", " ) }` );
Lead Enrichment
Enrich leads in your CRM:
async function enrichLead ( email ) {
const data = await getCombined (email);
if ( ! data) {
return { status: "not_found" };
}
return {
status: "enriched" ,
contact: {
first_name: data.person.name. split ( " " )[ 0 ],
last_name: data.person.name. split ( " " ). slice ( 1 ). join ( " " ),
title: data.person.position,
phone: data.person.phone,
email_verified: data.person.emailValid,
linkedin_url: data.person.linkedin,
},
company: {
name: data.company.name,
domain: data.company.domain,
industry: data.company.industry,
employee_count: data.company.employees,
annual_revenue: data.company.revenue,
tech_stack: data.company.technologies,
competitors: data.company.competitors,
},
};
}
Account-Based Marketing
Build comprehensive account profiles:
async function buildAccountProfile ( emails ) {
const profiles = await Promise . all (
emails. map (( email ) => getCombined (email)),
);
const validProfiles = profiles. filter (( p ) => p !== null );
if (validProfiles. length === 0 ) {
return null ;
}
// Aggregate company data (same company for all contacts)
const company = validProfiles[ 0 ].company;
// Collect all contacts
const contacts = validProfiles. map (( p ) => ({
name: p.person.name,
position: p.person.position,
phone: p.person.phone,
email_score: p.person.emailScore,
seniority: p.person.seniority,
}));
return {
company: {
name: company.name,
size: company.employees,
revenue: company.revenue,
industry: company.industry,
technologies: company.technologies,
competitors: company.competitors,
total_contacts_available: company.emailCount,
},
contacts: contacts,
decision_makers: contacts. filter (
( c ) =>
c.seniority === "executive" ||
c.position. includes ( "VP" ) ||
c.position. includes ( "Director" ),
),
};
}
Performance Optimization
Batch Processing
async function enrichLeadsBatch ( emails , batchSize = 10 ) {
const results = [];
for ( let i = 0 ; i < emails. length ; i += batchSize) {
const batch = emails. slice (i, i + batchSize);
const batchResults = await Promise . allSettled (
batch. map (( email ) => getCombined (email)),
);
results. push (
... batchResults. map (( r , idx ) => ({
email: batch[idx],
data: r.status === "fulfilled" ? r.value : null ,
error: r.status === "rejected" ? r.reason : null ,
})),
);
// Respect rate limits
if (i + batchSize < emails. length ) {
await new Promise (( resolve ) => setTimeout (resolve, 200 ));
}
}
return results;
}
// Usage
const emails = [
"contact1@company.com" ,
"contact2@company.com" ,
"contact3@company.com" ,
];
const enriched = await enrichLeadsBatch (emails);
console. log (
`Enriched ${ enriched . filter (( r ) => r . data ). length }/${ emails . length } leads` ,
);
Error Handling
async function getCombinedSafe ( email ) {
try {
const response = await axios. get (
"https://api.tomba.io/v1/combined/find" ,
{
params: { email },
headers: {
"X-Tomba-Key" : process.env. TOMBA_API_KEY ,
"X-Tomba-Secret" : process.env. TOMBA_API_SECRET ,
},
},
);
return {
success: true ,
data: response.data.data,
};
} catch (error) {
if (error.response?.status === 404 ) {
return {
success: false ,
error: "NOT_FOUND" ,
message: "Email or company not found in database" ,
};
} else if (error.response?.status === 429 ) {
return {
success: false ,
error: "RATE_LIMIT" ,
message: "Rate limit exceeded. Please retry later." ,
};
} else if (error.response?.status === 400 ) {
return {
success: false ,
error: "INVALID_EMAIL" ,
message: "Invalid email format" ,
};
} else if (error.response?.status === 401 ) {
return {
success: false ,
error: "AUTH_FAILED" ,
message: "Invalid API credentials" ,
};
} else {
return {
success: false ,
error: "UNKNOWN" ,
message: error.message,
};
}
}
}
Migration Checklist
Testing
Comprehensive Test Suite
const assert = require ( "assert" );
describe ( "Combined API Migration" , () => {
it ( "should return combined data for valid email" , async () => {
const data = await getCombined ( "john@stripe.com" );
// Person assertions
assert (data.person.name);
assert (data.person.position);
assert (data.person.emailValid !== undefined );
// Company assertions
assert (data.company.name);
assert (data.company.industry);
assert (data.company.emailCount > 0 );
});
it ( "should return null for non-existent email" , async () => {
const data = await getCombined ( "notexist@example12345.com" );
assert. strictEqual (data, null );
});
it ( "should include email verification" , async () => {
const data = await getCombined ( "valid@stripe.com" );
assert (data.person.emailValid === true );
assert (data.person.emailScore > 90 );
});
it ( "should include technology stack" , async () => {
const data = await getCombined ( "john@shopify.com" );
assert (Array. isArray (data.company.technologies));
assert (data.company.technologies. length > 0 );
});
it ( "should include similar companies" , async () => {
const data = await getCombined ( "john@salesforce.com" );
assert (Array. isArray (data.company.competitors));
});
it ( "should handle batch processing" , async () => {
const emails = [
"john@stripe.com" ,
"jane@shopify.com" ,
"bob@salesforce.com" ,
];
const results = await enrichLeadsBatch (emails);
assert. equal (results. length , emails. length );
assert (results. some (( r ) => r.data !== null ));
});
});
Next Steps
Ready to migrate from Clearbit?
Get started with a free Tomba account - includes 50 free searches and 500 free verifications!
Start Migration →
Last modified on November 16, 2025