enrichmentapi
v0.1.0
Published
Node SDK for EnrichmentAPI's enrichment endpoints (person, company, email finder/verifier, employee finder, funding).
Maintainers
Readme
enrichmentapi
Node SDK for EnrichmentAPI — typed methods for its 8 enrichment endpoints,
sane per-endpoint timeouts, and consistent error handling, so you don't have to hand-roll fetch calls,
api_key query params, and ad-hoc error checks yourself.
This is a thin client against the public https://api.enrichmentapi.io API — it does not depend on or
import the EnrichmentAPI server codebase, Mongo, or any of its internal env vars. It's a standalone package.
Install
npm install enrichmentapiUsage
import { EnrichmentAPIClient, EnrichmentAPIError } from "enrichmentapi";
const client = new EnrichmentAPIClient(process.env.ENRICHMENTAPI_KEY);
try {
const domain = await client.companyToDomain("stripe");
console.log(domain);
} catch (err) {
if (err instanceof EnrichmentAPIError) {
console.error(`Request failed (${err.statusCode}): ${err.message}`);
} else {
throw err;
}
}Get your API key from your account dashboard at enrichmentapi.io.
Examples
Verified live against the real API (person/company/employeeFinder spend credits on your account —
they hit a live LinkedIn scrape/search on a cache miss, the others don't):
await client.person("https://www.linkedin.com/in/satyanadella");
// -> { name, first_name, last_name, headline, current_company, current_role, location, about, education, ... }
await client.company("https://www.linkedin.com/company/stripe");
// -> [{ company_name, universal_name_id, industry, location, follower_count, about, ... }] <- note: an ARRAY
await client.employeeFinder("Stripe", { domain: "stripe.com", limit: 3 });
// -> { success: true, company: "Stripe", people: [{ linkedin_url, slug, name, title, description }, ...] }
await client.emailFinder("satya", "nadella", "microsoft.com");
// -> { email: "[email protected]", find_mail: true, is_reachable: true }
await client.verifyEmail("[email protected]");
// -> { email, isReachable: "invalid" | "safe" | "risky" | ..., confidence_level, image, references }
await client.reverseEmail({ firstName: "Bill", lastName: "Gates" });
// -> { people: [{ fullName, first_name, last_name, public_identifier, profile_url, headline, ... }, ...] }
await client.companyToDomain("microsoft");
// -> { success: true, company: "microsoft", websites: [{ domain, image, isVirtual }, ...] }
await client.companyFunding("stripe.com");
// -> { company, domain, total_raised, currency, valuation, rounds: [{ series, amount, date_month, date_year, investors, ... }] }Methods
| Method | Endpoint | Purpose |
|---|---|---|
| person(profileId) | GET /person | LinkedIn profile enrichment |
| company(companyId, opts?) | GET /company | LinkedIn company enrichment |
| employeeFinder(company, opts?) | GET /employee_finder | Find employees at a company |
| emailFinder(firstName, lastName, domain) | GET /email_finder | Guess a work email from name + domain |
| verifyEmail(email) | GET /verify_email | Verify email deliverability |
| reverseEmail({ email } \| { firstName, lastName }) | GET /reverse_email | Find a person from an email or name |
| companyToDomain(company) | GET /company_to_domain | Resolve a company name to its domain |
| companyFunding(domain) | GET /company_funding | Company funding/investment data |
Not included: /tech_stack (currently returns a hardcoded 503 Under Maintenance in EnrichmentAPI itself),
and the older /employees / /investment / /email routes, which are superseded by the endpoints above.
person and company use a longer timeout (70s) than the other methods (20s) — both hit a live LinkedIn
scrape upstream on a cache miss.
Error handling
Every failure — a non-2xx HTTP status, an explicit {success:false} response body (EnrichmentAPI reports
some failures this way even on 2xx-adjacent paths), a request timeout, or a non-JSON response — throws an
EnrichmentAPIError with:
message— human-readable failure reasonstatusCode— HTTP status, or0if no HTTP response was ever received (timeout, network failure, or client-side validation like an incompletereverseEmailquery)body— the parsed response body, if one was received
On success, methods resolve with the parsed JSON response body as-is. The response shape is not uniform across endpoints, or even within one endpoint:
- A cached
/personhit returns the raw record with nosuccessfield at all, while/company_to_domainand/employee_finderincludesuccess: true. company()'s response is a JSON array of one record, not an object — confirmed live (see Examples above) — while every other method returns a plain object.
There's no published schema to type response bodies against yet, so they're typed as unknown — narrow/
validate on your end if you need guarantees beyond "this is valid JSON from a successful call."
Testing
npm test # unit tests against a mocked fetch, no network calls or API key needed
npm run smoke # one live call against the real API -- needs ENRICHMENTAPI_KEYFor the smoke test:
cp example.env .env # then edit .env and set ENRICHMENTAPI_KEY
npm run build
npm run smokeDevelopment
src/
client.ts # EnrichmentAPIClient class + private request() helper
errors.ts # EnrichmentAPIError
types.ts # per-endpoint option types
index.ts # public exports
test/
client.test.ts # unit tests, mocked fetch
scripts/
smoke-test.mjs # end-to-end connectivity + live-call check, see Testing abovenpm run build # tsup -> dist/ (ESM + CJS + .d.ts)
npm testTroubleshooting
Process crashes with Assertion failed: !(handle->flags & UV_HANDLE_CLOSING) (Windows only) — happens if your
script calls process.exit() immediately after an SDK call resolves. The client uses AbortSignal.timeout()
internally, and on Windows, forcing an immediate exit can race libuv's cleanup of that timer's handle. Set
process.exitCode instead and let the script finish naturally (as scripts/smoke-test.mjs does) rather than
calling process.exit() directly.
verifyEmail times out under its default 20s timeout — seen live against the real API; the endpoint can run
a live SMTP-style check that occasionally exceeds 20s. This matches enrichmentapi-mcp's default timeout for
the same endpoint (parity, not a bug in this SDK) — if it's a recurring problem, that's worth raising with
EnrichmentAPI directly rather than just retrying client-side.
