Migrate from Apollo.io
This guide maps the Apollo.io People Enrichment (POST /api/v1/people/match) and Bulk People Enrichment (POST /api/v1/people/bulk_match) endpoints to Tomba. Apollo.io API behavior checked on 2026-09-30 against the Apollo.io API reference.
Before you begin
- Get your API key (
ta_…) and secret (ts_…); see Authentication. - Check what each endpoint costs in Credit costs and your plan's throttling in Limits by plan.
- Apollo.io matches a person from whichever identifiers you send. Tomba has one endpoint per input, so pick the endpoint from the identifiers you have: a name and company, a LinkedIn URL, or an email address.
- Set your HTTP client timeout to at least 180 seconds. Tomba returns the finished result in the response, including phone numbers.
Endpoint mapping
Replace the base URL https://api.apollo.io/api/v1 with https://api.tomba.io/v1. Apollo.io takes POST requests; Tomba lookups are GET requests with query parameters.
| Apollo.io | Tomba | Notes |
|---|---|---|
POST /people/match with a name and domain or organization_name | GET /v1/email-finder | Returns the person's work address. |
POST /people/match with linkedin_url | GET /v1/linkedin | Returns the same fields as the email finder. |
POST /people/match with email | GET /v1/people/find | Returns the person in the Clearbit-style format; see Enrichment. |
POST /people/match with reveal_phone_number | GET /v1/phone-finder | Or add enrich_mobile=true to the email finder or LinkedIn finder request. |
POST /people/bulk_match | Bulk jobs | See Bulk and async jobs. |
Authentication changes
Apollo.io reads the key from the x-api-key header, or an OAuth 2.0 bearer token. Tomba needs two headers, X-Tomba-Key and X-Tomba-Secret. Tomba also supports OAuth 2.0; see OAuth 2.0.
Code
Tomba API keys expire; plan their rotation with Key expiry.
Parameter mapping
| Apollo.io | Tomba | Notes |
|---|---|---|
first_name, last_name | first_name, last_name | Email finder. |
name | full_name | Email finder. Used when first_name and last_name aren't both sent. |
domain | domain | Email finder. A disposable or webmail domain returns 400 unknown_record. |
organization_name | company | Email finder. Apollo.io also matches a previous employer; Tomba searches the company you send. A domain gives better results. |
linkedin_url | url on GET /v1/linkedin | |
email | email on GET /v1/people/find | |
reveal_phone_number | enrich_mobile=true | Numbers are returned in data.phone_data in the same response, at an extra cost; no webhook is needed. |
webhook_url | webhook_url | Optional in Tomba. The result is still returned in the response, and also posted to your URL when it qualifies. See Per-request callbacks. |
id, hashed_email | No equivalent | |
reveal_personal_emails | No equivalent | Tomba returns work addresses. |
run_waterfall_email, run_waterfall_phone | No equivalent | |
poll_only | No equivalent | Every Tomba response is final. |
Response field mapping
Apollo.io returns the person in person. Tomba's email finder and LinkedIn finder return it in data; the fields are described in Person.
| Apollo.io | Tomba | Notes |
|---|---|---|
person.email | data.email | null when no address is found; the status is still 200. |
person.email_status | data.verification.status | See Status mapping. |
person.first_name, last_name | data.first_name, data.last_name | |
person.name | data.full_name | |
person.title | data.position | |
person.linkedin_url | data.linkedin | |
person.country | data.country | A two-letter country code in Tomba. |
person.departments[] | data.department | One department. The department names differ. |
person.organization | data.company, data.website_url | The company name and domain. For a full company profile, call GET /v1/companies/find. |
person.phone_numbers[].sanitized_number | data.phone_data[].e164_format | With enrich_mobile=true. line_type gives the number type; see Phone response. |
person.match_confidence | data.score | Computed differently. Recalibrate any threshold you apply to it. |
person.city, state | No equivalent | GET /v1/people/find returns data.geo.city and data.geo.state when Tomba has them. |
person.seniority | No equivalent | GET /v1/people/find returns data.employment.seniority when Tomba has it. |
person.id, contact_id, organization_id | No equivalent | |
person.headline, employment_history | No equivalent | |
request_id, waterfall | No equivalent | Tomba returns a request ID in the X-Request-ID header; see Going to production. |
Tomba also returns pattern, type, gender, accept_all, and the sources where the address was found. For the email lookup, GET /v1/people/find returns data.name, data.employment, data.geo, and data.linkedin.handle; see Enrichment.
Status mapping
Apollo.io email_status | Tomba verification.status |
|---|---|
verified | valid |
Tomba also returns accept_all, invalid, and unknown, or null when the address hasn't been verified. Compare status case-insensitively. Each value is defined in Status values.
Error mapping
Apollo.io returns an error string. Tomba returns an errors object with type, message, and code:
| Apollo.io | Tomba | What to do |
|---|---|---|
400 validation error | 422 params_invalid | Fix the parameter named in the message. |
400 missing webhook_url with reveal_phone_number | Not returned | Tomba doesn't need a webhook for phone numbers. |
401 invalid API key | 400 authentication_failed (missing or malformed), 400 api_key_expired, 401 authentication_failed (wrong key or secret) | Check both headers; rotate an expired key. |
429 rate limit exceeded | 429 rate_limit | Retry after the Retry-After delay; see Handle 429 responses. |
Also handle 402 quota_exceeded when you run out of credits, 400 unknown_record when the email finder receives a disposable or webmail domain, and 451 claimed_email when the owner of an address asked Tomba to stop processing it. GET /v1/people/find returns 200 with an errors object of type not_found when Tomba has no data for the address, so check the body for errors. Error responses don't consume credits; every type is listed in Errors.
Apollo.io reports its limits in x-rate-limit-* and x-*-requests-left headers. Tomba's headers are described in Rate limit headers.
Bulk and async jobs
Apollo.io's bulk endpoint takes up to 10 people in details[] and returns matches[] in the response. Tomba runs lists as bulk jobs; row limits per type are in Bulk types.
Apollo.io details[] entries | Tomba bulk type |
|---|---|
Name and domain or company | finder: a CSV file with name and company columns. See Email finder. |
linkedin_url | linkedin: a JSON list of profile URLs or a CSV file. See LinkedIn finder. |
email | enrich: a JSON list of addresses or a CSV file. See Email enrichment. |
reveal_phone_number | Set phone to true on the job, at an extra cost. |
Each job is created, launched, polled, and downloaded as CSV; see Lifecycle. To be notified when a job ends, set webhook_url; see Bulk job events. A job is charged when you first download its results; see Billing.
The bulk response fields total_requested_enrichments, unique_enriched_records, missing_records, and credits_consumed have no equivalent. Poll the job's progress instead.
Code example
This function replaces a people/match call made with a name and company or with a LinkedIn URL. It returns Apollo.io's person field names, or null when Tomba finds no address. It needs Node.js 18 or later.
Code
Cutover checklist
- Store the Tomba key and secret as
TOMBA_API_KEYandTOMBA_SECRET_KEY, and send them asX-Tomba-KeyandX-Tomba-Secretinstead ofx-api-key. - Route each
people/matchcall by its input: a name and company to/v1/email-finder, a LinkedIn URL to/v1/linkedin, and an email address to/v1/people/find. - Replace
reveal_phone_numberand its webhook withenrich_mobile=true, and read the numbers fromdata.phone_data. - Update response parsing: read
datainstead ofperson,positioninstead oftitle,linkedininstead oflinkedin_url, and translateemail_statuswith the status mapping. - Handle
402for exhausted credits, retry429afterRetry-After, and drop addresses that return451. - Move
bulk_matchbatches tofinder,linkedin, orenrichbulk jobs. - Run the same sample of people through both services and compare the results before you switch production traffic.