# Migrating from Resolve to Enrich

> Move identity resolution calls from the asynchronous /v2/resolve endpoints to the synchronous /v2/enrich endpoints.

Identity resolution has a new home: `POST /v2/enrich/persons` and `POST /v2/enrich/companies`. They take the same inputs as Resolve, cost the same, and answer synchronously: the match comes back in the API response, with no job to track and no callback to receive.

The previous endpoints, `POST /v2/resolve/persons/email`, `POST /v2/resolve/persons/name` and `POST /v2/resolve/companies/live`, remain functional until **October 13, 2026**, when they are retired. See the [endpoint transition overview](https://docs.reversecontact.com/guides/endpoint-transition-october-2026) for the full timeline. This guide covers everything that changes when you move to Enrich.

## What changes

| Area | `/v2/resolve/...` | `/v2/enrich/...` |
| ---- | ----------------- | ---------------- |
| Paths | One path per input type for persons, one for companies | One path for persons, one for companies |
| Delivery | Asynchronous: a `webhookId`, then polling or a webhook callback | Synchronous: the match is in the response |
| Person result | A focused subset of the profile | The same fields, plus the full profile of the [Profile](https://docs.reversecontact.com/endpoints/fetch-profile) endpoint |
| Other candidates | Not returned | `data.alternativePersons`, each one a full profile |
| Not found | `data-not-found` error code in the job result | `404` response |
| Pricing | 2 credits | 2 credits, only when a match is found |

## Paths

| Before | After |
| ------ | ----- |
| `POST /v2/resolve/persons/email` | `POST /v2/enrich/persons` |
| `POST /v2/resolve/persons/name` | `POST /v2/enrich/persons` |
| `POST /v2/resolve/companies/live` | `POST /v2/enrich/companies` |

The method and the `Authorization: Bearer YOUR_API_KEY` header stay the same.

## Inputs stay the same

Send the fields you already send today. The only field to drop is `webhookUrl`.

| Before | After |
| ------ | ----- |
| `/v2/resolve/persons/email` with `email` | `/v2/enrich/persons` with `email` |
| `/v2/resolve/persons/name` with `firstName`, `lastName` and `companyDomain` or `companyName` | `/v2/enrich/persons` with the same fields |
| `/v2/resolve/companies/live` with `domain` | `/v2/enrich/companies` with `domain` |

On `/v2/enrich/persons`, every field is optional and independent: provide at least one of `email`, `firstName`, `lastName`, `companyDomain` or `companyName`. You can also combine an email with a name and a company in a single request. See [Best practices](https://docs.reversecontact.com/endpoints/enrich-person#best-practices) for the trade-off between match rate and certainty.

## From asynchronous to synchronous

Resolve replied with a job acknowledgement and delivered the result later, through polling on `GET /v2/webhooks/:webhookId` or a callback to your `webhookUrl`. Enrich returns the match in the response to your request.

Before:

```javascript
const trigger = await fetch("https://api.reversecontact.com/v2/resolve/persons/email", {
  method: "POST",
  headers: { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json" },
  body: JSON.stringify({ email: "jane.doe@acme.com" })
});
const { data: { webhookId } } = await trigger.json();

while (true) {
  const res = await fetch(`https://api.reversecontact.com/v2/webhooks/${webhookId}`, {
    headers: { "Authorization": "Bearer YOUR_API_KEY" }
  });
  const { data } = await res.json();
  if (data.status === "succeeded") { console.log(data.result); break; }
  if (data.status === "errored") throw new Error(data.errorCode ?? "Job failed");
  await new Promise(r => setTimeout(r, 2000));
}
```

After:

```javascript
const response = await fetch("https://api.reversecontact.com/v2/enrich/persons", {
  method: "POST",
  headers: { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json" },
  body: JSON.stringify({ email: "jane.doe@acme.com" })
});

if (response.status === 404) {
  console.log("No match");
} else {
  const { data } = await response.json();
  console.log(data.person.firstName, data.person.currentCompanyName);
}
```

What you can remove from your integration:

- The callback endpoint you expose to receive Resolve results, and its `webhookUrl` in your requests.
- The polling loop on `GET /v2/webhooks/:webhookId`.
- The storage you use to correlate a `webhookId` with the original request.
- The handling of the `data-not-found`, `fetch-data-error` and `result-unavailable` job error codes. A not-found is now a `404` response, and other failures come back as standard HTTP errors, see [Errors & retries](https://docs.reversecontact.com/guides/errors-retries).

## Person results are full profiles

Resolve delivered a focused subset of the profile, and the documented flow was to chain into the [Profile](https://docs.reversecontact.com/endpoints/fetch-profile) endpoint for the rest. Enrich returns the full profile in `data.person`, with `currentPosition`, `experience`, `education`, `skills` and the other sections documented on the Profile endpoint. If you chained a Profile call after each resolution, drop it.

Every field of the Resolve result keeps its name and its place: `id`, `publicId`, `linkedinUrl`, `firstName`, `lastName`, `headline`, `currentPositionTitle`, `currentCompanyId`, `currentCompanyName`, `currentCompanyLinkedinId`, `updateDate` and `location`. Your existing parsing keeps working once you read `data.person` instead of the job result, and the Profile sections come on top. The complete field reference is on the [Profile](https://docs.reversecontact.com/endpoints/fetch-profile) endpoint.

In rare cases the full profile cannot be retrieved in time. `data.isFullProfile` is then `false` and `data.person` carries only the Resolve fields listed above, which you can complete with the [Profile](https://docs.reversecontact.com/endpoints/fetch-profile) endpoint. See [Light fallback](https://docs.reversecontact.com/endpoints/enrich-person#light-fallback).

## Alternative candidates

When several people match your input, Enrich returns the best match in `data.person` and the other candidates in `data.alternativePersons`, ordered best first, up to nine. Each alternative is a full profile too, with `currentCompanyId` always `null`. Resolve had no equivalent, so use this array as an ambiguity signal: when it is not empty, route the record to review before writing it. See [Data quality](https://docs.reversecontact.com/guides/data-quality#use-alternativepersons-as-an-ambiguity-flag).

## Pricing

Enrich costs **2 credits**, the same price as Resolve, and only when a match is found. A `404` costs 0 credits. The full profile of persons, alternative candidates and companies is included in that price.

The `fullProfile` request field is deprecated and ignored on both Enrich endpoints: it is still accepted so existing requests keep working, and you can remove it.

## Companies

`POST /v2/enrich/companies` takes the same `domain` as `/v2/resolve/companies/live` and returns the company in `data.company` as a full profile: the fields of the Resolve result keep their names, and the [Company Profile](https://docs.reversecontact.com/endpoints/fetch-company) fields come on top. See [Enrich company](https://docs.reversecontact.com/endpoints/enrich-company) for the full reference.

## Migration checklist

1. Replace `/v2/resolve/persons/email` and `/v2/resolve/persons/name` with `POST /v2/enrich/persons`, and `/v2/resolve/companies/live` with `POST /v2/enrich/companies`.
2. Remove `webhookUrl` from your requests and read the result from the response.
3. Remove the polling loop, the callback endpoint and the `webhookId` correlation.
4. Treat `404` as a normal no-match outcome.
5. Read the person from `data.person`: the Resolve fields keep their names.
6. Drop the Profile calls you used to chain after a resolution.
7. Check `data.alternativePersons` before writing a match automatically.
8. Remove `fullProfile` from your requests.
