LinkedIn Api with Python

linkedin

Introduction

LinkedIn is the world’s largest professional network on the internet. You can use LinkedIn to find the right job or internship, connect and strengthen professional relationships, and learn the skills you need to succeed in your career. LinkedIn provides an API interface that enables its partners and developers build, run, and grow sustainable businesses on the platform. An API, or Application Programming Interface, is a way of communication between various software components. It is a method in which applications give access to their data in a structured way, without the need of interacting with the user interface. Linkedin’s APIs are grouped and organized by their business lines covering Consumer, Compliance, Learning, Marketing, Sales, and Talent Solutions.

In this article, you will learn how to get your credentials, authenticate the API using OAuth 2.0 and share content from your personal profile.

Table of contents


Prerequisites

Create a linkedlin Company Page

  1. Navigate to the company creation page, Select Create a Company Page. Click on Company and enter the details and click on Create Page.

    linkedin

  2. You will be asked to provide the following details for you company page:

    1. Name of your company.
    2. Company URL
    3. Company Website
    4. Industry
    5. Company Size
    6. Logo
    7. Tagline - A short description of your company.

Once you have provided all these details, your LinkedIn company page has been created now.

linkedin

Create the LinkedIn app

linkedin
  1. Navigate to the developer portal, click on My Apps select Create App and enter the following details for your application:

    1. Application name
    2. Linkedin Page - The LinkedIn Company Page you select will be associated with your app
    3. An optional Privacy policy URL.
    4. Application logo
    5. Agree to the terms and click on Create App
      linkedin
  2. To verify your application click on the verify button.

linkedin
  1. Click Generate URL copy the verification URL provided and click the I’m done button.
linkedin
  1. Go to the verification url you just copied, here you will be asked to verify the application for your company page.
    linkedin
    Click on verify and you will now have successfully created a LinkedIn company page and also created an app to interact with LinkedIn using the REST APIs.

Add Products to the LinkedIn Applcation

Once you have created the LinkedIn App, you need to define what are the products that you are going to use with your app. Based on this, your app will be provided with the necessary permissions that you need. There are three products available to choose from:

  1. Share on LinkedIn - This allows you to post content from your profile.
  2. Sign In with LinkedIn - This allows you to use LinkedIn social sign on your webpage.
  3. Marketing Developer Platform - Allows you to post content from your page. This product requires additional access. You will receive a form for this. Fill up the form and wait for approval. Click on the View Access Form and fill it up with relevant details.
linkedin

Set-Up the Callback/Redirect URL

The callback url is the URL that OAuth invokes after the authentication process. OAuth redirects back to this URL and appends additional parameters to it, including an access code which will be exchanged for an access_token.

linkedin

Consuming LinkedIn API with python

Now comes the interesting bit, writing python code that interacts with the API. Under the OAuth 2.0 scopes card in the auth tab of your application you can see the levels of access your app has in my case that is:

linkedin
Therefore this article will only cover the above scopes which lets you:

  1. Get your own user ID
  2. Get your own email address
  3. Post on LinkedIn as yourself.

The above scopes are memeber specific and for this we will use the Authorization Code Flow (3-legged OAuth)

Store your OAuth Credentials

Fetch your OAuth credentials from the auth tab of your application.

linkedin
Leaving passwords in code is obviously terrible idea, as it’s lying there in plaintext for anyone to see and you’re also running risk of accidentally pushing it to git repo. A little better option would be to store it in environment variables. Create a .env file and populate it with your credentials as follows:

CLIENT_ID=XXXXXXXXXXXXXX
CLIENT_SECRET=XXXXXXXXXXXXXXXX
REDIRECT_URL=XXXXXXXXXXXXXXXXX

Now that we have the credentials in place lets begin interacting with the LinkedIn APIs

Install python dependencies

To write code that interacts with REST APIs, you need to make HTTP Requests to the various API endpoints.In python the requests library allows you to send HTTP/1.1 requests extremely easily. It abstracts the complexities of making requests behind a beautiful, simple API so that you can focus on interacting with services and consuming data in your application.

To install requests :

$ python -m pip install requests

Also to read the environment variables created above we need the python-dotenv library that facilitates reading key-value pairs from a .env file and set them as environment variables Install it with:

$ python -m pip install python-dotenv

Import dependencies

import os
import random
import string
import webbrowser
from pathlib import Path
from urllib.parse import parse_qs, urlparse

import requests
from dotenv import load_dotenv, set_key

Dont worry will get to how all this modules are used in the article.

Setup some global variables

