Look up email and phone
Changed onEmail and phone, with a confidence score. 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. Charged only when an email or phone is found; a miss is free. The person must be fetched first. The body is always { data, cost }, including one person and 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. 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/lead_8f2a1c_1/contacts \
-H "Authorization: Bearer $KEY" -H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" -d '{}'
# 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.
Request
- Base URL: https://api.recruitmcp.com
- URL: https://api.recruitmcp.com/v1/people/{profile_id}/contacts
- 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)
Path parameters
Query parameters
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
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.
API version. Defaults to the version the key was created with. Supported: 2026-10-13.
Act inside this customer (cus_…). Ignored for keys bound to a customer, which always act inside theirs.
Request body
Example request
{
"prefer": "personal"
}Response
200 when everything is already stored: a ContactsLookup at once. With ?dry_run=true, a Preview; nothing is charged or sent.
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_forcedenum value to the/response property for the response status - warning
added the new
approval_forcedenum value to the/response property for the response status - warning
added the new
approval_forcedenum value to the/response property for the response status - warning
added the new
approval_forcedenum value to the/response property for the response status - warning
added the new
approval_forcedenum value to the/response property for the response status - warning
added the new
approval_forcedenum value to the/response property for the response status - warning
added the new
approval_forcedenum value to the/response property for the response status - warning
added the new
approval_forcedenum value to the/response property for the response status
- warning