# Identity Resolution API - Reverse Email and Phone Lookup (`nabeelbaghoor/identity-resolution-api`) Actor

Resolve an email address, phone number, name, username or social profile URL into the person behind it, with names, emails, phones, addresses, jobs, education, social profiles and a confidence score on every match. Only pay for answers that contain the fields you asked for. Pay per result.

- **URL**: https://apify.com/nabeelbaghoor/identity-resolution-api.md
- **Developed by:** [Nabeel Hassan](https://apify.com/nabeelbaghoor) (community)
- **Categories:** Business, Developer tools, Lead generation
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$18.00 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Identity Resolution API - Reverse Email and Phone Lookup

Turn a single identifier into the person behind it. Give it an email address, a phone number, a name, a username, a social profile URL or a VIN, and get back one flat row per person: names, emails, phones, addresses, jobs, education, social profiles, gender, date of birth and a confidence score on the match itself.

### What this actor does

- **Resolves eight kinds of identifier.** Reverse email lookup, reverse phone lookup, name search, username or user id, social profile URL, VIN, a combined lookup that reads several lists row by row so one row is one person, and a follow-up search that expands a candidate from an earlier run.
- **Tells you how confident the answer is.** Every row carries a match score. A score of 1 means the provider resolved your search to a single person; anything lower means it returned candidates, and each candidate row is flagged with `isPossibleMatch` so a confident match is never quietly mixed in with a guess.
- **Lets you not pay for the wrong answer.** The provider evaluates a match requirement before it charges, so a search that comes back without the field you actually needed returns empty and is not billed. Ask for an email, or a mobile phone, or a work email and a job, and only matching answers cost anything. This is the single most important setting in the input.
- **Finds data you do not already have.** Switch on new data only, and a requirement of email means an email other than the one you searched with. This is how you expand a contact record rather than pay to be told what you already knew.
- **Expands ambiguous answers on request.** When the provider returns candidates rather than one person, each candidate carries its own search pointer. Turn on expansion and the actor follows those pointers to pull full records, up to a ceiling you set. It is off by default because each expansion is a second billable search.
- **Refuses a guess when you cannot afford one.** Confident single match mode returns the best high ranking person or nothing at all, never a candidate list, which is what automated enrichment needs.
- **Shows its work.** Switch on the source layer and every row carries the sources behind it, with source name, category, domain and origin URL, so an answer can be audited rather than trusted.
- **Handles pointers that are too long for a URL.** A search pointer is roughly two kilobytes of hex. Sent on a query string it comes back as HTTP 414, so follow-up searches go out in a form encoded body instead, which is what the provider documents.
- **Tells a rate limit from a spent quota.** Both arrive as HTTP 403 from this provider. The per second limit clears by itself and is retried with backoff; a used-up daily, weekly or monthly quota stops the run with what it has rather than burning retries on something waiting will not fix.
- **Never charges for a miss.** Only rows carrying a resolved person are billed.

### Input

| Field | What it does |
| --- | --- |
| `mode` | Email, phone, name, combined, username, profile URL, VIN or search pointer. |
| `emails`, `phones`, `names`, `addresses` | The identifiers to resolve. In combined mode they are read row by row. |
| `usernames`, `profileUrls`, `vins`, `searchPointers` | The other identifier types. |
| `country`, `state`, `city` | Narrow every search in the run. Biggest single lever on a name search. |
| `matchRequirements` | Fields the answer must contain, or it is free. |
| `matchRequirementsJoin` | Require all of them, or any one of them. |
| `matchRequirementsNewDataOnly` | Only count data that was not in your query. |
| `sourceCategoryRequirements` | Source types the answer must draw on, or it is free. |
| `topMatch` | Return a confident single person or nothing. |
| `minimumMatch` | Provider side confidence floor, 0 to 100. |
| `minimumProbability` | How sure the provider must be before including inferred data. |
| `inferPersons` | Allow people assembled purely by inference. |
| `liveFeeds` | Query live sources as well as the index. |
| `hideSponsored` | Leave out results behind a third party paywall. |
| `showSources` | Return the source layer: none, matching, or all. |
| `expandPossiblePersons`, `maxExpansionsPerSearch` | Follow candidate pointers into full records. |
| `minMatchScore`, `requireEmail`, `requirePhone`, `requireJob`, `requireSocialProfile`, `skipPossibleMatches` | Local filters on the rows. |
| `skipNotFound` | Leave unresolved searches out of the dataset. |
| `requestsPerMinute` | Pace the run under your plan's rate limit. |
| `maxResults` | Hard cap on rows, and therefore on spend and run time. |
| `apiKey` | Your own API key. Stored as a secret. |

### Example output

```json
{
  "found": true,
  "mode": "email",
  "query": "jane.doe@example.com",
  "isPossibleMatch": false,
  "matchScore": 1,
  "personId": "3b95e0c1-abca-4240-8e89-0da7473f8580",
  "searchPointer": "e43a4955834bb50c5478ba5eb38db",
  "isInferred": false,
  "fullName": "Jane A Doe",
  "firstName": "Jane",
  "middleName": "A",
  "lastName": "Doe",
  "email": "jane.doe@example.com",
  "emailType": "work",
  "emails": ["jane.doe@example.com", "jane@personal.example"],
  "phone": "+1 206-555-0100",
  "phoneType": "mobile",
  "address": "100 Main Street, Seattle, Washington",
  "city": "Seattle",
  "state": "WA",
  "country": "US",
  "jobTitle": "Director of Engineering",
  "organization": "Example Corp",
  "industry": "Software",
  "school": "University of Washington",
  "gender": "female",
  "dateOfBirth": "1985-06-18",
  "socialProfiles": ["https://www.linkedin.com/in/janedoe"],
  "usernames": ["janedoe"],
  "visibleSources": 5,
  "availableSources": 78,
  "error": null
}
```

Rows also carry `sources` when the source layer is on, `availableData` with the provider's own count of what it holds for the search, and `raw`, the untouched person object, so nothing is lost in flattening.

### Frequently asked questions

#### What is an identity resolution API?

It is an API that takes a fragment of identity, one email address, one phone number, one username, and returns the single real person that fragment belongs to, merged from many sources into one record with a confidence score attached. It is the difference between a directory lookup, which returns rows that mention your search term, and identity resolution, which returns a person.

#### How do I do a reverse email lookup?

Run the actor in email mode with a list of addresses. Each address is resolved to the person who owns it, and the row carries their name, other email addresses, phone numbers, postal addresses, jobs and social profiles. Set a match requirement of name or job if a row without those is not worth paying for.

#### How do I find someone's other email addresses or phone numbers?

Search by the identifier you already have, set the match requirement to email or phone, and switch on new data only. The provider then only charges when the answer contains an address or number other than the one you searched with, which is exactly the case you were paying to find.

#### What is the difference between a person response and a possible persons response?

A person response means the provider resolved your search to one individual, and it is the fuller of the two records. A possible persons response means it could not, and returns up to fifty thinner candidate records instead, each with its own match score and search pointer. Every candidate is written as its own row with `isPossibleMatch` set to true. Turn on expansion to follow their pointers into full records, or turn on confident single match mode to refuse candidates entirely.

#### What is a search pointer?

A token the provider returns with every person and candidate that stands for that exact search. Feed it back in search pointer mode and you get the full record for that specific person without having to describe them again. It is also how you check for updates on a record you already have.

#### How do I stop paying for searches that miss?

Use `matchRequirements`. The provider checks the condition before billing, so a search whose answer does not contain the field you required returns empty and free. The local filters in this actor, such as `requireEmail`, run after the provider has answered and charged, so they only trim the dataset. Use them for tidiness and the match requirement for cost.

#### Can I search by name alone?

Yes, but narrow it. A common name with no location returns candidates rather than a person nearly every time. Set a country, and a state or city where you know one, and consider combined mode if you also hold an email, a phone or an address for that person: a name plus one more identifier is usually the difference between a candidate list and a confident match.

#### Do I need my own API key?

Yes. This actor does not include data access. You use your own API key from Pipl, which is the provider whose identity resolution API this actor calls. Your own plan, quota and terms apply, including which data fields your key is entitled to. Paste the key into the `apiKey` field, where it is stored as an Apify secret, or supply it as the `DATA_API_KEY` environment secret.

#### What happens when a search matches nobody?

The row is written with `found: false` and an `error` giving the reason, together with any warnings the provider returned, and the run continues to the next input. A no match answer is HTTP 200 with a persons count of zero at this provider, not an error, so it is treated as a result. Misses are never charged for. Set `skipNotFound` to true to leave them out of the dataset entirely.

#### How much does a run cost?

Pricing is pay per result: you are charged for each person resolved into the dataset, and never for searches that matched nobody, for rows dropped by the filters, or for duplicates. Apify platform usage is included in the per-result price. Note that an ambiguous search can return several candidate rows, and that expanding a candidate produces a second charged row. Your own API provider's quota is separate and billed by them.

### Keyword map

identity resolution API, reverse email lookup API, reverse phone lookup API, people search API, person lookup by email, email to person, phone to person, social profile lookup, username lookup API, LinkedIn URL lookup, contact enrichment API, person enrichment, identity verification data, match score, confidence score, people data API, background data API, VIN owner lookup, contact data waterfall, lead enrichment

# Actor input Schema

## `mode` (type: `string`):

Every search resolves an identifier into the person behind it. Email, phone, username, profile URL and VIN each take a single identifier per row. Name search takes a full name, and is worth narrowing with a country, state or city. Combined reads several lists row by row so one row is one person, which is what a name plus a phone or a name plus an address search needs. Search pointer follows up a candidate returned by an earlier run.

## `emails` (type: `array`):

Email addresses, one per line. An address pasted inside a longer string, such as a mail header or a name and address pair, is extracted before the request. Used by email search, and as the email half of a combined lookup.

## `phones` (type: `array`):

Phone numbers, one per line, in international format such as +12065550100. Used by phone search, and as the phone half of a combined lookup.

## `names` (type: `array`):

Full names, one per line. A two or three word name is split into first, middle and last before the request, which matches better than sending the whole string; anything else is sent whole. Used by name search, and as the name half of a combined lookup.

## `addresses` (type: `array`):

Postal addresses, one per line, written as one line each, in the same order as the names. A full address including a house number is enough to search on by itself. Combined lookups only.

## `usernames` (type: `array`):

Screen names, one per line, minimum three characters. Target a specific service by writing it as name@service, for example janedoe@facebook. A value written as digits then a service, such as 11231@facebook, is sent as a user id instead, which is the separate parameter the provider defines for it.

## `profileUrls` (type: `array`):

Profile URLs on supported services, one per line, such as a LinkedIn, Facebook or Twitter profile. The provider parses each URL down to the username or user id behind it.

## `vins` (type: `array`):

Vehicle identification numbers, one per line, to resolve to the person on record for the vehicle.

## `searchPointers` (type: `array`):

Search pointers, one per line, copied from the searchPointer column of an earlier run. A pointer expands a thin candidate into the full record. Pointers are long, so they are sent in the request body rather than on the URL.

## `country` (type: `string`):

Two letter ISO 3166 country code applied to every search in the run, for example US. Narrowing a name search by country is the single biggest thing you can do to reduce the candidate list.

## `state` (type: `string`):

State or province code applied to every search in the run, for example WA. Recognised for the United States, Canada, Great Britain and Australia. A United States state with no country set implies the United States.

## `city` (type: `string`):

City applied to every search in the run.

## `matchRequirements` (type: `array`):

Require these fields to be present in the answer. This is the setting that controls cost: the provider evaluates the requirement before it charges, so a search that comes back without what you asked for returns empty and is not billed. Asking for an email when you only need emails is the difference between paying for every lookup and paying for the ones that worked.

## `matchRequirementsJoin` (type: `string`):

Whether every field above has to be present, or just one of them. Requiring all of a long list is how a run ends up paying for almost nothing; requiring any of two or three is usually what you want.

## `matchRequirementsNewDataOnly` (type: `boolean`):

Require the fields above to be data that was not in your own query. Searching by an email and requiring a new email is how you find a person's other addresses rather than paying to be told the one you already had.

## `sourceCategoryRequirements` (type: `array`):

Require the answer to include data from these kinds of source. Like the field requirement above, this is evaluated before charging, so a person found only in sources you do not care about is not billed.

## `sourceCategoryRequirementsJoin` (type: `string`):

Whether the answer has to draw on every source type selected, or just one of them.

## `topMatch` (type: `boolean`):

Return the single best high ranking person or nothing at all, never a list of candidates. This is the setting for automated enrichment, where a wrong person is worse than no person.

## `minimumMatch` (type: `integer`):

Only return people whose confidence score is at least this, on a 0 to 100 scale here and 0 to 1 at the provider. Set 100 to accept only single perfect matches. Applied by the provider, so it also keeps candidates you would have discarded out of the bill.

## `minimumProbability` (type: `integer`):

How confident the provider has to be before it includes a field it inferred rather than observed, on a 0 to 100 scale. The provider default is 90.

## `inferPersons` (type: `boolean`):

Allow people assembled purely by statistical inference from your query rather than found in a source. Off by default, and worth leaving off unless you are deliberately exploring.

## `liveFeeds` (type: `boolean`):

Query live sources as well as the index. Adds roughly two to three percent more data at the cost of one to two seconds per search, and a lower ceiling on requests per second. On by default at the provider.

## `hideSponsored` (type: `boolean`):

Leave out results whose extra data sits behind a third party paywall.

## `showSources` (type: `string`):

Return where each answer came from. Matching returns only the sources used to build the person, which is the useful setting for provenance and auditing. All also returns sources that were considered but not used.

## `expandPossiblePersons` (type: `boolean`):

When the provider answers with a list of candidates rather than one person, follow each candidate's search pointer to pull its full record. Off by default because each expansion is a second billable search at the provider as well as another charged row here.

## `maxExpansionsPerSearch` (type: `integer`):

Ceiling on how many candidates from a single search get expanded. Keeps one ambiguous name from turning into fifty lookups.

## `skipPossibleMatches` (type: `boolean`):

Leave out rows that are candidates rather than confident matches. Consider setting the confident single match option above instead, which stops the provider from returning them at all.

## `minMatchScore` (type: `integer`):

A second, local pass on the confidence score, on the same 0 to 100 scale, applied after the provider answers. The provider side minimum above is the one that saves money; this one only trims the dataset.

## `requireEmail` (type: `boolean`):

Skip resolved people who carry no email address. The match requirement above does the same thing at the provider and without being charged, so prefer it where you can.

## `requirePhone` (type: `boolean`):

Skip resolved people who carry no phone number.

## `requireJob` (type: `boolean`):

Skip resolved people who carry no job title or employer.

## `requireSocialProfile` (type: `boolean`):

Skip resolved people who carry no profile URL.

## `skipNotFound` (type: `boolean`):

By default a search that matched nobody is still written, with found set to false and the reason on the row, so a list can be reconciled against what went in. Switch this on to leave those rows out entirely. Rows with no match are never charged for either way.

## `maxResults` (type: `integer`):

Stop after this many rows. Note that one ambiguous search can return several candidate rows.

## `requestsPerMinute` (type: `integer`):

How fast to call the provider. Requests are spaced evenly rather than sent in bursts, which is what keeps a long list from bunching up against the per second limiter. Lower this if your plan has a tighter ceiling, and lower it further when live data sources are on.

## `apiKey` (type: `string`):

Your own API key for the identity data provider this actor calls. The actor never ships a key: you bring yours, the run uses it, and it is stored encrypted. Can also be supplied as the DATA\_API\_KEY environment secret.

## Actor input object example

```json
{
  "mode": "email",
  "emails": [
    "jane.doe@example.com"
  ],
  "matchRequirements": [],
  "matchRequirementsJoin": "all",
  "matchRequirementsNewDataOnly": false,
  "sourceCategoryRequirements": [],
  "sourceCategoryRequirementsJoin": "any",
  "topMatch": false,
  "inferPersons": false,
  "liveFeeds": true,
  "hideSponsored": false,
  "showSources": "false",
  "expandPossiblePersons": false,
  "maxExpansionsPerSearch": 3,
  "skipPossibleMatches": false,
  "requireEmail": false,
  "requirePhone": false,
  "requireJob": false,
  "requireSocialProfile": false,
  "skipNotFound": false,
  "maxResults": 100,
  "requestsPerMinute": 300
}
```

# Actor output Schema

## `results` (type: `string`):

One row per person resolved, with contact details, jobs, social profiles and a confidence score.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "emails": [
        "jane.doe@example.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("nabeelbaghoor/identity-resolution-api").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "emails": ["jane.doe@example.com"] }

# Run the Actor and wait for it to finish
run = client.actor("nabeelbaghoor/identity-resolution-api").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "emails": [
    "jane.doe@example.com"
  ]
}' |
apify call nabeelbaghoor/identity-resolution-api --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,nabeelbaghoor/identity-resolution-api"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/CugQMRsfAnbOLJSJo/builds/H8ih0Gh02TbzzcvTm/openapi.json
