[{"data":1,"prerenderedAt":71},["ShallowReactive",2],{"docs:rendered-article:\u002Fdocs\u002Fpublic\u002Fguides\u002Fdata-quality":3},{"path":4,"slug":5,"title":8,"description":9,"category":6,"categoryLabel":10,"order":11,"status":12,"family":13,"subfamily":13,"content":14,"html":15,"examplesHtml":16,"excerpt":17,"readTimeMinutes":18,"headings":19,"examplesHeadings":64,"lastUpdated":13,"endpointId":13,"requiredFlag":13,"relatedArticlePaths":65,"badge":13},"\u002Fdocs\u002Fguides\u002Fdata-quality",[6,7],"guides","data-quality","Data quality","How we decide what to return, what each response tells you about its own reliability, and how to trade match rate against certainty.","Guides",18,"new",null,"\nBad data costs more than missing data. A wrong profile written to your CRM gets copied, reported on, and emailed before anyone notices. A missing profile is just a gap you can fill later.\n\nThat asymmetry shapes the whole API. This guide explains the principle behind it, the signals every response carries about its own reliability, and how to tune the balance between match rate and certainty for your use case.\n\n## A not-found is a real answer\n\nWhen we cannot identify a person or a company with confidence, we say so. You get a `404`, or a documented error code on the async endpoints, instead of a plausible-looking record assembled to fill the response.\n\nThat is a deliberate design choice, and it runs through the whole platform: filters that we cannot honour are rejected instead of silently dropped, a job that failed reports why, and a profile removed by its owner stays removed. Every one of those cases is a place where we could have returned something instead of nothing. We would rather hand you a clean gap you can act on than a record you have no way to distrust.\n\n## Three questions behind every response\n\nEvery record you receive answers three separate questions, and each one has its own signal in the payload.\n\n1. **Is this the right person?** Identity resolution.\n2. **Is this still true?** Freshness.\n3. **Can I act on it?** Deliverability.\n\nA response can score well on one and poorly on another. A profile can be the right person and eighteen months out of date. An email address can be perfectly formed and belong to someone who left the company last year. Read the three signals separately.\n\n## 1. Is this the right person?\n\n### Two ways to identify someone\n\nThe API has two distinct families, and they answer to different inputs.\n\n| Family | Endpoints | What you send |\n| ------ | --------- | ------------- |\n| **Fetch** | [Profile](\u002Fdocs\u002Fendpoints\u002Ffetch-profile), [Profile status](\u002Fdocs\u002Fendpoints\u002Fcheck-person), [Profile live](\u002Fdocs\u002Fendpoints\u002Ffetch-profile-live) | A LinkedIn profile URL, or a Reverse Contact `id` (`prs_...`) |\n| **Enrich** | [Enrich profile](\u002Fdocs\u002Fendpoints\u002Fenrich-person), [Enrich company](\u002Fdocs\u002Fendpoints\u002Fenrich-company) | Attributes: `email`, `firstName`, `lastName`, `companyDomain`, `companyName`, or a company `domain` |\n\nFetch assumes you already know *which* profile you want and gives you its content. Enrich is the one doing identity resolution: it takes the attributes you have and decides who they point to. Everything below about ambiguity applies to Enrich.\n\n[Find Email](\u002Fdocs\u002Fendpoints\u002Fcontact-email) sits in between: it accepts a LinkedIn profile URL, or a full name with a company domain.\n\n### Match rate and certainty pull in opposite directions\n\nEvery field you add to an Enrich request narrows the search. That is a trade-off, not a free win.\n\n| What you send | Match rate | Certainty it is *your* person |\n| ------------- | ---------- | ----------------------------- |\n| `firstName` + `lastName` | Highest. Almost always returns someone | Lowest. There are hundreds of people with a common name |\n| `firstName` + `lastName` + `companyName` | High. Company labels are written many ways | Moderate. Company names are not unique |\n| `firstName` + `lastName` + `companyDomain` | Lower. Only matches if that person is known at that domain | High |\n| `email` | Lower. Only matches if we know that address | Highest. One address points to one person at one company |\n\nNeither end of that table is the correct answer. It depends on what a wrong match costs you:\n\n- **Automatic CRM writes, outbound sequences, anything a customer sees.** Prefer certainty. Send `companyDomain` or the email, accept more `404`s. A wrong profile is worse than no profile.\n- **Coverage work reviewed by a human, or a first pass you intend to refine.** A looser query is fine. Get the candidates, then decide.\n\nThe same logic applies to [Find Email](\u002Fdocs\u002Fendpoints\u002Fcontact-email): a name-and-domain lookup can resolve to the wrong person, and then the address it discovers is a correct address for the wrong human being.\n\n### A LinkedIn URL is not a permanent identifier\n\nIt is tempting to treat a profile URL as a primary key. It is not one. A public slug is chosen by the person who owns the profile, and they can change it whenever they like. When they do, the URL you stored six months ago points at nothing, and your call comes back as `404`, or as `422 INVALID_LINKEDIN_URL` if the stored value is malformed.\n\nStore our identifiers instead:\n\n- **`id` (`prs_...` for a person, `com_...` for a company)** is stable across slug changes. Every response that returns a person or a company carries it.\n- Both [Profile](\u002Fdocs\u002Fendpoints\u002Ffetch-profile) and [Profile status](\u002Fdocs\u002Fendpoints\u002Fcheck-person) accept `id` in place of `url`, and `id` wins when you send both.\n- [Profile live](\u002Fdocs\u002Fendpoints\u002Ffetch-profile-live) resolves the `id` to the current public slug before it starts, so a live refresh keeps working after a rename.\n\nKeep the URL for display and for humans. Key your records on `id`.\n\n### Use `alternativePersons` as an ambiguity flag\n\nWhen several people match your input, [Enrich profile](\u002Fdocs\u002Fendpoints\u002Fenrich-person) returns the best match in `data.person` and the other candidates in `data.alternativePersons`, ordered best first, up to nine.\n\nTreat that array as the confidence signal it is:\n\n- **Empty.** One person fits what you sent. Safe to write automatically.\n- **Not empty.** Several people fit. Add an identifying field and retry, or route the record to human review before it reaches a CRM or a sequence.\n\n::: code-group [Ambiguity gate]\n```javascript [JavaScript]\nconst { data } = await enrichPerson({ firstName, lastName, companyDomain });\n\nif (data.alternativePersons.length > 0) {\n  \u002F\u002F Several people match this input. Do not write automatically.\n  await queueForReview(data.person, data.alternativePersons);\n} else {\n  await writeToCrm(data.person);\n}\n```\n:::\n\nTwo details worth knowing before you build on the alternatives: each one is a full profile, the same shape as `data.person`, so you can review candidates side by side without an extra Profile call, and their `currentCompanyId` is always `null`, so you cannot chain a company fetch directly from a candidate you have not confirmed yet.\n\n### Current position is a decision, not a raw field\n\nA profile can list several jobs with no end date: a main role, a board seat, an advisory gig. `currentPosition` is the one we single out, and we pick it with fixed, published tie-breakers rather than a heuristic that drifts between calls. The `experience` and `education` arrays are ordered by the same fixed rules, whether the record came from our database or from a live fetch.\n\nSo when a title looks wrong, the cause is almost always a missing or outdated date in the source profile, not an unpredictable API. [Current position and work history ordering](\u002Fdocs\u002Fguides\u002Fcurrent-position-resolution) walks through exactly how the choice is made.\n\n## 2. Is this still true?\n\nProfessional data decays continuously. People change jobs, companies rename, teams get restructured. A record that was accurate when we captured it is not accurate forever, so every record tells you when it was last refreshed.\n\n| Where you look | Field | What it means |\n| -------------- | ----- | ------------- |\n| [Enrich profile](\u002Fdocs\u002Fendpoints\u002Fenrich-person), [People search](\u002Fdocs\u002Fendpoints\u002Fsearch-people) | `updateDate` | When we last refreshed that record |\n| [Profile](\u002Fdocs\u002Fendpoints\u002Ffetch-profile), [Company profile](\u002Fdocs\u002Fendpoints\u002Ffetch-company) | `metadata.updatedAt` | Last upstream update, present only when the response was served from cache |\n| [Profile status](\u002Fdocs\u002Fendpoints\u002Fcheck-person), [Company status](\u002Fdocs\u002Fendpoints\u002Fcheck-company) | `lastUpdate` | When we last fetched the profile from the source, `null` when it does not exist |\n\nThree ways to act on freshness instead of hoping for the best:\n\n- **Read it before you spend.** [Profile status](\u002Fdocs\u002Fendpoints\u002Fcheck-person) costs 0 credits and returns `exists` plus `lastUpdate`, along with `experienceCount`, `skillCount` and `schoolCount` so you can spot thin records too.\n- **Filter it at the source.** [People search](\u002Fdocs\u002Fendpoints\u002Fsearch-people) and [Company search](\u002Fdocs\u002Fendpoints\u002Fsearch-companies) accept `maxDataAgeDate`, an ISO 8601 datetime that returns only profiles refreshed after that date. Prefer it over filtering stale rows in your own code after you paid for them.\n- **Refresh on demand.** The live endpoints pull a fresh copy when the stored record is too old for your use case.\n\nPick an explicit threshold per use case instead of treating every record the same:\n\n| Use case | Suggested threshold | If older |\n| -------- | ------------------- | -------- |\n| Outbound sequence, meeting prep | 30 days | Refresh live before use |\n| CRM enrichment at scale | 90 days | Refresh in your next batch |\n| Analytics, market sizing | 180 days | Usually fine as is |\n\n[Data freshness](\u002Fdocs\u002Fguides\u002Fdata-freshness) covers the Check → Fetch → Live pattern and what it saves.\n\n## 3. Can I act on it?\n\n[Find Email](\u002Fdocs\u002Fendpoints\u002Fcontact-email) returns one entry per address discovered, each with a `value` and a `type`, typically `professional`. It is asynchronous: you get a `webhookId`, then retrieve the result by [polling](\u002Fdocs\u002Fguides\u002Fpolling) or through a [webhook callback](\u002Fdocs\u002Fguides\u002Fwebhooks). When nothing is found you receive `email-not-found` rather than a constructed address.\n\nTwo habits protect your sender reputation whatever the source of an address:\n\n- **Keep your own suppression list authoritative.** Unsubscribes, complaints and hard bounces you have already recorded always win over a freshly discovered address.\n- **Track bounce rate per input type.** If one segment bounces above your baseline, the input that produced it is usually the problem rather than the addresses themselves. Name-based lookups and URL-based lookups deserve separate counters, for the reason described earlier.\n\n> [!NOTE]\n> An address that exists is not the same as an address you should contact. Consent and applicable regulations remain yours to handle: the API tells you what exists, not whom you may email.\n\n## Filters that fail loudly\n\nSearch deserves its own note, because a bad filter is the quietest way to get bad data. If we ignored a field we did not recognise, you would receive a full page of results drawn from a query you never asked for, and nothing in the response would look wrong.\n\nSo the search endpoints validate every filter before the query runs. A rejected request returns `422`, lists every problem in `error.details.errors`, and costs 0 credits. Unknown field names, blank values, values outside a closed list such as `currentCompanyIndustry`, and inverted ranges are all refused rather than dropped. See [Search filter validation](\u002Fdocs\u002Fguides\u002Ferrors-retries#search-filter-validation) for the complete list.\n\nTwo behaviours to keep in mind when you read search results:\n\n- **Title matching is token-based, not exact.** `\"CTO\"` also matches `\"Deputy CTO\"`. Multiple titles are OR-matched, and `excludeCurrentPositionTitle` always wins over a match.\n- **Headcount is stored as fixed brackets.** A range only matches a bracket it fully contains, so bounds that fall inside a bracket return an empty array, and that request is still billed. Send bracket boundaries.\n\n## When a profile is blocked\n\nA profile removed under a GDPR or CCPA data subject request returns `451`. That state is permanent: the record is not stale, not missing, and will not come back. Retrying wastes calls, so treat `451` as a terminal outcome in your pipeline and drop the record from your enrichment queue rather than leaving it to be picked up on the next pass.\n\n## Putting it together\n\nFor a pipeline that writes to a system of record, this is the shape we recommend:\n\n::: code-group [Decision tree]\n```bash [Text]\nFor each lead:\n  1. Enrich with the most identifying fields you have\n     │\n     ├─ 404 (no confident match)\n     │  └─ Retry with fewer fields → route the result to review\n     │\n     └─ Match\n        │\n        ├─ alternativePersons not empty?\n        │  └─ Route to human review, do not write\n        │\n        └─ Unique match\n           │\n           ├─ Store the id (prs_...) and updateDate\n           │\n           └─ updateDate outside your threshold?\n              └─ Refresh live by id, then write\n```\n:::\n\nThe two gates that matter are the ambiguity gate and the freshness gate. Skipping either is how wrong data enters a CRM quietly.\n\n## Quality checklist\n\n- Use Fetch when you already know the profile, and Enrich when you are still resolving who it is.\n- Key your records on our `id`, not on a LinkedIn URL that its owner can rename.\n- Never write a record with a non-empty `alternativePersons` array without review.\n- Choose your input mix deliberately: certainty for automated writes, coverage for reviewed work.\n- Store the freshness timestamp next to every record so you can audit staleness later.\n- Set a staleness threshold per use case, filter with `maxDataAgeDate` on search, and refresh live above it.\n- Treat `404` as a normal outcome to log and `451` as terminal, not as errors to retry blindly.\n- Keep your suppression list ahead of any discovered address.\n- Track match rate and bounce rate per input type. They tell you which inputs to improve.\n","\u003Cp>Bad data costs more than missing data. A wrong profile written to your CRM gets copied, reported on, and emailed before anyone notices. A missing profile is just a gap you can fill later.\u003C\u002Fp>\n\u003Cp>That asymmetry shapes the whole API. This guide explains the principle behind it, the signals every response carries about its own reliability, and how to tune the balance between match rate and certainty for your use case.\u003C\u002Fp>\n\u003Ch2 id=\"a-not-found-is-a-real-answer\">A not-found is a real answer\u003C\u002Fh2>\n\u003Cp>When we cannot identify a person or a company with confidence, we say so. You get a \u003Ccode>404\u003C\u002Fcode>, or a documented error code on the async endpoints, instead of a plausible-looking record assembled to fill the response.\u003C\u002Fp>\n\u003Cp>That is a deliberate design choice, and it runs through the whole platform: filters that we cannot honour are rejected instead of silently dropped, a job that failed reports why, and a profile removed by its owner stays removed. Every one of those cases is a place where we could have returned something instead of nothing. We would rather hand you a clean gap you can act on than a record you have no way to distrust.\u003C\u002Fp>\n\u003Ch2 id=\"three-questions-behind-every-response\">Three questions behind every response\u003C\u002Fh2>\n\u003Cp>Every record you receive answers three separate questions, and each one has its own signal in the payload.\u003C\u002Fp>\n\u003Col>\n\u003Cli>\u003Cstrong>Is this the right person?\u003C\u002Fstrong> Identity resolution.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Is this still true?\u003C\u002Fstrong> Freshness.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Can I act on it?\u003C\u002Fstrong> Deliverability.\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>A response can score well on one and poorly on another. A profile can be the right person and eighteen months out of date. An email address can be perfectly formed and belong to someone who left the company last year. Read the three signals separately.\u003C\u002Fp>\n\u003Ch2 id=\"1-is-this-the-right-person\">1. Is this the right person?\u003C\u002Fh2>\n\u003Ch3 id=\"two-ways-to-identify-someone\">Two ways to identify someone\u003C\u002Fh3>\n\u003Cp>The API has two distinct families, and they answer to different inputs.\u003C\u002Fp>\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Family\u003C\u002Fth>\n\u003Cth>Endpoints\u003C\u002Fth>\n\u003Cth>What you send\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Cstrong>Fetch\u003C\u002Fstrong>\u003C\u002Ftd>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Fendpoints\u002Ffetch-profile\">Profile\u003C\u002Fa>, \u003Ca href=\"\u002Fdocs\u002Fendpoints\u002Fcheck-person\">Profile status\u003C\u002Fa>, \u003Ca href=\"\u002Fdocs\u002Fendpoints\u002Ffetch-profile-live\">Profile live\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>A LinkedIn profile URL, or a Reverse Contact \u003Ccode>id\u003C\u002Fcode> (\u003Ccode>prs_...\u003C\u002Fcode>)\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Cstrong>Enrich\u003C\u002Fstrong>\u003C\u002Ftd>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Fendpoints\u002Fenrich-person\">Enrich profile\u003C\u002Fa>, \u003Ca href=\"\u002Fdocs\u002Fendpoints\u002Fenrich-company\">Enrich company\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>Attributes: \u003Ccode>email\u003C\u002Fcode>, \u003Ccode>firstName\u003C\u002Fcode>, \u003Ccode>lastName\u003C\u002Fcode>, \u003Ccode>companyDomain\u003C\u002Fcode>, \u003Ccode>companyName\u003C\u002Fcode>, or a company \u003Ccode>domain\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>Fetch assumes you already know \u003Cem>which\u003C\u002Fem> profile you want and gives you its content. Enrich is the one doing identity resolution: it takes the attributes you have and decides who they point to. Everything below about ambiguity applies to Enrich.\u003C\u002Fp>\n\u003Cp>\u003Ca href=\"\u002Fdocs\u002Fendpoints\u002Fcontact-email\">Find Email\u003C\u002Fa> sits in between: it accepts a LinkedIn profile URL, or a full name with a company domain.\u003C\u002Fp>\n\u003Ch3 id=\"match-rate-and-certainty-pull-in-opposite-directions\">Match rate and certainty pull in opposite directions\u003C\u002Fh3>\n\u003Cp>Every field you add to an Enrich request narrows the search. That is a trade-off, not a free win.\u003C\u002Fp>\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>What you send\u003C\u002Fth>\n\u003Cth>Match rate\u003C\u002Fth>\n\u003Cth>Certainty it is \u003Cem>your\u003C\u002Fem> person\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>firstName\u003C\u002Fcode> + \u003Ccode>lastName\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Highest. Almost always returns someone\u003C\u002Ftd>\n\u003Ctd>Lowest. There are hundreds of people with a common name\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>firstName\u003C\u002Fcode> + \u003Ccode>lastName\u003C\u002Fcode> + \u003Ccode>companyName\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>High. Company labels are written many ways\u003C\u002Ftd>\n\u003Ctd>Moderate. Company names are not unique\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>firstName\u003C\u002Fcode> + \u003Ccode>lastName\u003C\u002Fcode> + \u003Ccode>companyDomain\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Lower. Only matches if that person is known at that domain\u003C\u002Ftd>\n\u003Ctd>High\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>email\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Lower. Only matches if we know that address\u003C\u002Ftd>\n\u003Ctd>Highest. One address points to one person at one company\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>Neither end of that table is the correct answer. It depends on what a wrong match costs you:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Automatic CRM writes, outbound sequences, anything a customer sees.\u003C\u002Fstrong> Prefer certainty. Send \u003Ccode>companyDomain\u003C\u002Fcode> or the email, accept more \u003Ccode>404\u003C\u002Fcode>s. A wrong profile is worse than no profile.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Coverage work reviewed by a human, or a first pass you intend to refine.\u003C\u002Fstrong> A looser query is fine. Get the candidates, then decide.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>The same logic applies to \u003Ca href=\"\u002Fdocs\u002Fendpoints\u002Fcontact-email\">Find Email\u003C\u002Fa>: a name-and-domain lookup can resolve to the wrong person, and then the address it discovers is a correct address for the wrong human being.\u003C\u002Fp>\n\u003Ch3 id=\"a-linkedin-url-is-not-a-permanent-identifier\">A LinkedIn URL is not a permanent identifier\u003C\u002Fh3>\n\u003Cp>It is tempting to treat a profile URL as a primary key. It is not one. A public slug is chosen by the person who owns the profile, and they can change it whenever they like. When they do, the URL you stored six months ago points at nothing, and your call comes back as \u003Ccode>404\u003C\u002Fcode>, or as \u003Ccode>422 INVALID_LINKEDIN_URL\u003C\u002Fcode> if the stored value is malformed.\u003C\u002Fp>\n\u003Cp>Store our identifiers instead:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>\u003Ccode>id\u003C\u002Fcode> (\u003Ccode>prs_...\u003C\u002Fcode> for a person, \u003Ccode>com_...\u003C\u002Fcode> for a company)\u003C\u002Fstrong> is stable across slug changes. Every response that returns a person or a company carries it.\u003C\u002Fli>\n\u003Cli>Both \u003Ca href=\"\u002Fdocs\u002Fendpoints\u002Ffetch-profile\">Profile\u003C\u002Fa> and \u003Ca href=\"\u002Fdocs\u002Fendpoints\u002Fcheck-person\">Profile status\u003C\u002Fa> accept \u003Ccode>id\u003C\u002Fcode> in place of \u003Ccode>url\u003C\u002Fcode>, and \u003Ccode>id\u003C\u002Fcode> wins when you send both.\u003C\u002Fli>\n\u003Cli>\u003Ca href=\"\u002Fdocs\u002Fendpoints\u002Ffetch-profile-live\">Profile live\u003C\u002Fa> resolves the \u003Ccode>id\u003C\u002Fcode> to the current public slug before it starts, so a live refresh keeps working after a rename.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Keep the URL for display and for humans. Key your records on \u003Ccode>id\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Ch3 id=\"use-alternativepersons-as-an-ambiguity-flag\">Use \u003Ccode>alternativePersons\u003C\u002Fcode> as an ambiguity flag\u003C\u002Fh3>\n\u003Cp>When several people match your input, \u003Ca href=\"\u002Fdocs\u002Fendpoints\u002Fenrich-person\">Enrich profile\u003C\u002Fa> returns the best match in \u003Ccode>data.person\u003C\u002Fcode> and the other candidates in \u003Ccode>data.alternativePersons\u003C\u002Fcode>, ordered best first, up to nine.\u003C\u002Fp>\n\u003Cp>Treat that array as the confidence signal it is:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Empty.\u003C\u002Fstrong> One person fits what you sent. Safe to write automatically.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Not empty.\u003C\u002Fstrong> Several people fit. Add an identifying field and retry, or route the record to human review before it reaches a CRM or a sequence.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"docs-code-group\">\u003Cdiv class=\"docs-code-group-header\">\u003Cspan class=\"docs-code-group-title\" data-static-title>Ambiguity gate\u003C\u002Fspan>\u003Cdiv class=\"docs-code-group-actions\">\u003Cspan class=\"docs-code-group-lang\">JavaScript\u003C\u002Fspan>\u003Cbutton type=\"button\" class=\"docs-code-group-copy\" data-copy-code=\"true\" aria-label=\"Copy code\">\u003Cspan class=\"iconify i-ph-copy docs-icon\" aria-hidden=\"true\">\u003C\u002Fspan>\u003C\u002Fbutton>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"docs-code-group-panel active\" data-tab=\"0\" role=\"tabpanel\">\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e\" tabindex=\"0\" data-language=\"javascript\">\u003Ccode class=\"language-javascript\">\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">const\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> { \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">data\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> } \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\"> await\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> enrichPerson\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">({ firstName, lastName, companyDomain });\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">if\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> (data.alternativePersons.\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">length\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\"> >\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 0\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">  \u002F\u002F Several people match this input. Do not write automatically.\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">  await\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> queueForReview\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(data.person, data.alternativePersons);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">} \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">else\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">  await\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> writeToCrm\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(data.person);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003C\u002Fdiv>\u003C\u002Fdiv>\n\u003Cp>Two details worth knowing before you build on the alternatives: each one is a full profile, the same shape as \u003Ccode>data.person\u003C\u002Fcode>, so you can review candidates side by side without an extra Profile call, and their \u003Ccode>currentCompanyId\u003C\u002Fcode> is always \u003Ccode>null\u003C\u002Fcode>, so you cannot chain a company fetch directly from a candidate you have not confirmed yet.\u003C\u002Fp>\n\u003Ch3 id=\"current-position-is-a-decision-not-a-raw-field\">Current position is a decision, not a raw field\u003C\u002Fh3>\n\u003Cp>A profile can list several jobs with no end date: a main role, a board seat, an advisory gig. \u003Ccode>currentPosition\u003C\u002Fcode> is the one we single out, and we pick it with fixed, published tie-breakers rather than a heuristic that drifts between calls. The \u003Ccode>experience\u003C\u002Fcode> and \u003Ccode>education\u003C\u002Fcode> arrays are ordered by the same fixed rules, whether the record came from our database or from a live fetch.\u003C\u002Fp>\n\u003Cp>So when a title looks wrong, the cause is almost always a missing or outdated date in the source profile, not an unpredictable API. \u003Ca href=\"\u002Fdocs\u002Fguides\u002Fcurrent-position-resolution\">Current position and work history ordering\u003C\u002Fa> walks through exactly how the choice is made.\u003C\u002Fp>\n\u003Ch2 id=\"2-is-this-still-true\">2. Is this still true?\u003C\u002Fh2>\n\u003Cp>Professional data decays continuously. People change jobs, companies rename, teams get restructured. A record that was accurate when we captured it is not accurate forever, so every record tells you when it was last refreshed.\u003C\u002Fp>\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Where you look\u003C\u002Fth>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>What it means\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Fendpoints\u002Fenrich-person\">Enrich profile\u003C\u002Fa>, \u003Ca href=\"\u002Fdocs\u002Fendpoints\u002Fsearch-people\">People search\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>updateDate\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>When we last refreshed that record\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Fendpoints\u002Ffetch-profile\">Profile\u003C\u002Fa>, \u003Ca href=\"\u002Fdocs\u002Fendpoints\u002Ffetch-company\">Company profile\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>metadata.updatedAt\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Last upstream update, present only when the response was served from cache\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Fendpoints\u002Fcheck-person\">Profile status\u003C\u002Fa>, \u003Ca href=\"\u002Fdocs\u002Fendpoints\u002Fcheck-company\">Company status\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>lastUpdate\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>When we last fetched the profile from the source, \u003Ccode>null\u003C\u002Fcode> when it does not exist\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>Three ways to act on freshness instead of hoping for the best:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Read it before you spend.\u003C\u002Fstrong> \u003Ca href=\"\u002Fdocs\u002Fendpoints\u002Fcheck-person\">Profile status\u003C\u002Fa> costs 0 credits and returns \u003Ccode>exists\u003C\u002Fcode> plus \u003Ccode>lastUpdate\u003C\u002Fcode>, along with \u003Ccode>experienceCount\u003C\u002Fcode>, \u003Ccode>skillCount\u003C\u002Fcode> and \u003Ccode>schoolCount\u003C\u002Fcode> so you can spot thin records too.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Filter it at the source.\u003C\u002Fstrong> \u003Ca href=\"\u002Fdocs\u002Fendpoints\u002Fsearch-people\">People search\u003C\u002Fa> and \u003Ca href=\"\u002Fdocs\u002Fendpoints\u002Fsearch-companies\">Company search\u003C\u002Fa> accept \u003Ccode>maxDataAgeDate\u003C\u002Fcode>, an ISO 8601 datetime that returns only profiles refreshed after that date. Prefer it over filtering stale rows in your own code after you paid for them.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Refresh on demand.\u003C\u002Fstrong> The live endpoints pull a fresh copy when the stored record is too old for your use case.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Pick an explicit threshold per use case instead of treating every record the same:\u003C\u002Fp>\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Use case\u003C\u002Fth>\n\u003Cth>Suggested threshold\u003C\u002Fth>\n\u003Cth>If older\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>Outbound sequence, meeting prep\u003C\u002Ftd>\n\u003Ctd>30 days\u003C\u002Ftd>\n\u003Ctd>Refresh live before use\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>CRM enrichment at scale\u003C\u002Ftd>\n\u003Ctd>90 days\u003C\u002Ftd>\n\u003Ctd>Refresh in your next batch\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>Analytics, market sizing\u003C\u002Ftd>\n\u003Ctd>180 days\u003C\u002Ftd>\n\u003Ctd>Usually fine as is\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>\u003Ca href=\"\u002Fdocs\u002Fguides\u002Fdata-freshness\">Data freshness\u003C\u002Fa> covers the Check → Fetch → Live pattern and what it saves.\u003C\u002Fp>\n\u003Ch2 id=\"3-can-i-act-on-it\">3. Can I act on it?\u003C\u002Fh2>\n\u003Cp>\u003Ca href=\"\u002Fdocs\u002Fendpoints\u002Fcontact-email\">Find Email\u003C\u002Fa> returns one entry per address discovered, each with a \u003Ccode>value\u003C\u002Fcode> and a \u003Ccode>type\u003C\u002Fcode>, typically \u003Ccode>professional\u003C\u002Fcode>. It is asynchronous: you get a \u003Ccode>webhookId\u003C\u002Fcode>, then retrieve the result by \u003Ca href=\"\u002Fdocs\u002Fguides\u002Fpolling\">polling\u003C\u002Fa> or through a \u003Ca href=\"\u002Fdocs\u002Fguides\u002Fwebhooks\">webhook callback\u003C\u002Fa>. When nothing is found you receive \u003Ccode>email-not-found\u003C\u002Fcode> rather than a constructed address.\u003C\u002Fp>\n\u003Cp>Two habits protect your sender reputation whatever the source of an address:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Keep your own suppression list authoritative.\u003C\u002Fstrong> Unsubscribes, complaints and hard bounces you have already recorded always win over a freshly discovered address.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Track bounce rate per input type.\u003C\u002Fstrong> If one segment bounces above your baseline, the input that produced it is usually the problem rather than the addresses themselves. Name-based lookups and URL-based lookups deserve separate counters, for the reason described earlier.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"markdown-alert markdown-alert-note\">\u003Cp class=\"markdown-alert-title\">\u003Cspan class=\"iconify i-ph-info docs-icon docs-markdown-icon\" aria-hidden=\"true\">\u003C\u002Fspan>Note\u003C\u002Fp>\u003Cp>An address that exists is not the same as an address you should contact. Consent and applicable regulations remain yours to handle: the API tells you what exists, not whom you may email.\u003C\u002Fp>\n\u003C\u002Fdiv>\n\u003Ch2 id=\"filters-that-fail-loudly\">Filters that fail loudly\u003C\u002Fh2>\n\u003Cp>Search deserves its own note, because a bad filter is the quietest way to get bad data. If we ignored a field we did not recognise, you would receive a full page of results drawn from a query you never asked for, and nothing in the response would look wrong.\u003C\u002Fp>\n\u003Cp>So the search endpoints validate every filter before the query runs. A rejected request returns \u003Ccode>422\u003C\u002Fcode>, lists every problem in \u003Ccode>error.details.errors\u003C\u002Fcode>, and costs 0 credits. Unknown field names, blank values, values outside a closed list such as \u003Ccode>currentCompanyIndustry\u003C\u002Fcode>, and inverted ranges are all refused rather than dropped. See \u003Ca href=\"\u002Fdocs\u002Fguides\u002Ferrors-retries#search-filter-validation\" target=\"_blank\" rel=\"noopener\">Search filter validation\u003C\u002Fa> for the complete list.\u003C\u002Fp>\n\u003Cp>Two behaviours to keep in mind when you read search results:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Title matching is token-based, not exact.\u003C\u002Fstrong> \u003Ccode>&quot;CTO&quot;\u003C\u002Fcode> also matches \u003Ccode>&quot;Deputy CTO&quot;\u003C\u002Fcode>. Multiple titles are OR-matched, and \u003Ccode>excludeCurrentPositionTitle\u003C\u002Fcode> always wins over a match.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Headcount is stored as fixed brackets.\u003C\u002Fstrong> A range only matches a bracket it fully contains, so bounds that fall inside a bracket return an empty array, and that request is still billed. Send bracket boundaries.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"when-a-profile-is-blocked\">When a profile is blocked\u003C\u002Fh2>\n\u003Cp>A profile removed under a GDPR or CCPA data subject request returns \u003Ccode>451\u003C\u002Fcode>. That state is permanent: the record is not stale, not missing, and will not come back. Retrying wastes calls, so treat \u003Ccode>451\u003C\u002Fcode> as a terminal outcome in your pipeline and drop the record from your enrichment queue rather than leaving it to be picked up on the next pass.\u003C\u002Fp>\n\u003Ch2 id=\"putting-it-together\">Putting it together\u003C\u002Fh2>\n\u003Cp>For a pipeline that writes to a system of record, this is the shape we recommend:\u003C\u002Fp>\n\u003Cdiv class=\"docs-code-group\">\u003Cdiv class=\"docs-code-group-header\">\u003Cspan class=\"docs-code-group-title\" data-static-title>Decision tree\u003C\u002Fspan>\u003Cdiv class=\"docs-code-group-actions\">\u003Cspan class=\"docs-code-group-lang\">Text\u003C\u002Fspan>\u003Cbutton type=\"button\" class=\"docs-code-group-copy\" data-copy-code=\"true\" aria-label=\"Copy code\">\u003Cspan class=\"iconify i-ph-copy docs-icon\" aria-hidden=\"true\">\u003C\u002Fspan>\u003C\u002Fbutton>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"docs-code-group-panel active\" data-tab=\"0\" role=\"tabpanel\">\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e\" tabindex=\"0\" data-language=\"bash\">\u003Ccode class=\"language-bash\">\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">For\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> each\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> lead:\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">  1.\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> Enrich\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> with\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> the\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> most\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> identifying\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> fields\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> you\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> have\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">     │\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">     ├─\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 404\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> (no \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">confident\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> match\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">     │\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">  └─\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> Retry\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> with\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> fewer\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> fields\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> →\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> route\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> the\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> result\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> to\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> review\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">     │\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">     └─\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> Match\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">        │\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">        ├─\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> alternativePersons\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> not\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> empty?\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">        │\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">  └─\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> Route\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> to\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> human\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> review,\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> do\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> not\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> write\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">        │\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">        └─\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> Unique\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> match\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">           │\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">           ├─\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> Store\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> the\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> id\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> (prs_...) and updateDate\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">           │\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">           └─\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> updateDate\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> outside\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> your\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> threshold?\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">              └─\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> Refresh\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> live\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> by\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> id,\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> then\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> write\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003C\u002Fdiv>\u003C\u002Fdiv>\n\u003Cp>The two gates that matter are the ambiguity gate and the freshness gate. Skipping either is how wrong data enters a CRM quietly.\u003C\u002Fp>\n\u003Ch2 id=\"quality-checklist\">Quality checklist\u003C\u002Fh2>\n\u003Cul>\n\u003Cli>Use Fetch when you already know the profile, and Enrich when you are still resolving who it is.\u003C\u002Fli>\n\u003Cli>Key your records on our \u003Ccode>id\u003C\u002Fcode>, not on a LinkedIn URL that its owner can rename.\u003C\u002Fli>\n\u003Cli>Never write a record with a non-empty \u003Ccode>alternativePersons\u003C\u002Fcode> array without review.\u003C\u002Fli>\n\u003Cli>Choose your input mix deliberately: certainty for automated writes, coverage for reviewed work.\u003C\u002Fli>\n\u003Cli>Store the freshness timestamp next to every record so you can audit staleness later.\u003C\u002Fli>\n\u003Cli>Set a staleness threshold per use case, filter with \u003Ccode>maxDataAgeDate\u003C\u002Fcode> on search, and refresh live above it.\u003C\u002Fli>\n\u003Cli>Treat \u003Ccode>404\u003C\u002Fcode> as a normal outcome to log and \u003Ccode>451\u003C\u002Fcode> as terminal, not as errors to retry blindly.\u003C\u002Fli>\n\u003Cli>Keep your suppression list ahead of any discovered address.\u003C\u002Fli>\n\u003Cli>Track match rate and bounce rate per input type. They tell you which inputs to improve.\u003C\u002Fli>\n\u003C\u002Ful>\n","","Bad data costs more than missing data. A wrong profile written to your CRM gets copied, reported on, and emailed before anyone notices. A missing profile is just a gap you can f...",9,[20,24,27,30,34,37,40,43,46,49,52,55,58,61],{"id":21,"title":22,"level":23},"a-not-found-is-a-real-answer","A not-found is a real answer",2,{"id":25,"title":26,"level":23},"three-questions-behind-every-response","Three questions behind every response",{"id":28,"title":29,"level":23},"1-is-this-the-right-person","1. Is this the right person?",{"id":31,"title":32,"level":33},"two-ways-to-identify-someone","Two ways to identify someone",3,{"id":35,"title":36,"level":33},"match-rate-and-certainty-pull-in-opposite-directions","Match rate and certainty pull in opposite directions",{"id":38,"title":39,"level":33},"a-linkedin-url-is-not-a-permanent-identifier","A LinkedIn URL is not a permanent identifier",{"id":41,"title":42,"level":33},"use-alternativepersons-as-an-ambiguity-flag","Use `alternativePersons` as an ambiguity flag",{"id":44,"title":45,"level":33},"current-position-is-a-decision-not-a-raw-field","Current position is a decision, not a raw field",{"id":47,"title":48,"level":23},"2-is-this-still-true","2. Is this still true?",{"id":50,"title":51,"level":23},"3-can-i-act-on-it","3. Can I act on it?",{"id":53,"title":54,"level":23},"filters-that-fail-loudly","Filters that fail loudly",{"id":56,"title":57,"level":23},"when-a-profile-is-blocked","When a profile is blocked",{"id":59,"title":60,"level":23},"putting-it-together","Putting it together",{"id":62,"title":63,"level":23},"quality-checklist","Quality checklist",[],[66,67,68,69,70],"\u002Fdocs\u002Fguides\u002Fdata-freshness","\u002Fdocs\u002Fendpoints\u002Fenrich-person","\u002Fdocs\u002Fguides\u002Fcurrent-position-resolution","\u002Fdocs\u002Fguides\u002Ferrors-retries","\u002Fdocs\u002Fguides\u002Fproduction-checklist",1790692476826]