# Migrate from ContactOut

This guide maps the phone lookups of ContactOut's Contact Info API (single, bulk v1, and bulk v2), LinkedIn Profile API (from a LinkedIn URL and from an email address), People Enrich API, and Phone Number Checker to Tomba. ContactOut API behavior checked on 2026-09-30 against the [ContactOut API reference](https://api.contactout.com/).

## Before you begin

- Get your API key (`ta_…`) and secret (`ts_…`); see [Authentication](/authentication).
- Check what each endpoint costs in [Credit costs](/usage-and-quotas#credit-costs) and your plan's throttling in [Limits by plan](/rate-limits#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.contactout.com` with `https://api.tomba.io`.

| ContactOut                                                             | Tomba                                                                        | Notes                                                                                                                                                        |
| ---------------------------------------------------------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `GET /v1/people/linkedin` with `include_phone=true`                    | [`GET /v1/phone-finder`](/api/phone#phone-finder) with `linkedin`            | For email addresses, call [`GET /v1/linkedin`](/api/finder#linkedin-finder).                                                                                 |
| `GET /v1/linkedin/enrich`                                              | [`GET /v1/phone-finder`](/api/phone#phone-finder) with `linkedin`            | Returns phone numbers only. For email addresses and profile data, call [`GET /v1/linkedin`](/api/finder#linkedin-finder).                                    |
| `GET /v1/email/enrich`                                                 | [`GET /v1/enrich`](/api/finder#email-enrichment) with `enrich_mobile=true`   | Returns the person and their numbers in `data.phone_data`. For numbers only, call `/v1/phone-finder` with `email`.                                           |
| `POST /v1/people/enrich` with `linkedin_url` or `email`                | [`GET /v1/phone-finder`](/api/phone#phone-finder)                            | See [Parameter mapping](#parameter-mapping).                                                                                                                 |
| `POST /v1/people/enrich` with a name and `company` or `company_domain` | [`GET /v1/email-finder`](/api/finder#email-finder) with `enrich_mobile=true` | Numbers are in `data.phone_data`.                                                                                                                            |
| `POST /v1/people/enrich` with `phone`                                  | No equivalent                                                                | Tomba doesn't look people up by phone number. [`GET /v1/phone-validator`](/api/phone#phone-validator) checks the number itself.                              |
| `GET /v1/people/linkedin/phone_status`                                 | [`GET /v1/linkedin`](/api/finder#linkedin-finder), `data.phone_number`       | `true` when Tomba has a number for the person. Unlike ContactOut's checker, this request can be charged; see [Credit costs](/usage-and-quotas#credit-costs). |
| `POST /v1/people/linkedin/batch`, `POST /v2/people/linkedin/batch`     | A [bulk job](#bulk-and-async-jobs)                                           |                                                                                                                                                              |

## Authentication changes

ContactOut reads the key from the `token` header. Tomba needs two headers, `X-Tomba-Key` and `X-Tomba-Secret`:

```bash
# ContactOut
curl -G "https://api.contactout.com/v1/people/linkedin" \
  --data-urlencode "profile=https://www.linkedin.com/in/jane-doe" \
  --data-urlencode "include_phone=true" \
  -H "token: $CONTACTOUT_API_TOKEN"

# Tomba
curl -G "https://api.tomba.io/v1/phone-finder" \
  --data-urlencode "linkedin=https://www.linkedin.com/in/jane-doe" \
  --data-urlencode "full=true" \
  -H "X-Tomba-Key: $TOMBA_API_KEY" \
  -H "X-Tomba-Secret: $TOMBA_SECRET_KEY"
```

Tomba API keys expire; plan their rotation with [Key expiry](/authentication#key-expiry).

## Parameter mapping

| ContactOut                                                                  | Tomba                                  | Notes                                                                                                            |
| --------------------------------------------------------------------------- | -------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `profile`, `linkedin_url`                                                   | `linkedin`                             | On `/v1/phone-finder`. On `/v1/linkedin` the parameter is `url`.                                                 |
| `email`                                                                     | `email`                                | On `/v1/phone-finder` or `/v1/enrich`.                                                                           |
| `include_phone=true`, `include` with `phone`                                | Not needed                             | `/v1/phone-finder` returns only phone numbers. On the finder endpoints, `enrich_mobile=true` adds them.          |
| `full_name`, `first_name`, `last_name`                                      | `full_name`, `first_name`, `last_name` | On `/v1/email-finder` with `enrich_mobile=true`.                                                                 |
| `company`                                                                   | `company`                              | On `/v1/email-finder`. Tomba takes one company per request; ContactOut takes an array.                           |
| `company_domain`                                                            | `domain`                               | On `/v1/email-finder`. One domain per request. On `/v1/phone-finder`, `domain` returns the company's number.     |
| No equivalent                                                               | `full`                                 | `full=true` returns every number found as an array; without it, `data` holds one number, preferring a valid one. |
| `email_type`, `profile_only`, `phone`, `job_title`, `location`, `education` | No equivalent                          |                                                                                                                  |

## Response field mapping

| ContactOut                                          | Tomba                                 | Notes                                                               |
| --------------------------------------------------- | ------------------------------------- | ------------------------------------------------------------------- |
| `status_code` in the body                           | HTTP status                           | Tomba doesn't repeat the status in the body.                        |
| `profile.phone[]` (array of numbers)                | `data[].e164_format` with `full=true` | Also in `intl_format`, `local_format`, and `rfc3966_format`.        |
| `profile.phone` (a string, from `/v1/email/enrich`) | `data.phone_data[].e164_format`       | On `/v1/enrich` with `enrich_mobile=true`.                          |
| `profile.url`                                       | `data.linkedin`                       | Only on a number found for the LinkedIn URL you sent.               |
| `profile.email`, `work_email`, `personal_email`     | No equivalent on the phone finder     | Call `/v1/linkedin` or `/v1/enrich` for the email address.          |
| `profile.github`, `twitter`, and profile details    | No equivalent on the phone finder     | For profile data, see the [Person API](/api/enrichment#person-api). |
| Phone checker `profile.phone`                       | `data.phone_number` on `/v1/linkedin` |                                                                     |

Tomba also returns `valid`, `country_code`, `line_type`, `carrier`, `region`, and `timezones` for each number. Every field is described in [Phone response](/attributes/phone).

## Status mapping

| ContactOut                          | Tomba                                                                                         |
| ----------------------------------- | --------------------------------------------------------------------------------------------- |
| `404` with `"message": "Not Found"` | `200` with `data` as an empty array                                                           |
| Empty `phone` or `phones` array     | `200` with `data` as an empty array                                                           |
| No equivalent                       | `valid` is `false`: the number isn't valid in its country's numbering plan                    |
| No equivalent                       | `line_type`, such as `MOBILE` or `FIXED_LINE`; see [Line types](/attributes/phone#line-types) |

With `full=true`, filter on `valid` if you only want valid numbers.

## Error mapping

ContactOut returns `status_code` and `message`. Tomba returns an `errors` object with `type`, `message`, and `code`:

| ContactOut                                        | Tomba                                                                                                                        | What to do                                                                                                                          |
| ------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `400` or `401` bad credentials or invalid headers | `400 authentication_failed` (missing or malformed), `400 api_key_expired`, `401 authentication_failed` (wrong key or secret) | Check both headers; [rotate](/authentication#rotate-keys) an expired key.                                                           |
| `400` or `401` bad request or invalid input       | `422 params_invalid`                                                                                                         | Fix the parameter named in the message. Sending none of `email`, `domain`, and `linkedin` to `/v1/phone-finder` also returns `422`. |
| `403` out of credits                              | `402 quota_exceeded`                                                                                                         | Wait for your usage window to renew or add credits.                                                                                 |
| `404` not found                                   | `200` with `data` as an empty array                                                                                          | Treat as no match.                                                                                                                  |
| `429` rate limit, with `retry-after`              | `429 rate_limit`, with `Retry-After`                                                                                         | Retry after the delay; see [Handle 429 responses](/rate-limits#handle-429-responses).                                               |
| No equivalent                                     | `451 claimed_email` or `451 claimed_linkedin`                                                                                | The person asked Tomba to stop processing their data. Remove the record from your data.                                             |

Error responses don't consume credits. Every type is listed in [Errors](/error-handling#error-types).

## Bulk and async jobs

ContactOut's bulk v1 endpoint answers up to 100 LinkedIn URLs synchronously, and bulk v2 returns a `job_id` to poll or a `callback_url` to notify. A Tomba bulk job runs in the background: create it, launch it, poll its progress, and download the results as CSV.

| ContactOut                               | Tomba bulk type                                                                                                         |
| ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `profiles` with `include_phone: true`    | `phone-finder`: a JSON `list` or CSV file of LinkedIn URLs or email addresses. See [Phone finder](/bulks#phone-finder). |
| `GET /v2/people/linkedin/batch/{job_id}` | Poll the job's progress, then download its results. See [Lifecycle](/bulks#lifecycle).                                  |
| `callback_url`                           | `webhook_url`, which receives a signed event when the job ends. See [Bulk job events](/webhook#bulk-job-events).        |

ContactOut keys batch results by profile URL. A Tomba JSON `list` is de-duplicated and reordered, so join the CSV rows back to your records on the LinkedIn URL, not on position. A job is charged when you first download its results; see [Billing](/bulks#billing).

## Code example

This function replaces a call to `GET /v1/people/linkedin` with `include_phone=true` and returns ContactOut's `profile.url` and `profile.phone` fields, or `null` when no number is found. It needs Node.js 18 or later.

```js title="phone-lookup.mjs"
const TOMBA_HEADERS = {
    "X-Tomba-Key": process.env.TOMBA_API_KEY,
    "X-Tomba-Secret": process.env.TOMBA_SECRET_KEY,
};

// Accepts ContactOut's profile parameter: a LinkedIn profile URL.
async function linkedinPhones(profile) {
    const url = new URL("https://api.tomba.io/v1/phone-finder");
    url.searchParams.set("linkedin", profile);
    url.searchParams.set("full", "true");

    const res = await fetch(url, { headers: TOMBA_HEADERS });
    const body = await res.json();
    if (body.errors) {
        throw new Error(
            `${res.status} ${body.errors.type}: ${body.errors.message}`,
        );
    }

    if (body.data.length === 0) return null;
    return {
        url: profile,
        phone: body.data.map((phone) => phone.e164_format),
    };
}

console.log(await linkedinPhones("https://www.linkedin.com/in/jane-doe"));
```

## Cutover checklist

1. Store the Tomba key and secret as `TOMBA_API_KEY` and `TOMBA_SECRET_KEY`, and send them as `X-Tomba-Key` and `X-Tomba-Secret` instead of `token`.
2. Route each lookup by its identifier: LinkedIn URL or email address to `/v1/phone-finder`, name and company to `/v1/email-finder` with `enrich_mobile=true`. Drop lookups by phone number.
3. Read numbers from `data` (with `full=true`) or `data.phone_data` instead of `profile.phone`, and use the HTTP status instead of `status_code`.
4. Treat an empty `data` array as no match, in place of `404`.
5. Swap error handling: `402` replaces the out-of-credits `403`, `422` covers invalid input, and `451` carries a `claimed_*` type.
6. Move batch lookups to Tomba bulk jobs and poll their progress instead of waiting for `callback_url`.
7. Run the same sample of profiles through both services and compare the numbers before you switch production traffic.
