Migrate from Lusha
This guide maps the phone lookups of Lusha's Person API (GET /v2/person, POST /v2/person) and the v3 Search and Enrich Contacts endpoints to Tomba. Lusha API behavior checked on 2026-09-30 against the Lusha API reference and Lusha API error codes.
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.
- Set your HTTP client timeout to at least 180 seconds. Tomba returns the finished result in the response.
Endpoint mapping
Replace the base URL https://api.lusha.com with https://api.tomba.io. Tomba picks the endpoint by the identifier you have:
| Lusha | Tomba | Notes |
|---|---|---|
GET /v2/person with email or linkedinUrl | GET /v1/phone-finder | Returns phone numbers only. |
GET /v2/person with firstName, lastName, and a company | GET /v1/email-finder with enrich_mobile=true | Numbers are in data.phone_data, next to the email address. |
GET /v2/person with personId | No equivalent | Look the person up by email address, LinkedIn URL, or name and company. |
POST /v2/person | Single requests, or a bulk job | |
POST /v3/contacts/search-and-enrich | Single requests, or a bulk job | Map each entry in contacts as a GET /v2/person call. |
POST /v3/contacts/search, then POST /v3/contacts/enrich | One request per person | Tomba has no separate reveal step: the lookup returns the numbers. |
| No equivalent | GET /v1/phone-validator | Checks a number you already have and returns its line type, carrier, and region. |
Authentication changes
Lusha reads the key from the api_key header. Tomba needs two headers, X-Tomba-Key and X-Tomba-Secret:
Code
Tomba API keys expire; plan their rotation with Key expiry.
Parameter mapping
| Lusha | Tomba | Notes |
|---|---|---|
email | email | On /v1/phone-finder. |
linkedinUrl | linkedin | On /v1/phone-finder. Send the full profile URL, such as https://www.linkedin.com/in/jane-doe. |
firstName, lastName | first_name, last_name | On /v1/email-finder with enrich_mobile=true. /v1/email-finder also accepts full_name. |
companyDomain | domain | On /v1/email-finder. On /v1/phone-finder, domain returns the company's number, not the person's. |
companyName | company | On /v1/email-finder. |
revealPhones | Not needed | /v1/phone-finder returns only phone numbers. On the finder endpoints, enrich_mobile=true adds them. |
filterBy=phoneNumbers | Not needed | A phone finder request with no number returns an empty data array. |
| No equivalent | full | full=true returns every number found as an array; without it, data holds one number, preferring a valid one. |
personId, refreshJobInfo, partialProfile, revealEmails, signals, signalsStartDate | No equivalent |
Response field mapping
Lusha returns the person in contact.data. The Tomba phone finder returns the number itself in data, or an array of numbers with full=true:
| Lusha | Tomba | Notes |
|---|---|---|
contact.data.phoneNumbers[].number, v3 phones[].number | data.e164_format | Also in intl_format, local_format, and rfc3966_format. |
contact.data.phones (deprecated) | data.e164_format | |
contact.data.phoneNumbers[].phoneType, v3 phones[].type | data.line_type | Different values; see Status mapping. |
contact.data.phoneNumbers[].doNotCall, v3 phones[].doNotCall | No equivalent | Check Do Not Call registries yourself before you dial. |
contact.data.phoneNumbers[].updateDate | No equivalent | |
contact.isCreditCharged | No equivalent | When a request is charged is listed in Credit costs. |
contact.data.location.countryIso2 | No equivalent on the phone finder | data.country_code is the country of the number, not of the person. |
Name, job title, company, and social fields in contact.data | No equivalent on the phone finder | /v1/email-finder returns the name, position, company, and LinkedIn URL. For a full profile, see the Person API. |
Email fields in contact.data | No equivalent on the phone finder | Use /v1/email-finder or GET /v1/linkedin. |
Tomba also returns valid, carrier, region, and timezones for each number, and the email, domain, or linkedin it was found for. Every field is described in Phone response.
Status mapping
| Lusha | Tomba |
|---|---|
phoneType Mobile, v3 type mobile | line_type MOBILE |
phoneType Direct or Phone, v3 direct or work | No direct equivalent. Tomba reports the kind of line, such as FIXED_LINE, VOIP, or FIXED_LINE_OR_MOBILE. |
contact.error.name EMPTY_DATA, or 404 | 200 with data as an empty array |
| No equivalent | valid is false: the number isn't valid in its country's numbering plan |
Every line_type value is defined in Line types. With full=true, filter on valid if you only want valid numbers.
Error mapping
Lusha returns errors as {statusCode, message, errors}. Tomba returns an errors object with type, message, and code:
| Lusha | Tomba | What to do |
|---|---|---|
400 validation failed, 412 invalid syntax | 422 params_invalid | Fix the parameter named in the message. Sending none of email, domain, and linkedin to /v1/phone-finder also returns 422. |
401 missing or invalid 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. |
402 insufficient credits | 402 quota_exceeded | Wait for your usage window to renew or add credits. |
403 account inactive | 401 authentication_failed | Contact support. |
404 record not in the database | 200 with data as an empty array | Treat as no match. |
429 rate limit or daily quota | 429 rate_limit | Retry after the Retry-After delay; see Handle 429 responses. |
451 GDPR restriction | 451 claimed_email or 451 claimed_linkedin | The person asked Tomba to stop processing their data. Remove the record from your data. |
499 client closed request | 504 after 180 seconds | Raise your client timeout, or use a bulk job. |
500 | 500 api_error | Retry with backoff. |
Error responses don't consume credits. Every type is listed in Errors, and Tomba's rate limit headers are in Rate limit headers.
Bulk and async jobs
Lusha's POST /v2/person and v3 contact endpoints answer up to 100 contacts in one synchronous response, keyed by your contactId or clientReferenceId. A Tomba bulk job runs in the background: create it, launch it, poll its progress, and download the results as CSV. For a few contacts, send single requests instead.
| Lusha input | Tomba bulk type |
|---|---|
Contacts with email or linkedinUrl | phone-finder: a JSON list or CSV file of email addresses or LinkedIn URLs. See Phone finder. |
| Contacts with a name and company | finder with find_phones set to true: a CSV file with name and company columns. See Email finder. |
A JSON list is de-duplicated and reordered, so join the results back to your records on the email address or LinkedIn URL, not on position. The requests for each step are in Lifecycle, and webhook_url sends an event when a job ends; see Bulk job events. A job is charged when you first download its results; see Billing.
Code example
This function replaces a GET /v2/person phone lookup and returns Lusha's phoneNumbers shape, or null when no number is found. It sends an email address or LinkedIn URL to the phone finder, and a name and company to the email finder with enrich_mobile=true. 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 ofapi_key. - Route each lookup by its identifier: email address or LinkedIn URL to
/v1/phone-finder, name and company to/v1/email-finderwithenrich_mobile=true. ReplacepersonIdlookups with one of these. - Read numbers from
data(withfull=true) ordata.phone_datainstead ofcontact.data.phoneNumbers, and mapphoneTypetoline_type. - Treat an empty
dataarray as no match, in place ofEMPTY_DATAand404. - Swap error handling:
422replaces400and412,451carries aclaimed_*type, anderrorsis an object. - Move batches of up to 100 contacts to Tomba bulk jobs, or loop over single requests.
- Run the same sample of contacts through both services and compare the numbers before you switch production traffic.