POST /v1/enrich and the enrichment profile
The enrichment lane on its own: who is this person, what company are they at. One shape regardless of which vendor answered, with a coverage figure that says how much of it actually came back.
Most callers never call this directly: /v1/decide runs the same lane lazily, only when a rule needs a field from it. Call /v1/enrich when you want the profile without a policy.
Request
curl -X POST https://api.sortance.com/v1/enrich \
-H "Authorization: Bearer $SORTANCE_KEY" \
-H "Content-Type: application/json" \
-d '{ "email": "mia@acme.com", "context": { "fields": ["company"], "max_cost_usd": 0.2 } }'| Field | Type | Meaning |
|---|---|---|
email | string | Required. |
context.fields | string[] | "person", "company" or both. Omitted means both. A group you do not ask for is never bought. |
context.max_cost_usd | number | Ceiling for this call. Default 0.10. |
context.max_latency_ms | number | Default 3000. |
context.strategy | enum | waterfall (default): ask the next vendor only when the last had no match, an error, or covered under half the fields. fanout: ask every eligible vendor in parallel and fill disagreement on every record — at the price of paying each one. |
What the lane does
- Cache. Person facts are cached under a keyed hash of the address and company facts under the domain, each for 30 days. A hit costs nothing.
- Company first, by domain. Company lookups send only the domain. A person lookup, which sends the address, runs only when you asked for person fields.
- Consumer mailboxes are skipped. A Gmail address gets neither lookup:
gmail.comis not a company and a person lookup on a personal address is the most privacy-sensitive, least useful thing this lane could do. The profile comes back with coverage 0. - Waterfall. People Data Labs is asked first; Apollo only when PDL had no match, failed, or covered under half the fields. Vendors bill per match, so asking both on every call would double the bill on every hit.
- Merge. When both answered, a field-level precedence picks the winner — Apollo for title, seniority and department; PDL for headcount; otherwise the fresher answer — and any difference is recorded in
disagreement. - Never exceed the ceiling. A lookup that would cross
max_cost_usdor start too late to finish insidemax_latency_msis skipped and its receipt saysskipped.
An upstream outage degrades the profile; it never fails the call. The affected group comes back null with coverage 0.
The profile
Every field is present on every response. Absent facts are null, never omitted and never guessed. The response is the profile with cost_usd, latency_ms and trace_id alongside, at the top level.
| Field | Type | Meaning |
|---|---|---|
person | object | null | null when no person lookup ran or matched. |
person.full_name, first_name, last_name | string | null | As the vendor gives them. Personal data: returned to you, never stored on a decision. |
person.title | string | null | The vendor's title text. Personal data, same rule. |
person.seniority | enum | null | c_suite, vp, director, manager, senior, entry, unknown. |
person.department | enum | null | engineering, product, design, marketing, growth, sales, customer_success, finance, legal, hr, operations, executive, other, unknown. |
person.linkedin_url | string | null | Personal data, same rule as the name. |
person.location | { country, region } | null | Country is kept on a decision; region is not. |
company | object | null | null when no company lookup ran or matched. |
company.name, domain | string | null | |
company.industry | enum | null | One of the normalized industry values. See the list in the policies reference. |
company.industry_raw | string | null | The vendor's own label, for display only. |
company.employees | number | null | The integer when given, otherwise the midpoint of employees_range. |
company.employees_range | string | null | The vendor's original range, e.g. 201-500. |
company.revenue_range | string | null | As the vendor reports it. |
company.founded | number | null | Year. |
company.country | string | null | ISO 3166-1 alpha-2, uppercase. |
company.type | enum | null | public, private, nonprofit, government, education, unknown. |
company.tech | string[] | Lowercased technology slugs. Empty when none were reported. |
company.linkedin_url | string | null | |
company.funding | { total_usd, last_round } | null | |
coverage | { person, company } | Fraction (0–1) of the fields you asked for that came back. A group you did not ask for, or that was skipped, reads 0. |
freshness | { person, company } | The newest date a vendor gave for that group, or null. |
sources | object | One receipt per vendor considered: { status, ms, cost, fields }. status is match, no_match, error, timeout or skipped. cost is what that vendor charged on this call. |
disagreement | object | Where two vendors answered the same field differently, keyed by path then vendor: { "company.employees": { "pdl": 240, "apollo": 180 } }. |
cache | { hit, ttl_s } | Whether this came from the 30-day cache. |
visitor | object | null | From a push source such as RB2B: { identified, first_seen, pages, source }. null when none. |
cost_usd | number | What this call cost, summed over sources. 0 on a cache hit. |
latency_ms | number | |
trace_id | string | en_-prefixed. |
A profile with company.industry: null at coverage 0.2 says almost nothing. Policies can read enrichment.coverage.person and enrichment.coverage.company directly, and an ICP score carries its own coverage for the same reason.
Not available yet
- Your own vendor keys. The adapters accept a key your account holds (and record its cost as 0), but there is no endpoint to store one yet. Clearbit is reachable only that way, so it is not reachable today.
- Choosing vendors per account. Every vendor configured on the deployment is eligible, in the order above.
- A monthly enrichment allowance.
402 enrichment_quota_exceededis reserved for it and is not returned today; each call is bounded only by its ownmax_cost_usd. Calls are not counted against the verification allowance. - A batch endpoint. One address per call.
Errors
| Status | Code | When |
|---|---|---|
400 | invalid_request | Malformed body or a missing address. |
401 | unauthorized | Missing, malformed or revoked key. |
429 | rate_limited | More than 600 requests a minute, counted together with verify, decide and the configuration endpoints. |
500 | internal_error | A bug. Carries the trace_id. |