Introducing API Playground (and YouTube Monitoring!) Learn more

LinkedIn Messages API: What Actually Works in 2026
proxycurl

LinkedIn Messages API: What Actually Works in 2026

The idea of being able to programmatically send LinkedIn messages at scale is appealing.

Unfortunately, if you're looking for a practical LinkedIn Messages API for sales, recruiting, or outbound, the short answer is no. The official LinkedIn Messages API exists, but it sits behind the Compliance API, and access is heavily restricted.

Here's how LinkedIn describes the requirements themselves:

The Compliance Partner Program is a private and paid program where LinkedIn provides access only to companies with use cases that meet our requirements. The requirement being that the organization or its customers should be FINRA / SEC registered. The primary use case should be archiving and monitoring regulated member's (from financial institutions) posts and public correspondence as required for the organization to be compliant with FINRA & SEC regulations.

So if you're not FINRA / SEC registered, don't bother. The main use case also needs to be archiving and monitoring for compliance, not sales or marketing.

Furthermore, the LinkedIn Messages API has these additional ground rules:

  • Your messages must be tied to specific user actions.
  • Your users must opt-in for messaging.
  • You can't give incentives for opting in to receive messages from you.
  • Your messages must be pure text. No styling of any kind.

That rules out almost everyone. If you do happen to be one of the ~3 companies on earth this API was made for, I'm half joking, you can apply for approval to the Compliance Partner Program here.

On the other hand, if you're in the majority that don't qualify, keep reading.

Alternative solutions to the LinkedIn Messages API

While there are quite a few LinkedIn automation tools designed to unofficially automate various tasks, there are three main LinkedIn automation tools designed primarily for messaging.

Tool Best for Starting price Messaging automation Connection automation Delivery model My take
Dripify Multi-step campaigns $59/mo ⭐⭐⭐⭐☆ ⭐⭐⭐⭐☆ Web app 4.0/5
Octopus CRM Budget/simple workflows $9.99/mo ⭐⭐⭐☆☆ ⭐⭐⭐⭐☆ Chrome extension 3.8/5
Expandi Power users $99/mo ⭐⭐⭐⭐☆ ⭐⭐⭐⭐⭐ Web app 4.2/5

1) Dripify

Dripify CRM's homepage

Dripify CRM's homepage

As the name suggests, Dripify focuses on drip campaigns, which is just another way of saying multi-step outreach with if/then logic.

Sometimes days out. Sometimes weeks. Different branches depending on what the prospect does or doesn't do.

And it all runs through a simple web app:

What a Dripify campaign looks like (Source: Dripify)

What a Dripify campaign looks like (Source: Dripify)

It works well overall for LinkedIn messages automation and integrates with a bunch of other apps.

On LinkedIn, you can only message your 1st-degree connections, so you need to connect first before messaging.

Dripify includes connection requests by email, which helps get around what is normally ~100 connections per week. It also caps sending at 100 messages per day for a free LinkedIn account or 150 per day for a paid account.

Dripify's plans range from $59 to $99 per user per month.

2) Octopus CRM

Octopus CRM's homepage

Octopus CRM's homepage

You could think of Octopus CRM as Dripify's simpler cousin. I'm honestly not sure if they're owned by the same people, but the overlap is obvious.

Octopus CRM does most of what Dripify does, in a simpler package, for less money. You do get less functionality. That's the trade.

Instead of a web app, it runs as a Chrome extension:

Octopus CRM's LinkedIn messaging automation dashboard

Octopus CRM's LinkedIn messaging automation dashboard

It supports connection requests by email, plus the same basic message throughput: 100 messages per day on a free LinkedIn account, or 150 on a paid account.

Pricing ranges from $9.99 per month to $39.99 per month.

Overall, I think it's a good balance of simplicity and functionality for automating LinkedIn messages if you don't need a huge amount of branching logic.

3) Expandi

Expandi's homepage

Expandi's homepage

Everything with Expandi works pretty cleanly. You don't have to click around much to find what you need, but it still has plenty of depth.

It works a lot like Dripify and runs as a web app, no browser extension required:

Expandi's messaging automation dashboard

Expandi's messaging automation dashboard (Source: Expandi)

They advertise 300 connection requests per week and limit messages to 100 per day.

Expandi is the most expensive of the three, starting at $99 per month.

The con about doing LinkedIn messages this way

LinkedIn is not a fan of automation at all. Here's their official stance:

LinkedIn is committed to keeping our members' data safe and our platform free from fraud and abuse. In order to protect our members' privacy and keep LinkedIn a trusted platform for authentic interactions, we don't allow the use of third-party software or browser extensions that scrape, modify the appearance of, or automate activity on LinkedIn's website.

And to be fair, they're pretty good at enforcing that.