This are the LinkedIn API endpoints we will use:

AUTHORIZATION_URL = "https://www.linkedin.com/oauth/v2/authorization"
ACCESS_TOKEN_URL = "https://www.linkedin.com/oauth/v2/accessToken"
USER_PROFILE_URL = "https://api.linkedin.com/v2/me"
SHARE_URL = "https://api.linkedin.com/v2/shares"

Getting Access to LinkedIn APIs

The LinkedIn API uses OAuth 2.0 for user authorization and API authentication. Applications must be authorized and authenticated before they can fetch data from LinkedIn or get access to member data.

Request an Authorization Code

To request an authorization code, you must direct the member’s browser to LinkedIn’s OAuth 2.0 authorization page, where the member either accepts or denies your application’s permission request.

GET https://www.linkedin.com/oauth/v2/authorization

Sample request with python

def generate_CSRF_token():
    return secrets.token_urlsafe(32)


def parse_redirect_uri(redirect_response):
    """
    Extract the authorization code from the redirect uri.
    """
    url = urlparse(redirect_response)
    url = parse_qs(url.query)
    return url["code"][0]


def request_authorization_code(url: str, client_id: str, redirect_uri: str):
    csrf_token = generate_CSRF_token()
    params = {
        "response_type": "code",
        "client_id": client_id,
        "redirect_uri": redirect_uri,
        "state": csrf_token,
        "scope": "r_liteprofile,r_emailaddress,w_member_social",
    }
    with requests.session() as s:
        response = s.get(url=url, params=params)
    OAuthURL = response.url
    webbrowser.open_new_tab(OAuthURL)
    print(
        f"""
    Copy the URL of where you have been redirected to:\n
    """
    )

    # Get the authorization code from the callback url input
    redirect_response = input("Paste the redirect URL here:")
    auth_code = parse_redirect_uri(redirect_response)
    return auth_code

This is a bit chunky but here we are basically building a GET request to the authorization endpoint with the following parameters:

  1. response_type (string) - The value of this field should always be: code
  2. client_id (string) - The API Key value generated when you registered your application.
  3. redirect_uri (string) - The URI your users are sent back to after authorization. This value must match one of the Redirect URLs defined in your application configuration.
  4. state (string) - A unique string value of your choice that is hard to guess.
  5. scope (string) - URL-encoded, space-delimited list of member permissions your application is requesting on behalf of the user.

The state request parameter string is generated in the generate_CSRF_token() function that uses the secrets module to generate a random URL-safe text string, containing 32 bytes random bytes.

Using the webbrowser module we open the authorizatio url we just built in a new window of your default browser basically making a GET request with the above parameters. This directs to LinkedIn’s OAuth 2.0 authorization page, where the member either accepts or denies your application’s permission request.

linkedin

By providing valid LinkedIn credentials and clicking Allow, the member approves your application’s request to access their member data and interact with LinkedIn on their behalf.

linkedin

This approval instructs LinkedIn to redirect the member to the redirect URL that you defined in your redirect_uri parameter.

linkedin

Attached to the redirect_uri are two important URL arguments that you need to read from the request:

  • code - The OAuth 2.0 authorization code.
  • state - A value used to test for possible CSRF attacks.

I’ve setup a simple HTTP server with python that servers a simple HTML page on the redirect url and using ngrok exposed this web server running on my machine to LinkedIn. You dont need to do this.

linkedin

This is beyond the scope of the article and is completely optional. The python script then reads the redirect url as input and filters out the Authorization code. You will thus need to copy this url and paste it after the prompt Copy the URL of where you have been redirected to: In our case the url is:

https://4680-41-90-189-114.ngrok.io/?code=AQQC_KO33d5Nx2isO7hhxqj1PiTcfqUsaC5Ddf6Rjb0yxTzIMsqqHCJMt8bnXj0vIS3VUmathZvlEox-9r8-SJyuN5VL_9KeZS1ROYlTIatUoyIjlu7fw6KlclUNdskT5GYIK5UOtr3U5XCRBu5ztW9llPIEPcIhMZs_CktNmPr50mxRp48yvU18nqBrSXd46d_C3yhkRbbqJHJqJGo&state=qfkbrgqkmicggbgqeuer

The Authorization code filtered from the above url is:

AQQC_KO33d5Nx2isO7hhxqj1PiTcfqUsaC5Ddf6Rjb0yxTzIMsqqHCJMt8bnXj0vIS3VUmathZvlEox-9r8-SJyuN5VL_9KeZS1ROYlTIatUoyIjlu7fw6KlclUNdskT5GYIK5UOtr3U5XCRBu5ztW9llPIEPcIhMZs_CktNmPr50mxRp48yvU18nqBrSXd46d_C3yhkRbbqJHJqJGo

