Documentation

Person Profile API (v3)

How the v3 Person Profile endpoint works — identifiers, freshness tiers, stale fallbacks, response metadata, billing, and error handling.

The v3 Person Profile endpoint (GET /api/v3/person) looks up a person by a profile URL or another supported identifier and returns the enriched v3 person profile. Compared to v2, it replaces cache parameters with explicit freshness tiers, reports a freshness verdict on every response, and returns structured, machine-readable errors.

Making a request

Send a GET request to https://enrichlayer.com/api/v3/person with your API key in the Authorization header as a Bearer token, and exactly one identifier parameter:

ParameterDescription
profileA person profile URL to look up, in the form https://linkedin.com/in/example-user/.
emailAn email address to look up (any address known for the person).
githubA GitHub profile URL or username to look up.
facebookA Facebook profile URL to look up.
twitterA Twitter/X profile URL to look up.

A request without an identifier is rejected with a 400 and the error code invalidInput.

curl -G \
  -H "Authorization: Bearer ${ENRICHLAYER_API_KEY}" \
  'https://enrichlayer.com/api/v3/person' \
  --data-urlencode 'profile=https://linkedin.com/in/example-user/' \
  --data-urlencode 'freshnessTier=fresh'
bash

Choosing a freshness tier

Instead of v2's cache parameters, v3 lets you buy a freshness guarantee with the freshnessTier parameter:

TierGuarantee
liveForces a live source fetch. A cached profile is never returned as a success.
freshThe profile was validated within the last 7 days.
recentThe profile was validated within the last 15 days.
standardThe profile was validated within the last 30 days. This is the default.
cachedCache-only — never triggers a live fetch. The profile is guaranteed to be no older than 90 days.

If the cached profile already satisfies the requested tier, it is returned immediately. If it doesn't, Enrich Layer performs a live fetch to meet the guarantee.

Pick the loosest tier your use case tolerates: looser tiers are served from cache more often, which means faster responses and lower cost.

Falling back to stale data

By default, when you request fresh, recent, or standard and the live fetch fails, the request fails with a 503 or 504. Set allowStale=true to receive a clearly labeled stale profile instead — the newest cached profile, within the 90-day cached cap. allowStale defaults to false.

Two things allowStale does not do:

  • It does not override a 404. If the source reports the profile gone or inaccessible, you get the 404 — not stale data.
  • It has no effect with freshnessTier=cached, which never fetches in the first place.

Combining allowStale=true with freshnessTier=live is invalid and returns a 400 with the error code invalidFreshnessPolicy — live means "never a cached result", so a stale fallback would contradict the request.

Reading the freshness verdict

Every 200 response carries a meta.freshness object with the verdict and a meta.billing object with the charge. Branch on meta.freshness.met, not on the profile age:

FieldDescription
meta.freshness.tierThe freshness tier you requested.
meta.freshness.metThe primary machine-readable verdict: whether the delivered profile met the requested tier.
meta.freshness.profileAgeDaysProfile age in whole days, rounded up. 0 means the profile was served live.
meta.freshness.fallbackPresent only when stale data was returned because allowStale=true. Always "stale".
meta.billing.chargedTierThe tier actually charged. Lower than the requested tier when a stale fallback was delivered.
meta.billing.creditsChargedThe number of credits charged for this response.

A request that met its tier reports:

{
  "tier": "standard",
  "met": true,
  "profileAgeDays": 12
}
json

A stale fallback is labeled met: false with fallback: "stale", and the response also carries a meta.warnings entry with the code servedStaleFallback:

{
  "tier": "fresh",
  "met": false,
  "profileAgeDays": 42,
  "fallback": "stale"
}
json

meta.warnings is a human-readable notice list — the meta.freshness object is the machine contract.

Response headers

Four response headers mirror the verdict, so you can route on it without parsing the body. The body is canonical; the headers are a convenience.