These LinkedIn automation tools can only work around LinkedIn's limits so well. At the end of the day, the platform is hostile to automation by design. That's why all of these tools share roughly the same constraints.

It's a cat-and-mouse game. If you automate LinkedIn messages this way, the account is always at some level of risk. At scale, people end up rotating accounts.

This is why plenty of teams use LinkedIn as one touchpoint, but not the entire outreach engine. There's a reason cold email and cold calling are still around, and that's because they still work.

That said, there are better ways to use LinkedIn data for outreach without being trapped inside LinkedIn messages itself.

Enriching LinkedIn contacts off-platform

LinkedIn is still the biggest B2B database in the world. That's what makes it useful. It's also what makes it frustrating.

If you stay fully on-platform, you're boxed into connection limits, messaging limits, account risk, and whatever LinkedIn decides to tighten next.

So instead of staying trapped in LinkedIn messages, you can use LinkedIn as the discovery layer and run your actual outreach somewhere you control.

That usually means pulling the information you actually need:

  • Name
  • Role
  • Work history
  • General prospect context
  • Education history
  • Phone/email
  • More

The hard part is getting that data reliably at scale.

Doing it yourself means scraping infrastructure, proxies, account rotation, parser maintenance, and constant breakage. When I was running scraping-heavy workflows years ago, that operational tax was the real cost. Not the infra bill. The distraction.

Many developers don't want to deal with that, even if they technically can.

Luckily, you don't have to build the scraping layer yourself.

Meet Proxycurl, and the NinjaPear update

Over at Proxycurl, we scraped hundreds of millions of LinkedIn profiles, companies, people, jobs, and turned that into a B2B data product. The dataset was called LinkDB.

Then we built enrichment APIs on top of it, plus other data sources like Crunchbase.

Quick update, because this article predates our current product direction: Proxycurl has since been sunset, and the founder behind Proxycurl is now building NinjaPear. I'm leaving the Proxycurl examples here because the workflow still teaches the right lesson. But if you're building this today, I'd use NinjaPear for the off-platform parts that actually matter: prospect discovery, person enrichment from public web sources, and work email lookup.

Using those kinds of endpoints, you can programmatically pull the B2B data you need and use that to build an outreach system that doesn't depend on LinkedIn messages.

The API workflow

Proxycurl had a bunch of endpoints. Person Profile. Company Profile. Personal phone lookup. Personal email lookup. Work email lookup.

Those URL-in, data-out endpoints worked well for enrichment when you already knew who you wanted.

The more interesting piece was search. The Search API, especially Person Search, let you search across the dataset and then enrich what you found.

If you want the modern equivalent, NinjaPear's Employee API can help with prospect discovery, employee/profile enrichment helps build the person record from public web sources, and work email lookup helps you move the conversation into cold email where you actually control the pipe.

That is the part I would personally bet on now.

Programmatically searching LinkedIn data

There are a lot of ways to build this kind of workflow, but I'll keep it simple here:

  1. Python
  2. A .csv file
  3. An outreach tool

Python is easy to work with and great for data manipulation. I picked .csv because every outreach platform on earth seems to support it.

Let's say you run a marketing agency focused on computer cleaner software companies, and you want decision-makers, specifically founders and co-founders.

The following Python script uses the legacy Proxycurl Person Search Endpoint plus personal contact endpoints to search for founders at computer software companies and export their contact info:

import requests
import csv

# API credentials and endpoint configuration
api_key = 'Your_API_Key_Here'  # Replace with your actual API key
headers = {'Authorization': 'Bearer ' + api_key}
search_api_endpoint = 'https://nubela.co/proxycurl/api/v2/search/person/'
contact_phone_endpoint = 'https://nubela.co/proxycurl/api/contact-api/personal-contact'
contact_email_endpoint = 'https://nubela.co/proxycurl/api/contact-api/personal-email'

# Parameters for the initial search request
search_params = {
    'country': 'US',
    'current_role_title': 'founder',
    'current_company_industry': 'computer software',
    'page_size': '1',
    'enrich_profiles': 'enrich',
}

# Output CSV file and headers configuration
output_file = 'enriched_profiles_with_contacts.csv'
fieldnames = [
    'linkedin_profile_url', 'first_name', 'last_name', 'occupation', 'headline',
    'company', 'company_linkedin_profile_url', 'city', 'state', 'personal_phone_number', 'personal_email'
]


def fetch_contact_info(api_endpoint, linkedin_profile_url):
    """Fetch personal contact info (phone number or email)."""
    params = {'linkedin_profile_url': linkedin_profile_url}
    response = requests.get(api_endpoint, headers=headers, params=params)
    if response.status_code == 200:
        data = response.json()
        if 'personal-contact' in api_endpoint:
            return ', '.join(data.get('numbers', []))
        elif 'personal-email' in api_endpoint:
            return ', '.join(data.get('emails', []))
    else:
        print(f"Error fetching contact info from {api_endpoint}: {response.status_code}, {response.text}")
    return ''