This authorization code is then stored as another environment variable in the authorize() function.

def authorize(client_id: str, redirect_url: str, env_path: Path):
    authorization_code = os.environ.get("AUTHORIZATION_CODE", None)
    if not authorization_code:
        authorization_code = request_authorization_code(
            authorization_url, client_id, redirect_url
        )
        set_key(env_path, "AUTHORIZATION_CODE", authorization_code)
    return authorization_code

Exchange Authorization Code for an Access Token

The next step is to get an access token for your application using the authorization code from the previous step. To generate an access token, issue a HTTP POST request against the accessToken url with a Content-Type header of x-www-form-urlencoded and the following parameters in the request body:

POST https://www.linkedin.com/oauth/v2/accessToken

Sample request with python

def get_access_token(
    auth_code: str, redirect_uri: str, client_id: str, client_secret: str
):
    # Request body parameters
    params = {
        "grant_type": "authorization_code",
        "code": auth_code,
        "redirect_uri": redirect_uri,
        "client_id": client_id,
        "client_secret": client_secret,
    }
    with requests.session() as s:
        response = s.post(url=ACCESS_TOKEN_URL, params=params)
    return response.json()["access_token"]

Here we make a HTTP POST request to the Access Token endpoint with the following parameters in the request body:

  1. grant_type (string) - The value of this field should always be: authorization_code
  2. code (string) - The authorization code obtained in the previous step.
  3. client_id (string) - The Client ID defined in your application configuration.
  4. client_secret (string) - The Client Secret Key defined in your application configuration.
  5. redirect_uri (string) - The same redirect_uri value that you passed in the previous steps.

Access Token Response

A successful access token request returns a JSON object containing the following fields:

  1. access_token (string) - The access token for the application.
  2. expires_in (int) - The number of seconds remaining until the token expires. Currently, all access tokens are issued with a 60-day lifespan.
{
    "access_token":"AQUeiSTK8hf_ZWIRlFL0yoOfXIzG3pSEl0DoNRWMff7xoqWn1M9SdYmvHUkL-OxeGE0joULvepSUH2txPpA1XS_mV-dzngJ6P1zZ40qwbolf1gcyvh-R1C4ElR-aDaKvqOIyZOlff...",
    "expires_in":5183999
}

Combined Authorization Code Flow

Now that we have the necessary Authorization functions we can combine them to a function that executes the authentication flow:

def main():
    env_path = Path.cwd() / ".env"
    load_dotenv(env_path)
    client_id, client_secret, redirect_url = (
        os.environ.get("CLIENT_ID", None),
        os.environ.get("CLIENT_SECRET", None),
        os.environ.get("REDIRECT_URL", None),
    )
    authorization_code = authorize(client_id, redirect_url, env_path)
    access_token = get_access_token(
        authorization_code, redirect_url, client_id, client_secret
    )

if __name__ == "__main__":
    main()

Here we:

  1. Define the file path to our .env file load it and read the environment variables.
  2. Obtain an Authorization Code
  3. Obtain an Access Token
  4. Run the encapsulating main() function

Make Authenticated Requests

Once you’ve obtained an access token, you can start making authenticated API requests on behalf of the member by including an Authorization header in the HTTP call to LinkedIn’s API.

Get User LinkedIn profile information

The Profile API returns a member’s LinkedIn profile, based on the access token. Make a HTTP GET request to the endpoint url:

GET https://api.linkedin.com/v2/me

Sample request with python

def get_linkedin_profile(access_token: str):
    # Request header
    header = {"Authorization": f"Bearer {access_token}"}
    with requests.session() as s:
        response = s.get(USER_PROFILE_URL, headers=header)
    return response.json()

Note how the access token is included in the header of the request.

Sample Response

A successful profile information request returns a JSON object of the form:

{
   "localizedLastName":"Nduati",
   "profilePicture":{
      "displayImage":"urn:li:digitalmediaAsset:C4D03AQEHi4lfUDPbaQ"
   },
   "firstName":{
      "localized":{
         "en_US":"Daniel"
      },
      "preferredLocale":{
         "country":"US",
         "language":"en"
      }
   },
   "lastName":{
      "localized":{
         "en_US":"Nduati"
      },
      "preferredLocale":{
         "country":"US",
         "language":"en"
      }
   },
   "id":"uvoFu7gji4",
   "localizedFirstName":"Daniel"
}

