---
title: "Enrich profiles"
method: POST
path: "/v1/people:enrich"
tags: ["Profiles"]
---

# Enrich profiles

`POST /v1/people:enrich`

## Request

- Base URL: `https://api.recruitmcp.com`
- URL: `https://api.recruitmcp.com/v1/people:enrich`
- Auth: one of:
  - HTTP bearer
  - OAuth 2 (authorization code, authorization URL `https://recruitmcp.com/oauth/authorize`, token URL `https://recruitmcp.com/oauth/token`, scopes: `sourcing:spend`)

Full public professional profiles for up to 100 profile ids or profile URLs. This does not return email or phone; a contact lookup does that. The body is the full profile, including education. MCP enrich_profiles defaults view to full and returns that same profile; compact is opt-in. Credits stay the profile price either way. profile_id is the search id or a public profile URL. alternate_id is the other profile id for the same person when both are known. Either id reads the same cached profile. Cached copies younger than max_age_days are free. A live enrich usually queues a job: the response is 202 with job_id, poll_url (/v1/jobs/{job_id}), and status queued, plus a Location header, and X-Credits-Charged is 0. Poll GET /v1/jobs/{job_id} every few seconds until status is completed, or wait for job.completed. The call that queued the job is charged when it completes; the completed job has result.data and credits_charged, read from the ledger and equal to result.cost.credits, and the ledger row carries this call's request_id and the job_id. When everything is already stored the profiles come back at once (200). A suppressed person is omitted and never charged.

Polling example:
```bash
KEY="<your API key>"
curl -s -X POST https://api.recruitmcp.com/v1/people:enrich \
  -H "Authorization: Bearer $KEY" -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" -d '{"ids":["lead_8f2a1c_1"]}'
# 202: {"job_id":"job_01J0JOB0EXAMPLE","poll_url":"/v1/jobs/job_01J0JOB0EXAMPLE","status":"queued",...}
until curl -s https://api.recruitmcp.com/v1/jobs/job_01J0JOB0EXAMPLE \
  -H "Authorization: Bearer $KEY" | grep -q '"status":"\(completed\|failed\)"'; do sleep 3; done
curl -s https://api.recruitmcp.com/v1/jobs/job_01J0JOB0EXAMPLE -H "Authorization: Bearer $KEY"
# result.data holds the profiles; credits_charged equals result.cost.credits
```

Scope: `sourcing:spend`.

## Query parameters

- `dry_run` boolean

## Headers

- `Idempotency-Key` string
- `RecruitMCP-Version` '2026-10-13'
- `RecruitMCP-Customer` string

## Request body

- PeopleFetchRequest
  - `ids` string[]: Profile ids or public profile URLs. Up to 100.
  - `profile_ids` string[]: Alias of ids, the same name POST /v1/people/contacts:fetch uses. Merged with ids.
  - `profile_urls` string[]: Public profile URLs. Up to 100 together with ids.
  - `max_age_days` integer: Cached copies younger than this are free. Default 30.
  - `enrich_companies` boolean: Add industry and size to each experience entry.
  - `confirm` boolean: Deprecated. Accepted and ignored. An authorized enrich runs without it.
  - `confirm_token` string: From approval_required. Required only when the estimate is above the key threshold.

## Response `200`

200 when everything is already stored: a ProfileList at once. With ?dry_run=true, a Preview; nothing is charged or sent.