def extract_company_info(profile):
    """Extract the most relevant company name and LinkedIn URL from the profile's experiences."""
    company_name = ''
    company_linkedin_url = ''
    experiences = profile.get('experiences', [])
    if experiences:
        most_recent_experience = experiences[0]
        company_name = most_recent_experience.get('company', '')
        company_linkedin_url = most_recent_experience.get('company_linkedin_profile_url', '')
    return company_name, company_linkedin_url


def process_and_write_profiles(writer, result_limit):
    """Fetch profiles and write their details, including contact info, to the CSV file."""
    url = search_api_endpoint
    params = search_params.copy()
    result_count = 0

    while url and result_count < result_limit:
        response = requests.get(url, headers=headers, params=params)
        if response.status_code != 200:
            print(f"Error fetching profiles: {response.status_code}, {response.text}")
            break

        data = response.json()
        for profile in data.get('results', []):
            if result_count >= result_limit:
                break

            email_info = fetch_contact_info(contact_email_endpoint, profile['linkedin_profile_url'])
            phone_info = fetch_contact_info(contact_phone_endpoint, profile['linkedin_profile_url'])

            profile_details = profile.get('profile', {})
            first_name = profile_details.get('first_name', '')
            last_name = profile_details.get('last_name', '')
            occupation = profile_details.get('occupation', '')
            headline = profile_details.get('headline', '')

            company, company_linkedin_url = extract_company_info(profile_details)

            city = profile_details.get('city', '')
            state = profile_details.get('state', '')

            writer.writerow({
                'linkedin_profile_url': profile['linkedin_profile_url'],
                'first_name': first_name,
                'last_name': last_name,
                'occupation': occupation,
                'headline': headline,
                'company': company,
                'company_linkedin_profile_url': company_linkedin_url,
                'city': city,
                'state': state,
                'personal_phone_number': phone_info,
                'personal_email': email_info,
            })
            result_count += 1

        next_page_url = data.get('next_page')
        if next_page_url and result_count < result_limit:
            url = next_page_url
            params = {}
        else:
            break


with open(output_file, mode='w', newline='') as file:
    writer = csv.DictWriter(file, fieldnames=fieldnames)
    writer.writeheader()
    process_and_write_profiles(writer, 10)

    # Add desired result limit above

print("Completed fetching LinkedIn profile data with contact information.")

It's set to generate 10 leads and grab:

  • LinkedIn profile URL
  • Full name
  • Occupation
  • Headline
  • Company
  • Company LinkedIn URL
  • City
  • State
  • Personal phone number
  • Personal email

And then it writes the output into enriched_profiles_with_contacts.csv.

It's also worth noting that the Search API supported Boolean operators, so you could use AND, OR, and NOT to refine the query.

So if you wanted only founders instead of founders plus co-founders, you could change:

'current_role_title': 'founder'

to:

'current_role_title': 'founder NOT co-founder'

And if you want more than 10 leads, just change this line:

process_and_write_profiles(writer, 10)

The modern NinjaPear version

If I were building this same workflow today, I would not build around LinkedIn messages.

I'd do this instead:

  1. Use NinjaPear employee discovery to find the right people at the right companies.
  2. Enrich the person record from public web data.
  3. Use work email lookup to get a verified work email.
  4. Push the result into your sequencer.
  5. Use LinkedIn messages as a support channel, not the main pipe.

That is a more durable system.

You control deliverability. You control sequencing. You control the data flow.

Turning B2B data into automated outreach

We'll use Reply.io in this example because it works well, and yes, they were a customer of ours.

Reply.io's homepage

Reply.io's homepage

Using the same .csv from earlier, you can import each contact and data point into Reply.io and create a sequence.

First, click People, then New import, then Import from CSV.

Reply.io's import process

Reply.io's import process

Then map the data in your .csv to variables inside Reply.io. You can create custom variables for fields that don't already exist.

Mapping our variables within Reply.io

Mapping our variables within Reply.io

Then click Sequences and New sequence and build the workflow you want.

Reply.io's sequences

Reply.io's sequences

You can make it as simple or as layered as you want, email, text, and yes, Reply.io also supports LinkedIn touches.

Reply.io's sequence builder

Reply.io's sequence builder

And you can personalize the messages with imported variables:

Repy.io's message editor

Repy.io's message editor

Not bad. This is the part that actually matters.

The barebones cold email option

If you want to focus on cold email using LinkedIn-derived data, you could use a tool like Sendy, which is a simple self-hosted email automation tool. I've used it for years. Proxycurl used it too.