Share on LinkedIn from your Personal Profile

There are multiple ways to share content with your LinkedIn network. You can create shares using text, URLs, and images. In this article i have selected to share an article from my dev.to account. Your application may post shares in the context of a specific member or organization. Use a URN in the owner field to associate the share with an organization or authenticated member. The valid URN formats are urn:li:person:{id}.

You will need the permission w_member_social to create shares on behalf of a member, or w_organization_social to create shares on behalf of an organization.

POST https://api.linkedin.com/v2/shares

Request body schema

  1. owner - Owner of the share
  2. text - Text of the share
  3. content - Referenced content such as articles and images
  4. distribution - Distribution target for the share

Create an Atricle Share

Sample Request Body

{
    "content": {
        "contentEntities": [
            {
                "entityLocation": "https://dev.to/danchei99/getting-started-with-fast-api-and-docker-part-1-54oo",
                "thumbnails": [
                    {
                        "resolvedUrl": "https://res.cloudinary.com/practicaldev/image/fetch/s--k24egrxV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8aifzgpakwlbxzukgaaj.png"
                    }
                ]
            }
        ],
        "title": "Getting Started with Fast API and Docker"
    },
    "distribution": {
        "linkedInDistributionTarget": {}
    },
    "owner": "urn:li:person:<person_id>",
    "text": {
        "text": "Learn more about APIs and Docker by reading the Blog!"
    }
}

Sample request with python

def share_article(access_token: str, profile_id: str):
    headers = {
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json",
    }
    payload = {
        "content": {
            "contentEntities": [
                {
                    "entityLocation": "https://dev.to/danchei99/getting-started-with-fast-api-and-docker-part-1-54oo",
                    "thumbnails": [
                        {
                            "resolvedUrl": "https://res.cloudinary.com/practicaldev/image/fetch/s--k24egrxV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8aifzgpakwlbxzukgaaj.png"
                        }
                    ],
                }
            ],
            "title": "Getting Started with Fast API and Docker",
        },
        "distribution": {"linkedInDistributionTarget": {}},
        "owner": f"urn:li:person:{profile_id}",
        "text": {"text": "Learn more about APIs and Docker by reading the Blog!"},
    }
    with requests.session() as s:
        response = s.post(url=SHARE_URL, headers=headers, json=payload)
    return response.json()

Sample Response

A successful share request returns a JSON response:

{
   "owner":"urn:li:person:uvoFu7gji4",
   "activity":"urn:li:activity:6956317238383509504",
   "edited":false,
   "created":{
      "actor":"urn:li:person:uvoFu7gji4",
      "time":1658515271742
   },
   "text":{
      "text":"Learn more about APIs and Docker by reading the Blog!"
   },
   "lastModified":{
      "actor":"urn:li:person:uvoFu7gji4",
      "time":1658515271742
   },
   "id":"6956317237402046465",
   "distribution":{
      "linkedInDistributionTarget":{
         "visibleToGuest":true
      }
   },
   "content":{
      "title":"Getting Started with Fast API and Docker",
      "contentEntities":[
         {
            "thumbnails":[
               {
                  "imageSpecificContent":{
                     
                  },
                  "resolvedUrl":"https://res.cloudinary.com/practicaldev/image/fetch/s--k24egrxV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8aifzgpakwlbxzukgaaj.png"
               }
            ],
            "title":"Getting Started with Fast API and Docker",
            "entityLocation":"https://dev.to/danchei99/getting-started-with-fast-api-and-docker-part-1-54oo"
         }
      ],
      "shareMediaCategory":"ARTICLE"
   }
}

Once the request is successful, the content will be shared from your personal profile and it will appear in the feed as follows:

linkedin

Full Code

Here is the complete code:

import os
import secrets
import string
import webbrowser
from pathlib import Path
from urllib.parse import parse_qs, urlparse

import requests
from dotenv import load_dotenv, set_key

AUTHORIZATION_URL = "https://www.linkedin.com/oauth/v2/authorization"
ACCESS_TOKEN_URL = "https://www.linkedin.com/oauth/v2/accessToken"
USER_PROFILE_URL = "https://api.linkedin.com/v2/me"
SHARE_URL = "https://api.linkedin.com/v2/shares"


def generate_CSRF_token():
    return secrets.token_urlsafe(32)


def parse_redirect_uri(redirect_response):
    """
    Extract the authorization code from the redirect uri.
    """
    url = urlparse(redirect_response)
    url = parse_qs(url.query)
    return url["code"][0]