- union
  - ProfileList
    - `object` 'list'
    - `data` Profile[], required
      - `profile_id` string, required: Search id. The same value search returned when both ids are known.
      - `alternate_id` string, nullable: The other profile id for the same person. Null until the person has been enriched and both ids are known.
      - `full_name` string, nullable
      - `headline` string, nullable
      - `summary` string, nullable
      - `photo` string, nullable: Profile photo on this API, /v1/media/{token}. The token expires after 7 days; a later response signs a new one. Null when there is no photo.
      - `profile_url` string, nullable
      - `public_identifier` string, nullable
      - `locations` object[]
        - `name` string
      - `experience` object[]
        - `position` string, nullable
        - `company` string, nullable
        - `started` string, nullable
        - `ended` string, nullable
        - `current` boolean
        - `summary` string, nullable
        - `location` string, nullable
        - `company_url` string, nullable
        - `company_industry` string, nullable
        - `company_size` string, nullable
      - `education` object[]
        - `university` string, nullable
        - `url` string, nullable: School profile URL.
        - `degree` string[]
        - `faculty` string, nullable
        - `started_year` integer, nullable
        - `ended_year` integer, nullable
      - `skills` string[]
      - `language` object[]
        - `name` string, nullable
        - `proficiency` string, nullable
      - `certification` object[]
        - `name` string, nullable
        - `authority` string, nullable
      - `open_to_work` boolean
      - `source` 'profile_index' | 'contact_provider', required
      - `fetched_at` string, date-time
    - `not_found` string[], required
    - `omitted` object
      - `suppressed` integer: Requested people who are suppressed. Never charged.
    - `cost` Cost
      - `credits` integer, required: Credits charged by this call. Whole credits. 0 on an idempotent replay, where the body also has replayed: true and the header Idempotent-Replayed: true.
      - `cached_items` integer: Items read from cache. On a replay, the value of the first response.
      - `live_items` integer: Items fetched live by this call. 0 on a replay.
      - `upstream_calls` integer: Index calls made by this call. 0 on a replay.
  - Preview: Returned when dry_run=true. Nothing was spent or changed. Search, profile, company, and contact previews set dry_run true and requires_confirm false.
    - `dry_run` true, required: True on this preview. Nothing was charged and nothing was changed.
    - `estimate` Estimate, required
      - `credits_min` integer, required: Lowest expected spend. Whole credits.
      - `credits_max` integer, required: Ceiling. Spend never passes it. Whole credits.
      - `expected_pages` integer
      - `expected_profile_fetches` integer
      - `skipped_pages_from_checkpoints` integer: Pages already paid for in an earlier search. Not charged again.
      - `cached_items` integer: Items served from cache, free.
      - `balance_credits` integer, nullable: Account balance. A customer-bound key gets that customer's remaining daily cap, or null when the customer has no cap.
      - `sufficient` boolean: True when the visible balance covers credits_max, or when that balance is hidden. False when a live call can return 402 insufficient_credits.
      - `warning` string, nullable: Null when sufficient is true. Otherwise the live call can return 402 insufficient_credits.
      - `requires_confirm` boolean, required: False on a search, profile, company, or contact preview. True when the call still needs an explicit confirm.
    - `cost` Cost
      - `credits` integer, required: Credits charged by this call. Whole credits. 0 on an idempotent replay, where the body also has replayed: true and the header Idempotent-Replayed: true.
      - `cached_items` integer: Items read from cache. On a replay, the value of the first response.
      - `live_items` integer: Items fetched live by this call. 0 on a replay.
      - `upstream_calls` integer: Index calls made by this call. 0 on a replay.

## Other responses

- `202`: Live response, usually. The work is queued: the body has job_id, poll_url, and status. Nothing is charged yet. Poll GET /v1/jobs/{job_id} every few seconds until status is completed.
- `400`: `idempotency_key_required` (do not retry): This route spends credits or sends a message. Send an Idempotency-Key header, or dry_run=true for a preview. `invalid_request` (do not retry): A field is missing or has a value this endpoint does not accept. `unsupported_version` (do not retry): RecruitMCP-Version is not a supported version.
- `401`: `unauthorized` (do not retry): Missing bearer API key.
- `402`: `insufficient_credits` (do not retry): Not enough credits for this operation.
- `403`: `forbidden` (do not retry): This customer is disabled. `insufficient_scope` (do not retry): This key does not include the scope this endpoint requires.
- `409`: `approval_required` (do not retry): This call is above the key's approval threshold. Nothing was charged. Send confirm_token to run it. `confirm_token_used` (do not retry): This confirm_token was already used. Ask for a new preview to get another. `idempotency_in_progress` (retry): A request with this Idempotency-Key is still running. Retry after it finishes. `idempotency_key_reused` (do not retry): Idempotency-Key was already used with a different request.
- `429`: `rate_limited` (retry): Per-key rate limit exceeded.

## Changes

- **2026-10-20** `ad27b35513a6`: 6 warning
  - added the new `approval_forced` enum value to the `error/code` response property for the response status `400`
  - added the new `approval_forced` enum value to the `error/code` response property for the response status `401`
  - added the new `approval_forced` enum value to the `error/code` response property for the response status `402`
  - added the new `approval_forced` enum value to the `error/code` response property for the response status `403`
  - …2 more

[Operation history](https://skmtc.dev/recruitmcp/apis/recruitmcp-api/changes/v1/people:enrich/post.md)

---

[API](https://skmtc.dev/recruitmcp/apis/recruitmcp-api.md) · [All operations](https://skmtc.dev/recruitmcp/apis/recruitmcp-api/llms.txt) · [OpenAPI document](https://skmtc.dev/recruitmcp/apis/recruitmcp-api/revisions/ad27b35513a6?raw)
