Primitive/people4 min read

Introducing /people: B2B people search and contact enrichment for agents

Search real people by title, company, industry, geography and seniority — then enrich a known identity into a work profile. Priced on what the provider run really cost, work-email only, and priced off the run rather than the row count.

People primitive →Read the docs →

/people /people /people /people /people /people /people /people /people /people
/people /people /people /people /people /people /people /people /people /people
/people /people /people /people /people /people /people /people /people /people
/people /people /people /people /people /people /people /people /people /people
/people /people /people /people /people /people /people /people /people /people
/people /people /people /people /people /people /people /people /people /people
/people /people /people /people /people /people /people /people /people /people
/people /people /people /people /people /people /people /people /people /people
/people /people /people /people /people /people /people /people /people /people
/people /people /people /people /people /people /people /people /people /people
/people /people /people /people /people /people /people /people /people /people
/people /people /people /people /people /people /people /people /people /people
/people /people /people /people /people /people /people /people /people /people
/people /people /people /people /people /people /people /people /people /people
Primitive/people
TL;DR
  • /people search B2B leads by title, company, industry, geo and seniority, or enrich an identity you already hold
  • Priced on 3× the provider run's real spend a search matching nothing is free, and the endpoint you choose does not change the rate
  • Work details only home addresses and personal phone numbers are dropped in the adapter, not filtered at the route
  • Email deliverability is reported honestly: a catch-all domain reads domain_only, never valid
  • Opt-in per AccountKit an absent entry denies, so a fresh workspace cannot use it by accident
  • Sync calls store nothing; async batches prune at 90 days

Agents that do sales work keep hitting the same wall: they can reason about who to contact, but they cannot find out who exists. /people closes that gap with two operations.

Search finds candidates; enrich resolves identities

The distinction is whether you already know who you want.

Search is a query over a population — filters in, candidate list out. Enrich is a lookup of one known entity — an identifier in, that person's record out. They are not two steps of a pipeline. Enrich is what you run over a list you already own: rows from your CRM, a form fill, a LinkedIn URL an agent found.

naive people search --title "VP Engineering" --company stripe --limit 5
naive people enrich --linkedin linkedin.com/in/patrickcollison --include-contact

Priced on what the run cost

Both endpoints bill 3× the provider spend the request actually incurred, read back off the run. A search matching nothing is free, and so is an enrich that finds nobody — the run's cost in that case is ours.

This matters more than it sounds. The obvious design — a flat fee per search — is a loss the moment a caller asks for more than a couple of results, and agents always ask for the maximum. A flat fee is only safe when the upstream cost is flat, and per-record data is not.

Deriving both endpoints from the same real spend also removes an arbitrage an earlier list-price design had: search returning work emails more cheaply than enrich made it a 2.5× cheaper substitute for the same data, so a rational agent would never have called enrich at all. Now the price follows the work, and the endpoint you pick does not change it.

Narrowing happens before metering, too. Rows a seniority filter removes, empty records, and duplicates of the same person are all dropped before the meter runs.

Work details only, enforced in the adapter

The lead dataset behind search carries street address, city, state and postal code for a named individual. None of it is read.

The filter lives in the adapter rather than at the route, so no downstream caller can reach the excluded fields by accident. The type that describes a provider record is the allowlist: a field absent from it cannot reach you. phone stays on the response schema and is never populated, because person-level phone numbers are personal numbers.

That boundary is what keeps this a B2B tool rather than a consumer dossier — the same API call, categorically different regulatory exposure.

Deliverability is reported, not assumed

A work email comes back with an email_verification block that says what was actually established. The most common honest answer is domain_only: the domain accepts mail for any local part, so the address existing proves very little. mailbox_verified is true only when a mailbox was really probed, and null when it was not.

The failure mode we care about most is the quiet one. Verification used to swallow every provider exception, which meant an unpaid invoice at the provider returned unverified for every address indefinitely and looked healthy. Account-level faults now propagate as errors. A lapsed credential is an outage, not a data quality trend.

What it does not do

Honest limits, because discovering them mid-integration is worse than reading them here:

  • No name-only search. Filters are title, company, industry, geo, seniority. Names go through enrich, with a company as the disambiguator.
  • Email-only and domain-only enrich do not resolve. The enrich actor keys on LinkedIn identity. Both shapes stay on the request contract and are refused with a hint rather than answered empty.
  • No match_likelihood. The current provider returns a profile or nothing and scores neither, so the field stays absent instead of carrying a number we invented.
  • No phone numbers, by design.
  • One provider, no waterfall. A miss returns null, so match rates are that provider's match rates.
  • Not a consumer report. Employment, credit, insurance and housing decisions are prohibited under Terms Section 18, regardless of how the data is combined.

Opt-in, and default-deny

people is disabled on a fresh workspace, and an absent AccountKit entry denies rather than defaulting open — the same gate over MCP as over HTTP, so naive_people_* is refused under exactly the conditions /v1/people/* is.

Retention is close to nothing

Sync search and enrich store nothing — records are normalized, returned, and dropped. Async batches are the only persistence, and they prune at 90 days. The audit log records who ran a lookup, never who was looked up.

POST /v1/people/erasure removes a subject from stored results, unmetered — charging for a deletion request would give a tenant a reason to delay one. It does not reach the upstream provider, and the response says so.

Full reference: People docs. Current gaps, in one place: what is not done.

Frequently Asked Questions
What is /people?+
The Naïve primitive for B2B people search and contact enrichment. Search finds candidates matching a filter; enrich resolves an identity you already hold into a work profile.
Can I search for someone by name?+
Not via search — its filters are title, company, industry, geo and seniority. Use enrich with a name plus a company; a bare name cannot be disambiguated.
What personal data does it return?+
A work email, employer, title, LinkedIn URL and firmographics. It does not return a home address, a personal phone number, date of birth or consumer attributes, even though the upstream dataset carries some of them.
Is it on by default?+
No. people is opt-in per AccountKit and an absent entry denies rather than defaulting open, so an admin must enable it deliberately.
A
AshleyEngineering

Engineering at Naïve. Owns the data primitives.

@ashley_naive