Skip to content

Contact.enrich

Fill a Contact’s missing title, employer, seniority and location from outside sources, each value marked with where and when it was read.

Price
20¢ per 100 contacts
Success rate
99.99% of 136,987 calls
Freshness
Fresh for 90 days
Emits
Contact.enriched
Capability
Enrichment
POST https://api.sb/{your-domain}/contacts/enrich

Options and prices

You getInput that selects itPriceSuccess rate
Title, employer, seniority and location{"fields":["title","organization","seniority","location"]}About 20¢ per 100 contacts99.99% of 136,987 calls
The full profile with past roles{"fields":["history"]}About 64¢ per 100 contacts99.98% of 200,979 calls

Inputs

NameTypeDefaultMeaning
contactContactRequiredA saved Contact id. Also served at /contacts/{contact}/enrich.
fields('title' | 'organization' | 'seniority' | 'location' | 'profiles' | 'history')[]every empty fieldWhat to fill. A field you already hold is skipped and costs nothing.
maxAgestring'90d'A value read more recently than this is kept, at $0, instead of reading again.
maxCostnumber0.05The most this Contact may spend, in USD. Over it, the call is refused before it spends, or stops early with what it has.

Outputs

FieldTypeWhat null means
contactContactNever null: the Contact the values were written onto.
filledstring[]Never null: the fields this call filled; empty when no source knew the person, at $0.
valuesRecord<string, { value: string | number | null; observedAt: string; method: Observed['method'] }>Never null: one entry per filled field, each with when and how it was read.
values.organization.valueHostnameNo current employer on record.
values.seniority.value'owner' | 'c-level' | 'vp' | 'director' | 'manager' | 'individual'Unknown.
costCost, in USDNever null. In try mode usd is 0 and maxUsd is what the call would have cost.

Read the result

FieldValueWhat it meansWhat to do
values.organizationNot the company the Contact was found atThey changed jobs. The old address will bounce or reach whoever inherited the inbox.Stop their Sequence and find them again at the new company; a new job is a reason to write. (the job-change rule for cold email)
filledEmpty from an email address aloneAn email resolves a person less often than a profile URL: about 72% across every kind of identity, against 99.99% from a profile URL.Save the profile URL Prospect.find returns and enrich from that. (measured across 394,379 routed calls)
values.*.observedAtOlder than 90 daysTitles drift: a quarter-old title is a guess about today.Read it again before a first email that quotes the title. (our planning number)

Effects

Action class

None: no approval rule names it, so it runs within its seat’s Permissions and Limits

Modes
  • try: Reads the values and returns them; nothing is written onto the Contact and nothing is billed.
  • send: Returns a pending Action at once; its Event carries the rows when the read finishes.
  • do: Waits for the read and returns the rows, billed per result.
Idempotency

The same Idempotency-Key with the same input replays the first answer at $0; with other input it returns 409.

Saved or shared

Writes each filled value onto the Contact with where and when it was read; a value you entered is never overwritten. Readings about people are never shared across startups.

Emits

Contact.enriched with { contact, filled, values }

Performed by

Prospector

Price

About 20¢ per 100 contacts. Free: a Contact no source knows, fields you already hold. maxCost defaults to 5¢. Every Contact in a 300-person list: 300 × 0.2¢ a contact is about 60¢.

Reliability

The first route succeeds 99.99% of 136,987 calls. If it fails, a second route answers at 99.98% of 200,979 calls, and you pay for one.

Freshness

A reading younger than 90 days returns at $0. Readings about people are never shared across startups.

Batch and pages

Up to 1 Contact a call. It answers in one response.

Rate

The shared rate rules: 10 calls a second per key, and an array input counts as one call.

Refuses when

This call has no refusals of its own. Every data call can also meet the shared data-call refusals: filter-unsupported, input-missing, limit-exceeded, max-cost, rate-limited, sources-busy, venue-unsupported.

When it waits

Returns in one response; nothing waits.

Example

Text
Fill in what we know about Dana Whitfield before I write to her; spend
up to 1¢.

To retry, send the same Idempotency-Key: the retry returns the first answer for $0. A new key reads again.

Test values, and watching it

A sk_test_ key answers these the same way every time, at $0 (every test value).

Test valueAlways returnsWhat it is for
maxCost: 0.0001Refuses with max-costAny priced call with a maxCost of 0.0001 is refused before it spends: the price is above the cap.
rate-limited.testRefuses with rate-limitedAny call naming this hostname answers 429 with Retry-After, so your retry logic runs.
sources-busy.testRefuses with sources-busyAny call naming this hostname answers 503 as if every route were busy, and nothing is spent.

Watch it on a schedule: a Monitor runs this call and records an Event only when the reading changes.

TypeScript
await $.Monitor.create({
  verb: 'Contact.enrich',
  input: { contact: 'contact_FdmRdAKcbV', fields: ['organization', 'title'] },
  change: 'job-change',
  every: '4w',
})

Used by