Contacts

Look up email and phone for many people

Changed on

Email and phone, with a confidence score, for up to 100 fetched people. Each email includes type work or personal when the lookup provides it. Each result includes best_email and best_phone. best_email prefers personal, then an unlabeled address, then work. prefer is personal or work and defaults to personal. best_phone prefers a mobile number. The emails and phones lists stay complete. A flagged entry stays in the list with flagged true and flag_reason, and is left out of best_email and best_phone. Same pricing and the same { data, cost } body as the single lookup, including a repeat of a stored lookup. A live lookup usually answers 202 with status pending, job_id, and poll_url, and charges nothing yet: cost.credits, charged_this_request, and credits_charged are 0 and X-Credits-Charged is 0. Poll GET /v1/contacts/jobs/{job_id} every few seconds until status is completed, or wait for contacts.completed. The lookup that started the job is charged when it completes, only for people with an email or phone found. The completed poll shows that charge as credits_charged and charged_this_request, the same number as cost.credits and the ledger row, whose request_id is the X-Request-Id of the call that started the lookup. When every person is already stored the lookup answers 200 at once. A 202 also has canonical_id on each person. Replaying the Idempotency-Key after completion returns the completed result and does not charge again. That replay has replayed: true, cost.credits 0 and charged_this_request 0, like every replay. Each person's credits still show what that lookup charged. dry_run in the query or the JSON body previews and never charges, and accepts contacts:spend or sourcing:read. A malformed person id is 400 invalid_id. The reserved id test_miss_0000 always returns not_found, charges 0, and does not call the contact provider. It does not need a fetched profile, and it applies only to contact lookup.

Polling example:

KEY="<your API key>"
curl -s -X POST https://api.recruitmcp.com/v1/people/contacts:fetch \
  -H "Authorization: Bearer $KEY" -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" -d '{"profile_ids":["lead_8f2a1c_1"]}'
# 202: {"status":"pending","job_id":"cjob_01J0CJOB0EXAMPLE","poll_url":"/v1/contacts/jobs/cjob_01J0CJOB0EXAMPLE",...}
# Poll until status is completed (pending until then). The wait doubles from 2 s up to 30 s; stop on an error, give up after about 2 minutes.
delay=2
for i in 1 2 3 4 5 6 7 8; do
  body=$(curl -sS https://api.recruitmcp.com/v1/contacts/jobs/cjob_01J0CJOB0EXAMPLE -H "Authorization: Bearer $KEY")
  echo "$body" | grep -q '"status":"completed"' && break
  echo "$body" | grep -q '"error":{' && break
  sleep $delay
  delay=$((delay*2>30?30:delay*2))
done
echo "$body"
# data[].contacts.best_email and best_phone; credits_charged and charged_this_request equal cost.credits

Scope: contacts:spend.

post/v1/people/contacts:fetch

Request

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

Query parameters

dry_runboolean

Optional. true returns a free preview, labelled dry_run, and changes nothing. The same flag in the JSON body is honored. A dry run never charges. Omit it to run the call.

Headers

Idempotency-Keystring

Required unless dry_run=true. At most 128 characters. The same key and body replays the first response for 24 hours and does not charge again. A different body is 409 idempotency_key_reused. A second call waits for the first and replays it. 409 idempotency_in_progress means the first call was still running when the wait ended.

RecruitMCP-Version'2026-10-13'

API version. Defaults to the version the key was created with. Supported: 2026-10-13.

RecruitMCP-Customerstring

Act inside this customer (cus_…). Ignored for keys bound to a customer, which always act inside theirs.

Request body

profile_idsstring[]

Profile ids already fetched. Up to 100. Send this or ids.

idsstring[]

Alias of profile_ids, the same name POST /v1/people:enrich uses. Merged with profile_ids.

prefer'personal' | 'work'

Email to prefer. personal, the default, ranks personal, then unlabeled, then work. work reverses that order. Phones still prefer mobile.

dry_runboolean

When true, preview the cost and change nothing. Nothing is charged. Same as the dry_run query.

confirmboolean

Deprecated. Accepted and ignored. An authorized lookup runs without it.

confirm_tokenstring

From approval_required. Required only when the estimate is above the key threshold.

Example request

{
  "profile_ids": [
    "lead_8f2a1c_1"
  ],
  "prefer": "personal"
}

Response

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

OR

Example response

{
  "data": [
    {
      "profile_id": "lead_8f2a1c_1",
      "contacts": {
        "status": "ready",
        "emails": [
          {
            "value": "ada.peeters@example.com",
            "type": "personal",
            "line": "personal",
            "confidence": 0.9
          }
        ],
        "phones": [
          {
            "value": "+447700900001",
            "type": "personal",
            "line": "mobile",
            "confidence": 0.8
          }
        ],
        "best_email": {
          "value": "ada.peeters@example.com",
          "type": "personal",
          "line": "personal",
          "confidence": 0.9,
          "reason": "only_candidate"
        },
        "best_phone": {
          "value": "+447700900001",
          "type": "personal",
          "line": "mobile",
          "confidence": 0.8,
          "reason": "only_candidate"
        },
        "expires_at": "2026-11-12T09:00:00Z"
      },
      "credits": 25,
      "cost": {
        "credits": 25,
        "cached_items": 0,
        "live_items": 1,
        "upstream_calls": 1
      }
    }
  ],
  "cost": {
    "credits": 25,
    "cached_items": 0,
    "live_items": 1,
    "upstream_calls": 1
  },
  "charged_this_request": 25
}

Changes

    • warning

      added the new approval_forced enum value to the / response property for the response status

    • warning

      added the new approval_forced enum value to the / response property for the response status

    • warning

      added the new approval_forced enum value to the / response property for the response status

    • warning

      added the new approval_forced enum value to the / response property for the response status

    • warning

      added the new approval_forced enum value to the / response property for the response status

    • warning

      added the new approval_forced enum value to the / response property for the response status

    • warning

      added the new approval_forced enum value to the / response property for the response status

    • warning

      added the new approval_forced enum value to the / response property for the response status