> ## Documentation Index
> Fetch the complete documentation index at: https://www.forager.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Forager is a REST API for B2B people, company and job post data: contact lookups (phone numbers, personal emails, work emails), person, company and job post search, and bulk data feeds.
> Server: https://api-v2.forager.ai. Send the API key in the X-API-KEY header. Most paths include {account_id}. Read FORAGER_API_KEY and FORAGER_ACCOUNT_ID from the environment; never put the key in client-side code, logs or prompts.
> To add Forager to a project, follow https://www.forager.ai/docs/guides/agent-quickstart.md. To pick an endpoint, read https://www.forager.ai/docs/guides/choose-an-endpoint.md. For end-to-end builds, see https://www.forager.ai/docs/recipes/overview.md.
> Most requests spend credits. Do not guess costs; use https://www.forager.ai/docs/api-overview/credit-pricing.md and ask the user before spending credits.
> Use only endpoints and fields defined in https://www.forager.ai/docs/openapi.yaml. Forager has no MCP server for the API yet; the /mcp endpoint on this site searches the documentation only.

# Candidate contact information lookup

> Look up a candidate's personal email and phone number from a LinkedIn profile URL in your recruiting tool, with filtering and credit handling.

```text Recipe prompt theme={"theme":{"light":"github-light","dark":"github-dark"}}
Implement this recipe in my project: https://www.forager.ai/docs/recipes/candidate-contact.md

Add a "Get contact details" action to a candidate profile. Extract the LinkedIn public identifier from the candidate's LinkedIn profile URL, call the Forager personal emails lookup and the phone numbers lookup with it, and return both lists. Drop emails whose validation_status is invalid. Handle empty results, a 403, and a 429 with backoff, and make the action idempotent so one click never spends credits twice. Keep the API key server-side in FORAGER_API_KEY with FORAGER_ACCOUNT_ID. Add tests that stub the HTTP calls.
```

![Candidate contact information: a candidate profile goes into Forager and an email and phone come back](https://mintcdn.com/forager/OnhSHNo_LBQ7yKjR/images/recipes/candidate-contact-information-email-phone.svg?fit=max\&auto=format\&n=OnhSHNo_LBQ7yKjR\&q=85\&s=bd786005f55e08c10568047430b2fb01)

## What you'll build

A button in your recruiting tool. A recruiter opens a candidate with a LinkedIn profile URL, clicks once, and sees the candidate's personal emails and phone numbers. Each lookup is a separate call, so you can run only the one you need.

```mermaid theme={"theme":{"light":"github-light","dark":"github-dark"}}
flowchart LR
  A[Profile URL] --> B[Public identifier]
  B --> C[Personal email lookup]
  B --> D[Phone lookup]
  C --> E[Filter by validation]
  D --> F[Candidate record]
  E --> F
```

## Steps

**1. Extract the LinkedIn public identifier**

Both lookups accept `linkedin_public_identifier`, the slug in `linkedin.com/in/<slug>/`. For `https://www.linkedin.com/in/jane-doe-example/`, the identifier is `jane-doe-example`. If you already store a Forager ID, send `person_id` instead.

**2. Look up the personal email**

Send a `POST` to [Personal emails lookup](https://www.forager.ai/docs/openapi/personal-emails/datastorage_person_contacts_lookup_personal_emails_create.md).

```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X POST "https://api-v2.forager.ai/api/$FORAGER_ACCOUNT_ID/datastorage/person_contacts_lookup/personal_emails/" \
  -H "X-API-KEY: $FORAGER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"linkedin_public_identifier": "jane-doe-example"}'
```

```python Python (requests) theme={"theme":{"light":"github-light","dark":"github-dark"}}
import os, requests

BASE = f"https://api-v2.forager.ai/api/{os.environ['FORAGER_ACCOUNT_ID']}/datastorage/person_contacts_lookup"
HEADERS = {"X-API-KEY": os.environ["FORAGER_API_KEY"]}
BODY = {"linkedin_public_identifier": "jane-doe-example"}

r = requests.post(f"{BASE}/personal_emails/", headers=HEADERS, json=BODY, timeout=30)
r.raise_for_status()
emails = [e for e in (r.json() if r.content else []) if e["validation_status"] != "invalid"]
```

```javascript JavaScript (fetch) theme={"theme":{"light":"github-light","dark":"github-dark"}}
const BASE = `https://api-v2.forager.ai/api/${process.env.FORAGER_ACCOUNT_ID}/datastorage/person_contacts_lookup`;
const headers = {
  "X-API-KEY": process.env.FORAGER_API_KEY,
  "Content-Type": "application/json",
};
const body = JSON.stringify({ linkedin_public_identifier: "jane-doe-example" });

const r = await fetch(`${BASE}/personal_emails/`, { method: "POST", headers, body });
if (!r.ok) throw new Error(`Forager ${r.status}`);
const text = await r.text();
const emails = (text ? JSON.parse(text) : []).filter((e) => e.validation_status !== "invalid");
```

**Example response (illustrative values)**

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
[{ "email": "jane.doe@gmail.com", "email_type": "personal", "validation_status": "valid" }]
```

The endpoint returns only free webmail addresses. Company and school domains are excluded.

**3. Look up the phone number**

Send a `POST` to [Phone numbers lookup](https://www.forager.ai/docs/openapi/phones/datastorage_person_contacts_lookup_phone_numbers_create.md) with the same body.

```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X POST "https://api-v2.forager.ai/api/$FORAGER_ACCOUNT_ID/datastorage/person_contacts_lookup/phone_numbers/" \
  -H "X-API-KEY: $FORAGER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"linkedin_public_identifier": "jane-doe-example"}'
```

```python Python (requests) theme={"theme":{"light":"github-light","dark":"github-dark"}}
r = requests.post(f"{BASE}/phone_numbers/", headers=HEADERS, json=BODY, timeout=30)
r.raise_for_status()
phones = [row["phone_number"] for row in r.json()]
```

```javascript JavaScript (fetch) theme={"theme":{"light":"github-light","dark":"github-dark"}}
const p = await fetch(`${BASE}/phone_numbers/`, { method: "POST", headers, body });
if (!p.ok) throw new Error(`Forager ${p.status}`);
const phones = (await p.json()).map((row) => row.phone_number);
```

Phone results contain `phone_number` only.

**4. Save to the candidate record**

Store the lists on the candidate with the date you looked them up. Before spending credits again, check whether you already have recent contact details for this candidate. If a recruiter learns that a number works or does not, report it with [phone-number feedback](https://www.forager.ai/docs/openapi/feedback/datastorage_feedback_phone_numbers_create.md).

## Handle the edge cases

* **Not found.** Empty lists mean no contact details. Show "none found" and keep the other lookup's result.
* **403.** The key is missing or invalid. Fix the credentials; do not retry.
* **429.** Back off and retry. See [errors and rate limits](https://www.forager.ai/docs/errors-and-rate-limits.md).
* **Credits.** A personal email lookup costs 5 credits, and is not charged when no address is returned. A phone number lookup costs 15 credits per lookup. Run only the lookup the recruiter asked for. See [credit pricing](https://www.forager.ai/docs/api-overview/credit-pricing.md).

## Related

> [card] [Find phone numbers](https://www.forager.ai/docs/guides/find-phone-numbers.md)
>
> Phone lookup guide.

> [card] [Enrich a contact](https://www.forager.ai/docs/guides/enrich-a-contact.md)
>
> Get the full person record.

> [card] [Source candidates](https://www.forager.ai/docs/recipes/source-candidates.md)
>
> Find candidates by title, skill and location.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.