Enrich profiles, detailed

The profile record plus the sections you tick: counts, role detail, skills, interests, recommendations. One credit, +0.5 each.

POSThttps://api.easydata.win/v1/profiles/enrich/detailed
1  per targetone profile fetch, plus one per section you asked for at half each

The same record as Enrich profiles, out of the same first request, plus whichever optional sections you tick. One credit for the profile and +0.5 for each section.

A LinkedIn profile is not one document. The record behind Enrich profiles is one request and carries identity, badges, location, summary, skills, education, certifications, languages and the skeleton of every position. The rest of what a person's page shows sits behind further requests, and each of those is what a section is.

At least one section is required. With none of them set this is Enrich profiles under a longer name at the same price, so it is refused with 400 invalid_request naming the cheaper path.

A section that did not answer is not charged for. Every section is best-effort: the profile is owed the moment the first request lands, and a section LinkedIn refused leaves its fields absent and its half credit uncharged. So one target costs between 1 and 3.5, and credits_used on each result entry is the exact figure for that one.

A section is a list in three states. skill_details, interests and recommendations are absent when you did not ask for them, [] when you did and this member has none, and populated otherwise - and only the last two were charged for, so the difference is one you can bill against.

find_emails works here exactly as it does on Enrich profiles, for the same flat +4 per person we reach a verdict about.

Need it in the response?POST https://api.easydata.win/v1/profiles/enrich/detailed/sync runs the same operation inside your request and hands back the record, for 2per target - twice the price above, because it outranks every queued batch for a worker and holds one open while you wait. It takes target, one entity, never a targets array. See Synchronous requests.

Request #

FieldTypeNotes
targetsrequiredarrayWhat to look up. Each entry is a bare string - read as a URL or a public identifier - or an object. At most 50,000 per submission; splitting a larger job into several batches costs nothing.
include_countsbooleanFill follower_count and connection_count. +0.5. They live on the profile top card, which is a separate request - no profile record carries either, which is why this is a flag and not part of the record. connection_count is LinkedIn's own reported total and caps at 500.
include_skill_detailsbooleanFill skill_details: every skill with how many people endorsed it and the lines LinkedIn renders underneath. +0.5. A superset of skills, not a replacement - the record's flat list stops at 20 and this one does not, so both are returned. endorsements is read off the rendered line and is 0 when that line names its endorsers instead of counting them, in which case the line is in insights.
include_interestsbooleanFill interests: the people, companies, groups, newsletters and schools this member follows. +0.5. One request whatever they follow - LinkedIn renders the section as tabs and sends every tab in the one response, so nothing is paged and a member with five categories costs what a member with one costs. category is LinkedIn's own token rather than the tab label, so it does not change with the language a page was rendered in.
include_recommendationsbooleanFill recommendations: written recommendations in both directions, in one request. +0.5. direction is received when somebody wrote it about this member and given when this member wrote it about somebody else, and name is always the other person - the author on a received one, the subject on a given one.
include_experience_detailbooleanFill the six per-position fields the record leaves off: description, location, workplace_type, employment_type, skills and tenure_months. +0.5. They come from the profile's experience section, which is a separate request and a second view of the same roles - it carries the free text under a role and where the role was held, and none of the structured dates or company facts you already have. The two views are merged on company and title, so a person with two roles at one employer gets each role's own description.
find_emailsbooleanLook up a work email address for every person this returns, and fold it in under emails. Adds a flat +4 per person we reach a verdict about - an address, an address on a catch-all domain, or an honest "this domain has no mailbox for them". A person we could not look up at all comes back with emails.status: "unresolved" and a reason, and is not charged. Flat whatever it took: the price does not move with how much work it was.
targets[].domainstringOptional, per target: the company mail domain this person's address lives on, as { "url": "...", "domain": "acme.com" }. Only read when find_emails is set. It skips resolving their employer, which makes the lookup faster and more accurate when your own data already knows the answer, and it changes nothing about the price.
external_idstringYour own correlation handle. It comes back on the batch, on the completion webhook and in the usage rollup, so spend can be attributed to your pipeline rather than only to ours.
callback_urlstringWhere to deliver batch.completed. Optional - polling is a first-class path, not a fallback. Validated when you send it and again when we dial it. Mutually exclusive with webhook_tag.
webhook_tagstringRoute the completion to the endpoints carrying this tag instead of to the untagged ones, so you can name a destination without knowing its URL. A tag matching no enabled endpoint is refused here rather than discovered as a webhook that never fired.

Call it #

cURL
curl -X POST "https://api.easydata.win/v1/profiles/enrich/detailed" \
  -H "X-API-Key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"targets":["https://linkedin.com/in/satyanadella"],"include_counts":true,"include_experience_detail":true,"include_skill_details":true,"include_interests":true,"include_recommendations":true,"external_id":"crm-sync-2026-09-20"}'

Response #