HeaderMirrors
EnrichLayer-Freshness-Tiermeta.freshness.tier
EnrichLayer-Freshness-Metmeta.freshness.met
EnrichLayer-Profile-Age-Daysmeta.freshness.profileAgeDays
EnrichLayer-Charged-Tiermeta.billing.chargedTier

Every response also carries an x-request-id header — quote it when reporting issues.

How credits are charged

Billing follows the delivered contract, not the requested one:

  • When the response meets the requested tier (met: true), you are charged for that tier.
  • A stale fallback is charged as the cached tier — you never pay a fresh-tier price for stale data.
  • Non-2xx responses carry no profile-data charge.

The actual charge is always reported in meta.billing.creditsCharged. Per-tier pricing is listed on the pricing page.

Response sections

The base sections — location, experience, accomplishments, publications, and patents — are always included. Extra sections are gated by two mutually exclusive parameters:

ParameterDescription
sectionsComma-separated extra sections to include: contactInformation, estimatedSalary, seniority, developerProfile, skills. Cannot be combined with exclude.
excludeComma-separated extra sections to omit from the same list. Base sections cannot be excluded. Cannot be combined with sections.

Individual enrichments are controlled by include/exclude flags. Each defaults to exclude:

ParameterDescription
extraInclude extra profile details.
personal_emailInclude personal emails.
personal_contact_numberInclude personal contact numbers.
github_profile_idInclude the GitHub profile id, when resolvable.
facebook_profile_idInclude the Facebook profile id, when resolvable.
twitter_profile_idInclude the Twitter/X profile id, when resolvable.
inferred_salaryInclude the inferred salary range.
skillsInclude the skills list.
github_dataInclude the GitHub developer profile section.

Handling errors

Errors are returned as RFC 9457 problem details (application/problem+json). Every error body carries a stable machine-readable code — branch on it, not on the HTTP status alone:

StatusCodeMeaning
400invalidInputThe request parameters are invalid — for example, no identifier was provided.
400invalidFreshnessPolicyThe tier/policy combination is invalid — for example, allowStale=true with freshnessTier=live.
404profileNotFoundNo profile exists for the identifier.
404profileInaccessibleThe source reports the profile gone or not accessible as a profile.
409cacheMissfreshnessTier=cached and no cached profile exists.
409cacheExpiredfreshnessTier=cached and the cached profile exceeds the 90-day guarantee.
500internalErrorSomething went wrong on our end.
503profileFreshnessUnavailableStrict freshness was requested and the source is unavailable. Retryable.
504profileFreshnessTimeoutStrict freshness was requested and the source timed out. Retryable.

A 404 is always a data-side outcome — the profile doesn't exist or can't be accessed at the source. Policy failures with freshnessTier=cached deliberately return 409, not 404: the profile may well exist, but the cache can't satisfy the guarantee you asked for. When that happens, retry with a tier that is allowed to fetch.

A 409 cacheExpired body looks like this:

{
  "type": "https://enrichlayer.com/docs/problems/cache-expired",
  "title": "Cached profile unavailable",
  "status": 409,
  "code": "cacheExpired",
  "detail": "A cached profile exists, but it is older than the 90-day cached tier guarantee.",
  "requestId": "8f14e45fceea167a5a36dedd4bea2543",
  "requestedFreshness": { "tier": "cached" },
  "lastKnownProfile": { "available": true, "profileAgeDays": 143 }
}
json

503 and 504 responses set retryable: true — retrying the same request may succeed. If you would rather receive stale data than retry, set allowStale=true.

Migrating from v2

The v2 cache parameters do not map one-to-one onto v3 — in particular, there is no v3 equivalent of use_cache=if-present (return any cached result regardless of age). The closest tier, cached, never triggers a fetch and enforces a 90-day cap.

v2v3
profile_urlprofile
live_fetch=forcefreshnessTier=live
fallback_to_cache=neverallowStale=false (the default)
fallback_to_cache=on-errorallowStale=true
use_cache=if-presentNo direct equivalent — see above.
max_age_days (earlier v3 draft)freshnessTier=standard (the default), or one of the named tiers.