Sendy's homepage

Sendy's homepage

It costs $69 one time and integrates with AWS SES, which costs $0.10 per 1,000 emails.

We published a more detailed guide on using Sendy elsewhere, so I won't go too deep here, but it's straightforward.

The one annoying bit is import. You can work around that with a simple script using the Sendy API after setting up your custom fields.

//-------------------- EDIT HERE -----------------------//
$sendy_url = 'https://SendyInstallation.com';
$api_key = 'xxxxxxxxxxxxxx';
$list_id = 'xxxxxxxxxxxxxx'; 
$csv_file = 'enriched_profiles_with_contacts.csv';   
//------------- STOP EDITING HERE --------------------//
ini_set('auto_detect_line_endings',TRUE);
$file_handle = fopen($csv_file, "r");
$ch = curl_init();

while (!feof($file_handle) ) {
    set_time_limit(0);

    $line_of_text = fgetcsv($file_handle, 1024);
    // Extract the first email if there are multiple emails
    $emails = explode(',', $line_of_text[10]); // Emails are in the 11th column
    $email = isset($emails[0]) ? trim($emails[0]) : '';

    // Check if a phone number is present
    $phone_number = isset($line_of_text[9]) && !empty($line_of_text[9]) ? $line_of_text[9] : ''; // Phone numbers are in the 10th column

    $fields = array(
        'name' => urlencode($line_of_text[1] . ' ' . $line_of_text[2]), // Combining first name and last name
        'email' => urlencode($email), // Using the first email if multiple
        // Add your custom fields here
        'Occupation' => urlencode($line_of_text[3]), // Occupation is in the 4th column
        'Company' => urlencode($line_of_text[5]), // Company is in the 6th column
        'City' => urlencode($line_of_text[7]), // City is in the 8th column
        'State' => urlencode($line_of_text[8]), // State is in the 9th column
        'PhoneNumber' => urlencode($phone_number), // Phone number is in the 10th column
        'api_key' => urlencode($api_key),
        'list' => urlencode($list_id)
    );

    $fields_string = http_build_query($fields);

    curl_setopt($ch, CURLOPT_URL, $sendy_url.'/subscribe');
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string);

    // Making sure there's a first name and a valid email before sending the request
    if (!empty($line_of_text[1]) && filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $response = curl_exec($ch);
        if(curl_errno($ch)) {
            echo 'Curl error: ' . curl_error($ch);
        }
        sleep(5);
    }

    echo htmlspecialchars($line_of_text[1], ENT_QUOTES, 'UTF-8');
    echo '<br>';
}

fclose($file_handle);
curl_close($ch);
//------------------------------------------------------//

Create an import folder in your Sendy installation, create index.php, paste the script, add your Sendy API information at the top, then upload the .csv into that same folder.

After that, go to YourSendyInstall.com/import/index.php and let it run. It may take a while depending on list size.

Then you're ready to run cold email.

You probably didn't need LinkedIn messages anyway

You can still use LinkedIn. I do. Most serious outbound teams do.

I just would not build my whole outbound machine around LinkedIn messages.

From LinkedIn touches to cold email, texting, and sequencers, the better approach is to use LinkedIn for signal and discovery, then run the actual system somewhere you control.

That was the useful part of the old Proxycurl workflow. And it's still the useful part now.

If you're building this today, the current version of that idea is NinjaPear: use it for enrichment, employee discovery, work email lookup, and company monitoring, then run the outreach off-platform.

That is simply more durable than depending entirely on LinkedIn messages automation.

Create your account

If you're ready to build the off-platform version of this workflow, start with NinjaPear.

NinjaPear gives you the current pieces that matter here: person and company enrichment from public sources, employee discovery, work email lookup, and monitoring data you can use to time outreach better.

If you want a hands-on place to do this without building the whole thing from scratch, Prospector is also worth looking at. It lets you build prospect lists, enrich rows, and export them into your sequencer-ready workflow.

That is the next step I would take.

Colton Randolph | Technical Writer
Colton is a technical writer skilled in Python, SEO, and content strategy. He combines rich data with his 8-year expertise in writing to illustrate how Sapiengraph can move the needle for you.

Featured Articles

Here's what we've been up to recently.

I dismissed someone, and it was not because of COVID19

The cadence of delivery. Last month, I dismissed the employment of a software developer who oversold himself during the interview phase. He turned out to be on the lowest rung of the software engineers in my company. Not being good enough is not a reason to be dismissed. But not

sharedhere

I got blocked from posting on Facebook

I tried sharing some news on Facebook today, and I got blocked from posting in other groups. I had figured that I needed a better growth engine instead of over-sharing on Facebook, so I spent the morning planning the new growth engine. Growth Hacking I term what I do in