Enrichment
What Connect knows about the company at a domain — what it does, what it looks like, and how visible it is online.
Three reads, each answering a different question about the business at a
domain. They are what makes "the account filled itself in" possible: a record
holding nothing but priya@acme.com can offer a name, an industry, a size
band and an address instead of an empty form.
| Call | Answers | Scope |
|---|---|---|
connect.enrichment.company() | What the business does | enrichment:read |
connect.enrichment.brand() | What it looks like | enrichment:brand |
connect.enrichment.presence() | How findable it is | enrichment:presence |
Three scopes rather than one, because the cost is not comparable: a company read fetches a few pages, a brand extraction crawls stylesheets and may render the site in a headless browser, and a presence scan is roughly twenty fetches across a site's well-known paths. A key holds only what you granted it in Console.
Measured, not recalled
Every value is read off the company's own website and comes back with the URL
it came from. A field the pages never stated is null — not a plausible
guess. Ask a model what a company does and it will answer confidently about a
site it never opened; that is the failure mode these endpoints exist to avoid.
Looking a company up
const result = await connect.enrichment.company({ email: "priya@acme.com" });
if (result.status === "enriched") {
prefill({ name: result.profile.name, industry: result.profile.industry });
}Send an email or a domain — whichever you hold. domain wins when both
are given.
It is a suggestion, not a save. Nothing is written to your store, and nothing should be written without the person in front of it agreeing. Render it as a prefill they can correct, which is how the same feature works everywhere else they have seen it.
Every call can decline
Each of the three answers a two-arm union, and the second arm is not an error:
if (result.status === "skipped") {
// result.reason — render the form empty and move on
}| Reason | Means |
|---|---|
no_domain | Nothing to look up |
consumer_domain | founder@gmail.com names a person, not an employer |
blocked_domain | Connect will not read this one |
unreachable | The site did not answer |
no_signal | The pages stated nothing usable (company, brand) |
no_stylesheet | Nothing to measure a brand from (brand) |
provider_unavailable | Connect did not answer (presence) |
unreachable and provider_unavailable are worth separating in your UI. The
first is a fact about the customer's site that they may want to know; the
second is our problem and is never something to tell them about their own
site.
A successful result also carries cached, which says whether Connect served
it from its platform-wide cache. The profile is identical either way — it is
there for your own telemetry, not for a decision.
Brand
const result = await connect.enrichment.brand({ domain: "acme.com" });
if (result.status === "extracted") {
const primary = result.profile.brandKit.palette.find((c) => c.role === "primary");
}Useful for white-labelling a portal to a customer's brand, pre-filling a design brief, or showing a real account card instead of a grey placeholder. Three things decide whether your integration works:
- Set your client timeout to at least 90 seconds. A cold read runs the whole extraction before answering and can take up to a minute. Many HTTP clients default to 30s and abandon a request that was about to succeed. Cached is the normal case — profiles are held for 90 days, so you pay the cold path roughly once per domain.
- Never store
brandKit.logos[].url. It is a short-lived signed URL minted per read. Render it directly or re-read the profile; persistsourceUrlif you need something stable. - Check
palette[].source. Acss-varentry is the company naming its own colour. Afrequencyentry is a ranked count of what appears most, and is much weaker evidence.
Presence
How visible the business is online, scored out of 100 — by a search engine, by a customer, and by the assistants that increasingly answer questions about a business before anyone visits its site.
const result = await connect.enrichment.presence({ domain: "acme.com" });
if (result.status === "scanned") {
for (const next of result.scan.recommendations) {
console.log(`${next.priority}. ${next.title} — ${next.fix}`);
}
}- The score is computed, not generated. Every point comes from a check run
against something observed, and each check ships with its weight, what was
seen, and the URL that settles it — so the total is reconstructable. A model
only orders
recommendationsand writes the prose; it cannot move the number. That is what makes this safe to show a customer. - A low score is a success, not a skip. Every check failing is the most useful report this produces.
socialis claimed, never verified. Those are URLs the site itself printed; nothing proves the accounts belong to that business, so render them as "linked from their site".machineReadable: falsemeans a real profile that search engines have to infer from a footer link — a specific, common and easily fixed finding.- Timeout ≥ 90 seconds, same as brand. Scans are held for a fortnight.
Refreshing, and who the call is attributed to
await connect.enrichment.company({ domain: "acme.com", refresh: true, asUser: sessionToken });refresh re-reads the site even when Connect holds a fresh result. It is for
an operator pressing Refresh — not for automatic calls, which should take
the cache and the latency that comes with it.
asUser takes a member's session token and attributes the generation to their
workspace; without it the call is attributed to your application. The token
travels as a header and never reaches the request body — it is a credential,
not a field.
Next steps
- API reference — the three endpoints, their request and response schemas, and the scope each needs.
- Reference —
EnrichmentResource, generated from source.