Introduction
It is a common use case to reach out to a LinkedIn profile for hiring or sales. LinkedIn InMail is limited because you have to pay a significant price per outreach. You will probably need an external solution.
Some tools help you fetch a work email from any LinkedIn profile. But more often than not, these are tools with a user interface intended for end-users. If you're seeking a user interface tool, this is not the article for you.
In this article, you'll learn how teams historically used Proxycurl's Contact API to get verified B2B emails and personal numbers from LinkedIn profiles programmatically, and what to use now that Proxycurl has been sunset.
This article is for teams with software engineers looking to implement an email finder API within their product or business workflow, so you can fetch B2B emails at scale, programmatically.
On top of just work emails, the original Proxycurl Contact API also supported the following data:
- Personal emails
- Personal numbers
- Social media accounts
In addition, emails returned by the legacy API were marketed as:
- Verified with a 95%+ deliverability guarantee
- Fresh, even if the profile had changed jobs
Getting Started
This tutorial shows how to query the legacy Contact API endpoints in Node.js using express and axios. I am keeping the original implementation flow here because it is still useful if you are maintaining old code, migrating an internal integration, or trying to understand what your team built a few years ago.
Steps we'll cover
- How to get B2B emails with the Work Email Lookup API endpoint.
- How to get personal emails with the Personal Email Lookup API endpoint.
- How to get personal numbers with the Personal Number Lookup API endpoint.
- What to use on NinjaPear now instead.
Prerequisites
In your preferred working directory, initialize a new project by running:
npm init -y
Next, run the following command to install the packages to bootstrap your application:
npm install express axios dotenv
express: a minimal and flexible Node.js framework to bootstrap our server.axios: a lightweight HTTP-based data fetching library to query the API endpoints.dotenv: lets us load environment variables seamlessly into the Node.js process.
To begin using the legacy Proxycurl Contact API, you would have needed an API key to make requests to the various endpoints.
With your API key ready, create a .env file in your project root directory and add the following code to it:
API_KEY='<your_api_key>'
Next, in your project root directory create another file index.js and add the following code to it:
import express from 'express';
import dotenv from 'dotenv';
import axios from 'axios';
const port = 8000;
const app = express();
dotenv.config();
app.listen(port, () => {
console.log(`App connected successfully on port: ${port}`);
});
Next, add "type": "module" in package.json to let Node.js know we're writing ES modules and edit the scripts field to the following:
"scripts": {
"dev": "node index.js"
}
Run npm run dev in the terminal and the following should be logged to the console if everything went smoothly:
App connected successfully on port: 8000
How to get B2B emails with Work Email Lookup API Endpoint
The legacy Work Email Lookup endpoint let you get the work email address of a LinkedIn profile.
Update: Proxycurl has been sunset. The founder behind Proxycurl is now building NinjaPear. If you are starting fresh, do not build a new dependency on the old Proxycurl Contact API. Use NinjaPear's Work Email finder and related Employee API endpoints instead. NinjaPear does not scrape LinkedIn, provides richer B2B data in a similar API-first shape, and does so with none of the legal liability.
Endpoint URL
The legacy endpoint URL to get a LinkedIn profile work email was:
https://nubela.co/proxycurl/api/linkedin/profile/email
In the old pricing model, every request cost 3 credits.
In this tutorial, we'll explore two methods by which you could leverage the URL to get a person's work email in your applications.
With webhooks
The legacy Work Email Lookup endpoint might not return results immediately since it needed to perform real-time heuristics to fetch and verify the email. The endpoint required a mandatory parameter: linkedin_profile_url. This is the LinkedIn profile URL of the person whose work email we're looking for.
Alternatively, by adding an optional callback_url parameter to the request, you could create a webhook with Webhook.site to listen to the result once it's available. Webhook.site gives a randomly generated URL to listen to, so whenever you query the work email endpoint, the response, which is an object in the format {'email': ..., 'status': ...}, is sent to your Webhook.site custom URL.
To get started, visit Webhook.site to get your custom URL.
Copy the URL and add it to the WEBHOOK_URL variable in your code like so:
const WEBHOOK_URL = '<your_webhook.site_url>'
Here's the complete code for this example:
// index.js
import express from 'express';
import dotenv from 'dotenv';
import axios from 'axios';
const port = 8000;
const app = express();
dotenv.config();
const WORK_EMAIL_ENDPOINT = 'https://nubela.co/proxycurl/api/linkedin/profile/email';
const LINKEDIN_PROFILE_URL = 'https://www.linkedin.com/in/williamhgates/';
const WEBHOOK_URL = 'https://webhook.site/18fa951e-8cd1-4345-847d-e7ae329e147c';
const workEmailConfig = {
url: WORK_EMAIL_ENDPOINT,
method: 'get',
headers: { Authorization: 'Bearer ' + process.env.API_KEY },
params: {
linkedin_profile_url: LINKEDIN_PROFILE_URL,
callback_url: WEBHOOK_URL,
},
};
const getWorkEmail = async () => {
try {
return await axios(workEmailConfig);
} catch (err) {
throw new Error(err);
}
};
const email = await getWorkEmail();
console.log('Work Email:', email.data.email_queue_count);
app.listen(port, () => {
console.log(`App connected successfully on port: ${port}`);
});
Now if you visit Webhook.site, the response of the query can be seen there.
In the original example, the LinkedIn profile searched did not have a work email, so the email and status fields in the response returned null and email_not_found, respectively.
With Dashboard logs
What's interesting to note is that Proxycurl automatically logged every response to the dashboard whenever the Work Email API endpoint was queried. So you could log on to your dashboard and navigate to the Work Email Lookup Logs tab to see it in action.
The dashboard logs also provided additional information, such as the timestamp of the query and the LinkedIn profile URL being searched. You could also export the logs by clicking the Download as CSV button on the dashboard.
NinjaPear alternative
If you are replacing this flow today, the mental model is different.
NinjaPear does not take a LinkedIn profile URL and return contact data from LinkedIn. Instead, it gives you adjacent primitives that are cleaner to maintain in production:
- Work Email Endpoint: find a person's verified work email from name + company domain.
- Person Profile Endpoint: enrich a person from a work email and return job history, education, and public profile fields.
- Company Website Lookup: resolve a company name to its canonical website before email lookup.
That means the modern flow is usually:
- Resolve company name to website.
- Find work email from person name + company website/domain.
- Enrich the person or company further if needed.
That flow avoids LinkedIn dependency entirely, which is exactly the point.
How to get personal emails with Personal Email Lookup API Endpoint
With the legacy Personal Email Lookup API endpoint you could get the list of personal emails belonging to a LinkedIn profile at the cost of 1 credit for every email returned.
Endpoint URL
The legacy endpoint for the personal email lookup was:
https://nubela.co/proxycurl/api/contact-api/personal-email
Parameters
The endpoint required a mandatory parameter: linkedin_profile_url, which is the LinkedIn profile URL of the person whose personal email we're looking for, and an optional email_validation parameter to specify if we want additional validation of the emails found in the profile. This came at an additional cost of 1 credit for every email found. The email_validation parameter accepted a string value of include. It defaulted to exclude.
// axios config example
params: {
email_validation: 'include'
}
Response
The personal email lookup returned an object with two properties: an emails field containing a list of personal emails of the given LinkedIn profile, and an invalid_emails field containing invalid emails associated with the profile. If the email_validation parameter wasn't specified, the invalid_emails field returned an empty array.
The demo below shows how easy it was to use the endpoint:
// index.js
const PERSONAL_EMAIL_ENDPOINT = 'https://nubela.co/proxycurl/api/contact-api/personal-email';
const LINKEDIN_PROFILE_URL = 'https://linkedin.com/in/steven-goh-6738131b';
const personalEmailConfig = {
url: PERSONAL_EMAIL_ENDPOINT,
method: 'get',
headers: { Authorization: 'Bearer ' + process.env.API_KEY },
params: {
linkedin_profile_url: LINKEDIN_PROFILE_URL,
email_validation: 'include',
},
};
const getPersonalEmail = async () => {
try {
return await axios(personalEmailConfig);
} catch (err) {
throw new Error(err);
}
};
const email = await getPersonalEmail();
console.log('Personal Email:', email.data);
Which logs the following response to the console:
Personal Email: {
emails: ['[email protected]'],
invalid_emails: ['[email protected]']
}
NinjaPear alternative
NinjaPear is not a drop-in replacement for the old personal email lookup flow from a LinkedIn URL. That is intentional.
What NinjaPear does offer is the cleaner B2B path most product teams actually need:
- verified work email lookup
- person profile enrichment from public web data
- company enrichment
If your real use case is outbound sales, recruiting, CRM enrichment, or routing inbound leads, a verified work email is usually the field that matters. If your use case truly depends on personal emails sourced from a LinkedIn URL, this old Proxycurl section is here for legacy reference only.
How to get personal numbers with Personal Number Lookup API Endpoint
With the legacy Personal Number Lookup API endpoint you could get the list of personal numbers belonging to a LinkedIn profile at the cost of 1 credit for every contact number returned.
Endpoint
The legacy endpoint for the personal number lookup was:
https://nubela.co/proxycurl/api/contact-api/personal-contact
Parameters
The personal number lookup endpoint accepted a single required parameter: linkedin_profile_url. This is the URL of the LinkedIn profile whose number we're looking for.
Response
The endpoint returned an object with a numbers field which is an array containing all contact numbers of a LinkedIn profile.
Example code:
const PERSONAL_CONTACT_NUMBER_ENDPOINT = 'https://nubela.co/proxycurl/api/contact-api/personal-contact';
const LINKEDIN_PROFILE_URL = 'https://linkedin.com/in/test-phone-number';
const personalContactNumberConfig = {
url: PERSONAL_CONTACT_NUMBER_ENDPOINT,
method: 'get',
headers: { Authorization: 'Bearer ' + process.env.API_KEY },
params: {
linkedin_profile_url: LINKEDIN_PROFILE_URL,
},
};
const getPersonalContactNumber = async () => {
try {
return await axios(personalContactNumberConfig);
} catch (err) {
throw new Error(err);
}
};
const number = await getPersonalContactNumber();
console.log('Personal number:', number.data);
The above example logs the following response to the console:
Personal number: { numbers: ['+1123123123'] }
NinjaPear alternative
Same story here. NinjaPear is not trying to recreate a LinkedIn-profile-to-personal-number pipeline. It is building a better B2B data stack that does not depend on LinkedIn scraping at all.
If you are migrating an old Proxycurl integration, the practical replacement is usually one of these:
- use the Employee API to enrich the person from work email
- use the Company API to enrich the employer
- use the Customer API or Monitor API if your actual goal is prospecting, account research, or sales timing
That sounds like a broader answer because it is. Most teams that thought they needed "contact data from LinkedIn URLs" were really trying to solve prospecting, enrichment, or routing.
Summary
B2B work emails are not easily available on LinkedIn profiles. You need external tools, and most external tools do not work with LinkedIn profiles programmatically, at scale.
Historically, Proxycurl's Contact API filled that gap. If you still maintain that integration, the examples above should help you understand the request shape and migration surface area.
If you are building now, use NinjaPear instead. The company behind Proxycurl moved on for a reason. NinjaPear provides API-first B2B data primitives such as work email lookup, person profile enrichment, company details, employee count, company updates, and customer intelligence, all without scraping LinkedIn and with none of the legal liability.
Start with the free trial on NinjaPear, test the Work Email and Employee endpoints against your real workflow, and build the integration you actually want to maintain 12 months from now.