def request_authorization_code(client_id: str, redirect_uri: str):
    csrf_token = generate_CSRF_token()
    params = {
        "response_type": "code",
        "client_id": client_id,
        "redirect_uri": redirect_uri,
        "state": csrf_token,
        "scope": "r_liteprofile,r_emailaddress,w_member_social",
    }
    with requests.session() as s:
        response = s.get(AUTHORIZATION_URL, params=params)
    OAuthURL = response.url
    webbrowser.open_new_tab(OAuthURL)
    print(
        f"""
    Copy the URL of where you have been redirected to:\n
    """
    )
    redirect_response = input("Paste the redirect URL here:")
    """
    Extract the authorization code from the redirect uri.
    """
    auth_code = parse_redirect_uri(redirect_response)
    return auth_code


def authorize(client_id: str, redirect_url: str, env_path: Path):
    authorization_code = os.environ.get("AUTHORIZATION_CODE", None)
    if not authorization_code:
        authorization_code = request_authorization_code(
            AUTHORIZATION_URL, client_id, redirect_url
        )
        set_key(env_path, "AUTHORIZATION_CODE", authorization_code)
    return authorization_code


def get_access_token(
    auth_code: str, redirect_uri: str, client_id: str, client_secret: str
):
    # Request body parameters
    params = {
        "grant_type": "authorization_code",
        "code": auth_code,
        "redirect_uri": redirect_uri,
        "client_id": client_id,
        "client_secret": client_secret,
    }
    with requests.session() as s:
        response = s.post(url=ACCESS_TOKEN_URL, params=params)
    return response.json()["access_token"]


def get_linkedin_profile(access_token: str):
    # Request header
    header = {"Authorization": f"Bearer {access_token}"}
    with requests.session() as s:
        response = s.get(USER_PROFILE_URL, headers=header)
    # return response.json()["id"]
    return response.json()


def share_article(access_token: str, profile_id: str):
    headers = {
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json",
    }
    payload = {
        "content": {
            "contentEntities": [
                {
                    "entityLocation": "https://dev.to/danchei99/getting-started-with-fast-api-and-docker-part-1-54oo",
                    "thumbnails": [
                        {
                            "resolvedUrl": "https://res.cloudinary.com/practicaldev/image/fetch/s--k24egrxV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8aifzgpakwlbxzukgaaj.png"
                        }
                    ],
                }
            ],
            "title": "Getting Started with Fast API and Docker",
        },
        "distribution": {"linkedInDistributionTarget": {}},
        "owner": f"urn:li:person:{profile_id}",
        "text": {"text": "Learn more about APIs and Docker by reading the Blog!"},
    }
    with requests.session() as s:
        response = s.post(url=SHARE_URL, headers=headers, json=payload)
    return response.json()


def main():
    env_path = Path.cwd() / ".env"
    load_dotenv(env_path)
    client_id, client_secret, redirect_url = (
        os.environ.get("CLIENT_ID", None),
        os.environ.get("CLIENT_SECRET", None),
        os.environ.get("REDIRECT_URL", None),
    )
    authorization_code = authorize(client_id, redirect_url, env_path)
    access_token = get_access_token(
        authorization_code, redirect_url, client_id, client_secret
    )
    user_profile = get_linkedin_profile(access_token)
    person_id = user_profile["id"]
    share_response = share_article(access_token, person_id)


if __name__ == "__main__":
    main()

Limitations of LinkedIn APIs

Starting on May 12, 2015, LinkedIn limited the open APIs to only support the following uses:

  • Enabling members to share professional content to their LinkedIn network from across the Web leveraging our Share API.
  • Enabling companies to share professional content to LinkedIn with our Company API.

The use of all other APIs was restricted to those developers approved by LinkedIn requiring developers to become members of one of their partnership programs which means having to reach out to your LinkedIn Relationship Manager or Business Development contact to get this and you will need to meet certain criteria and sign an API agreement with data restrictions in order to use this integration.

Conclusion

In this article, we have focused on how to use the LinkedIn REST APIs and share content from a personal profile. LinkedIn is a powerful platform to share content with your social network. Ensuring your content receives the professional audience it deserves.

This enables you:

  1. Get your content in front of an audience of millions of professionals.
  2. Drive traffic to your site and grow your member base.
  3. Benefit from having your content shared across multiple professional networks worldwide.

Resources

To understand LinkedIn Apis better, i recommend looking at the following resource:

  1. Api Documentation