JSON
{
  "data": {
    "batch_id": "afec9131-080b-4b96-b3bb-726249e4bbf8",
    "operation": "profiles.enrich.detailed",
    "status": "queued",
    "total": 2,
    "succeeded": 0,
    "failed": 0,
    "pending": 2,
    "results_available": 0,
    "credits_used": 0,
    "external_id": "crm-sync-2026-09-01",
    "created_at": "2026-09-01T10:00:00Z",
    "completed_at": null,
    "recommended_poll_ms": 2000
  },
  "meta": { "requestId": "req_8f2ka91x", "recommendedPollMs": 2000 }
}

A result entry #

Once the batch has drained, this is what reading its results gives you.

JSON
{
  "item_index": 0,
  "input": "https://linkedin.com/in/satyanadella",
  "status": "succeeded",
  "credits_used": 2,
  "data": {
    "public_identifier": "satyanadella",
    "urn": "ACoAAA8BYqEBCGLg_vT9kbxlBWbLnLGVsBWc_pI",
    "member_id": 19186432,
    "url": "https://www.linkedin.com/in/satyanadella",
    "first_name": "Satya",
    "last_name": "Nadella",
    "full_name": "Satya Nadella",
    "headline": "Chairman and CEO at Microsoft",
    "avatar_url": "https://media.licdn.com/dms/image/...",
    "premium": false,
    "influencer": true,
    "creator": false,
    "top_voice": false,
    "summary": "Chairman and CEO of Microsoft. Focused on ...",
    "location": "Redmond, Washington, United States",
    "open_to_work": false,
    "follower_count": 14231890,
    "connection_count": 500,
    "birth_date": { "month": 8, "day": 19 },
    "locale_country": "US",
    "locale_language": "en",
    "skills": ["Cloud Computing", "Enterprise Software", "Business Strategy"],
    "positions": [
      {
        "title": "Chairman and CEO",
        "description": "As chairman and CEO, I am focused on empowering ...",
        "current": true,
        "employment_type": "Full-time",
        "location": "Redmond, Washington, United States",
        "workplace_type": "Hybrid",
        "skills": ["Leadership", "Cloud Computing"],
        "company_name": "Microsoft",
        "company_id": 1035,
        "company_url": "https://www.linkedin.com/company/1035",
        "company_industry": "Software Development",
        "company_employee_range": "10001+",
        "company_logo_url": "https://media.licdn.com/dms/image/...",
        "started_on": { "year": 2014, "month": 2 },
        "tenure_months": 151
      },
      { "...": "Position" }
    ],
    "educations": [ { "...": "Education" } ],
    "certifications": [ { "...": "Certification" } ],
    "languages": [
      { "name": "English", "proficiency": "NATIVE_OR_BILINGUAL" }
    ],
    "scraped_at": "2026-09-20T10:00:04Z"
  },
  "created_at": "2026-09-20T10:00:04Z"
}

credits_used is per entry and tells you exactly what landed. A target that got the profile and every section is 3.5; one whose experience section was refused is 3; one whose profile could not be fetched at all is 0. For the three list sections you can also read it off the record: skill_details, interests and recommendations are absent when you did not ask and [] when you asked and the member has none, so a section that was fetched is visible whether or not it found anything. The two scalar sections have no such tell - a member who wrote no descriptions looks like one whose section we could not read, which is the honest answer in both directions.

The sections go out together, after the profile has landed, so asking for both costs one extra round trip rather than two. On /sync that matters: a request that ticks everything is slower than one that ticks nothing, and every price on it doubles - 2 for the profile and +1 per section.

Errors #

StatusTypeWhen
400invalid_requestThe body could not be accepted as sent. `error.field` names what was wrong. A field this reference does not list is one of these: an unknown key is refused rather than accepted and ignored, because a typo that is silently dropped is indistinguishable from one that worked.
401invalid_api_keyMissing, malformed, revoked or expired key. Never metered.
403quota_exhaustedThis month's allowance cannot cover the submission. Nothing is reserved and nothing is charged.
403email_unverifiedNobody on the account has confirmed their email address, and the account is on the default allowance rather than an agreed one. Open the link sent when the account was created, or request a new one from the console.
429rate_limitedToo fast: the request budget for your key, too many batches in flight, too many synchronous requests (per minute or at once), or an agreed hourly spend already met. `message` says which. Carries `Retry-After`. Never metered.
400invalid_requestNo section was set. error.field is empty and the message names Enrich profiles, which is the same record for less.
422unprocessable_targetPer item, on the result entry: the profile is private, deleted or unreachable. credits_used is 0 - including for the sections - and the batch carries on.
422capacity_unavailablePer item: we had no capacity able to run it right now. The message narrows it - Sales Navigator capacity on a search, general capacity elsewhere. credits_used is 0, so retrying later costs you only the wait.

Every failure uses the same envelope, and none of them is metered - see Errors.