The whole thing in one call
A lookup returns every field you asked for with its own state and check date, plus the credits actually charged. A miss is an HTTP 200 with match: false and credits_charged: 0.
curl https://api.heyenrich.io/v1/lookup \
-H "Authorization: Bearer he_live_…" \
-H "Content-Type: application/json" \
-d '{ "linkedin_url": "https://linkedin.com/in/priya-raman-example",
"fields": ["work_email", "personal_email", "mobile"] }'
{
"match": true,
"name": "Priya Raman",
"title": "Head of Growth",
"company": { "name": "Northwind Traders", "domain": "northwind-traders.example",
"headcount_band": "51-200", "industry": "Logistics" },
"work_email": { "value": "p.raman@northwind-traders.example",
"state": "verified", "checked_at": "2026-10-18" },
"personal_email": { "value": "pr.raman@gmail.com",
"state": "verified", "checked_at": "2026-09-30" },
"mobile": { "value": "+15125550141", "line_type": "mobile",
"state": "verified", "checked_at": "2026-10-02" },
"credits_charged": 8,
"latency_ms": 171
}Credits per endpoint
One table, every endpoint, including what a miss costs. Rate limits are per account and shared across sub-keys.
| Endpoint | Does | Credits | On a miss | Rate limit |
|---|---|---|---|---|
| POST /v1/lookup | One person from a LinkedIn URL, name + domain, name + location, or work email | 1 work, 2 personal, 5 mobile | 0 | 60 to 1,000 rpm by plan |
| POST /v1/bulk | Up to 100,000 rows; job id back; poll or webhook | Same per filled field | 0 | Queued, not rate-limited |
| GET /v1/company/{domain} | Email pattern with share, catch-all, MX provider, headcount band | 1, or free with a person match | 0 | Same as lookup |
| POST /v1/verify | Check an address you already have | 0.5 | 0 | Same as lookup |
| GET /v1/credits | Balance, usage, rollover expiry | 0 | 0 | Unlimited |
Measured, not claimed
median /v1/lookup, 30 days, at our edge
uptime target, published on status.heyenrich.io
credits charged by he_test_ keys, ever
Behaviour worth knowing
A miss is a 200, not a 404
match: false and credits_charged: 0. Your pipeline doesn't need a special case.
Ask only for the fields you'll use
fields: ["work_email"] means you're never charged for a mobile you didn't want.
Idempotent bulk
Pass an Idempotency-Key on /v1/bulk and a retried upload won't run twice.
Sub-keys for agencies
Keys scoped to a client workspace with their own limits. Usage shows in your dashboard; the client never sees your main key.
Opt-outs by webhook
Subscribe to person.suppressed and delete the record on your side. Required if you store what we return.
Sandbox
Keys prefixed he_test_ return the three sample people from the homepage and never charge.
Keys on the first screen after signup
No form, no call. Create a key, make a lookup, read the states.
API questions
What do the states mean?
verified: resolved at lookup time. risky: the domain accepts every address, so the value may or may not exist; charged and labelled. notfound: nothing on file; not charged.
Is there an MCP server?
Not yet. It ships when it works; we'd rather not list it until then. The REST API is simple enough that an agent can call it with one tool definition.
Can I use it from Clay?
Yes. Add an HTTP API column pointing at /v1/lookup with your key in the header. The walkthrough, with the exact body and column mappings, is on /integrations/clay.
Rate limits?
60 requests a minute on Starter, 300 on Growth; Custom plans set their own. Bulk jobs don't count. Headers tell you what's left.
Which SDKs exist?
None yet, on purpose. It's one endpoint with JSON in and out. The reference has curl, Node and Python samples for every call.