Endpoints // Contact
Find Email
Find a professional email address from a LinkedIn URL or full name and company domain, then receive the result by webhook.
Introduction
The Find Email endpoint discovers professional email addresses for a person. You can identify the person with a LinkedIn profile URL, or with their full name and company domain. Processing is asynchronous: the API acknowledges the request with a webhookId, then you retrieve the result by polling (recommended, no public endpoint required) or through a webhook callback (advanced, push delivery).
Use this endpoint when you need an email address for outreach or CRM enrichment. For push delivery, configure a workspace webhook URL in Settings > Webhooks, or provide webhookUrl in the request.
Authorization
| Name | Type | Required | Description |
|---|---|---|---|
apikey | string | Yes | Your API key from the developer dashboard. Pass it in the Authorization: Bearer YOUR_API_KEY header. |
Request body
Choose one lookup strategy. Use either a LinkedIn profile URL, or a full name with a company domain. The split firstName + lastName form is also accepted.
| Name | Type | Required | Description |
|---|---|---|---|
url | string | Conditional | Public LinkedIn profile URL, such as https://www.linkedin.com/in/janedoe. Use this for the URL strategy. |
fullName | string | Conditional | Person's full name, such as Jane Doe. Use this with companyDomain. |
firstName | string | Conditional | Person's first name. Use this with lastName and companyDomain instead of fullName. |
lastName | string | Conditional | Person's last name. Use this with firstName and companyDomain instead of fullName. |
companyDomain | string | Conditional | Company domain, such as acme.com. Required for the name-based strategies. |
webhookUrl | string | Optional | HTTPS URL that receives the callback. Omit it to retrieve the result by polling instead. |
Response structure
This endpoint replies immediately with a webhookId. You then have two ways to retrieve the actual result:
- Polling (recommended). Call
GET /v2/webhooks/:webhookIduntil the status issucceeded. No webhook URL needed. See the Polling guide. - Webhook callback (advanced). If you provided a
webhookUrl(or a workspace default is configured), the gateway POSTs the emails to that URL once the lookup completes. See Webhook callback payload (advanced) below.
| Field | Type | Description |
|---|---|---|
success | boolean | true if the job was created successfully |
dataobjectInitial job acknowledgement. Save the webhookId to retrieve the result via polling (GET /v2/webhooks/:webhookId) or to correlate an upcoming webhook callback (null on error responses).
Show child attributesHide child attributes
| Field | Type | Description |
|---|---|---|
status | string | Job status, always "created" for the initial response |
webhookId | string | Unique identifier (UUID). Save it. Use it to poll the result at GET /v2/webhooks/:webhookId, or to correlate an incoming webhook callback. |
pollUrl | string | Convenience hint pointing to the polling endpoint for this job (e.g. /v2/webhooks/{webhookId}). |
| Field | Type | Description |
|---|---|---|
error | null | Always null on success |
quotasobjectCredits and rate limit usage after this request
Show child attributesHide child attributes
| Field | Type | Description |
|---|---|---|
creditsConsumed | number | Credits consumed by this request |
workspaceobjectWorkspace-level limits
Show child attributesHide child attributes
| Field | Type | Description |
|---|---|---|
id | string | Your workspace identifier |
hasUnlimitedCredits | boolean | Whether workspace credits are unlimited |
allowDailyOvercost | boolean | Whether the workspace may exceed its daily cap |
creditsobjectCredit balance
Show child attributesHide child attributes
| Field | Type | Description |
|---|---|---|
total | number | Total credits in your plan |
used | number | Credits consumed so far |
left | number | Remaining credits |
dailyLimitobjectDaily request limit (null if not configured)
Show child attributesHide child attributes
| Field | Type | Description |
|---|---|---|
limit | number | Maximum requests per day |
used | number | Requests made today |
left | number | Remaining requests today |
nextReset | string | When the daily window resets (ISO 8601) |
minuteRateLimitobjectPer-minute rate limit
Show child attributesHide child attributes
| Field | Type | Description |
|---|---|---|
limit | number | Maximum requests per minute |
used | number | Requests made in the current minute |
left | number | Remaining requests in the current minute |
nextReset | string | When the minute window resets (ISO 8601) |
keyobjectAPI key-level limits (null when request is from dashboard)
Show child attributesHide child attributes
| Field | Type | Description |
|---|---|---|
id | string | API key identifier |
dailyLimitobjectDaily limit for this key (null if not configured)
Show child attributesHide child attributes
| Field | Type | Description |
|---|---|---|
limit | number | Maximum requests per day |
used | number | Requests made today |
left | number | Remaining requests today |
nextReset | string | When the daily window resets (ISO 8601) |
minuteRateLimitobjectMinute rate limit for this key
Show child attributesHide child attributes
| Field | Type | Description |
|---|---|---|
limit | number | Maximum requests per minute |
used | number | Requests made in the current minute |
left | number | Remaining requests in the current minute |
nextReset | string | When the minute window resets (ISO 8601) |
metadataobjectRequest tracking information
Show child attributesHide child attributes
| Field | Type | Description |
|---|---|---|
requestId | string | Unique identifier for this request |
executionTimeMs | number | Total execution time in milliseconds |
Webhook callback payload (advanced)
When the lookup succeeds, ReverseContact sends a JSON POST request to your webhookUrl. The callback is not wrapped in the V2 response envelope. It always includes the webhookId from the initial acknowledgement and a data value.
Success payload
{
"webhookId": "b8c9d0e1-f2a3-4b4c-5d6e-7f8091a2b3c4",
"data": [
{
"value": "jane.doe@acme.com",
"type": "professional"
}
]
}dataobjectOne entry per email address discovered for the person.
Show child attributesHide child attributes
| Field | Type | Description |
|---|---|---|
value | string | The discovered email address |
type | string | Email category, typically professional |
Webhook callback errors (advanced)
When the lookup cannot return an email, data is not an array. Instead, it contains an errorCode identifying the failure reason:
Error payload
{
"webhookId": "b8c9d0e1-f2a3-4b4c-5d6e-7f8091a2b3c4",
"data": {
"errorCode": "email-not-found"
}
}data.errorCode will be one of the following values:
| Code | Billed | Description |
|---|---|---|
email-not-found | No | No email address was discovered for the person |
person-not-found | No | No matching person was found for the supplied identifiers |
invalid-request | No | The lookup input is malformed or does not match an accepted form |
fetch-data-error | No | The lookup failed while fetching data; safe to retry |
webhook-url-invalid | Yes | The callback URL returned a 4xx response |
webhook-url-errored | Yes | The callback URL returned a 5xx response or a network error |
webhook-url-timeout | Yes | The callback URL did not respond in time |
webhook-url-unreachable | Yes | The callback URL could not be reached |
Code examples
Code Examples
const KEY = "YOUR_API_KEY";
const BASE = "https://api.reversecontact.com";
const trigger = await fetch("https://api.reversecontact.com/v2/contact/email", {
method: "POST",
headers: {
"Authorization": `Bearer ${KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
url: "https://www.linkedin.com/in/janedoe"
})
});
const { data: { webhookId } } = await trigger.json();
async function pollUntilDone(id) {
while (true) {
const res = await fetch(`${BASE}/v2/webhooks/${id}`, {
headers: { "Authorization": `Bearer ${KEY}` }
});
const { data } = await res.json();
if (data.status === "succeeded") return data.result;
if (data.status === "errored") throw new Error(data.errorCode ?? "Job failed");
await new Promise(r => setTimeout(r, 2000));
}
}
const result = await pollUntilDone(webhookId);
console.log(result);const response = await fetch("https://api.reversecontact.com/v2/contact/email", {
method: "POST",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
},
body: JSON.stringify({
fullName: "Jane Doe",
companyDomain: "acme.com",
webhookUrl: "https://your-app.com/webhooks/reversecontact"
})
});
const result = await response.json();
console.log(result.data.status, result.data.webhookId);KEY="YOUR_API_KEY"
ID=$(curl -sX POST https://api.reversecontact.com/v2/contact/email \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"url":"https://www.linkedin.com/in/janedoe"}' | jq -r .data.webhookId)
while :; do
S=$(curl -s https://api.reversecontact.com/v2/webhooks/$ID \
-H "Authorization: Bearer $KEY" | jq -r .data.status)
[ "$S" = "succeeded" ] && break
[ "$S" = "errored" ] && echo "job failed" && exit 1
sleep 2
done
curl -s https://api.reversecontact.com/v2/webhooks/$ID \
-H "Authorization: Bearer $KEY" | jq .data.resultcurl -X POST https://api.reversecontact.com/v2/contact/email \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"fullName": "Jane Doe",
"companyDomain": "acme.com",
"webhookUrl": "https://your-app.com/webhooks/reversecontact"
}'import time
import requests
KEY = "YOUR_API_KEY"
BASE = "https://api.reversecontact.com"
trigger = requests.post(
"https://api.reversecontact.com/v2/contact/email",
headers={
"Authorization": f"Bearer {KEY}",
"Content-Type": "application/json"
},
json={
"url": "https://www.linkedin.com/in/janedoe"
}
)
webhook_id = trigger.json()["data"]["webhookId"]
def poll_until_done(wid):
while True:
res = requests.get(
f"{BASE}/v2/webhooks/{wid}",
headers={"Authorization": f"Bearer {KEY}"}
)
data = res.json()["data"]
if data["status"] == "succeeded":
return data["result"]
if data["status"] == "errored":
raise RuntimeError(data.get("errorCode") or "Job failed")
time.sleep(2)
result = poll_until_done(webhook_id)
print(result)import requests
response = requests.post(
"https://api.reversecontact.com/v2/contact/email",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
},
json={
"fullName": "Jane Doe",
"companyDomain": "acme.com",
"webhookUrl": "https://your-app.com/webhooks/reversecontact"
}
)
result = response.json()
print(result["data"]["status"], result["data"]["webhookId"])Example response
Example Response
{
"success": true,
"data": {
"status": "created",
"webhookId": "b8c9d0e1-f2a3-4b4c-5d6e-7f8091a2b3c4",
"pollUrl": "/v2/webhooks/b8c9d0e1-f2a3-4b4c-5d6e-7f8091a2b3c4"
},
"error": null,
"metadata": {
"requestId": "req_e1m2a3i4l5f6",
"executionTimeMs": 95
},
"quotas": {
"creditsConsumed": 3,
"workspace": {
"id": "ws_abc123",
"credits": {
"total": 10000,
"used": 4539,
"left": 5461
},
"dailyLimit": null,
"minuteRateLimit": {
"limit": 60,
"used": 10,
"left": 50,
"nextReset": "2025-01-20T14:16:00.000Z"
},
"hasUnlimitedCredits": false,
"allowDailyOvercost": false
},
"key": {
"id": "key_abc123",
"dailyLimit": null,
"minuteRateLimit": {
"limit": 60,
"used": 10,
"left": 50,
"nextReset": "2025-01-20T14:16:00.000Z"
}
}
}
}{
"webhookId": "b8c9d0e1-f2a3-4b4c-5d6e-7f8091a2b3c4",
"data": [
{
"value": "jane.doe@acme.com",
"type": "professional"
}
]
}