How to Post to LinkedIn from the Command Line

By Scott Steinhardt — March 9, 2026

A step-by-step guide to using the LinkedIn API and a bash script to publish posts — including images — directly from your terminal.


What You'll Need


Step 1: Create a LinkedIn Developer App

  1. Go to linkedin.com/developers and sign in
  2. Click Create App
  3. Fill in the form — you'll need a LinkedIn Company Page (a throwaway one works fine)
  4. Go to the Products tab and request access to:
  5. Both should approve instantly

Step 2: Configure Your App

  1. In your app, go to the Auth tab
  2. Under Authorized redirect URLs, add: https://localhost
  3. Note down your Client ID and Client Secret

Step 3: Get an Authorization Code

Visit this URL in your browser (replace YOUR_CLIENT_ID):

https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=YOUR_CLIENT_ID&redirect_uri=https://localhost&scope=w_member_social%20openid%20profile

Log in and approve. Your browser will redirect to https://localhost/?code=LONG_STRING. Copy the value after code=.

Step 4: Exchange the Code for an Access Token

Run this quickly — the code expires in minutes:

curl -X POST 'https://www.linkedin.com/oauth/v2/accessToken' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'grant_type=authorization_code' \
  --data-urlencode 'code=YOUR_AUTHORIZATION_CODE' \
  --data-urlencode 'client_id=YOUR_CLIENT_ID' \
  --data-urlencode 'client_secret=YOUR_CLIENT_SECRET' \
  --data-urlencode 'redirect_uri=https://localhost'

Save the access_token from the response — it's valid for ~60 days.

Step 5: Get Your Person ID

curl -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
  'https://api.linkedin.com/v2/userinfo'

Look for the sub field in the response — that's your Person ID.

Step 6: Create the Script

Save this as linkedin-post.sh and fill in your TOKEN and PERSON_ID at the top:

#!/bin/bash
# Usage (text only):  ./linkedin-post.sh "Your caption"
# Usage (with image): ./linkedin-post.sh "Your caption" /path/to/image.jpg

TOKEN="YOUR_ACCESS_TOKEN"
PERSON_ID="YOUR_PERSON_ID"

CAPTION="$1"
IMAGE_PATH="$2"

if [ -z "$CAPTION" ]; then
  echo "Usage: ./linkedin-post.sh \"Your caption\" [/path/to/image.jpg]"
  exit 1
fi

CAPTION_JSON=$(echo "$CAPTION" | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read().strip()))')

if [ -n "$IMAGE_PATH" ]; then

  REGISTER_RESPONSE=$(curl -s -X POST \
    'https://api.linkedin.com/v2/assets?action=registerUpload' \
    -H "Authorization: Bearer $TOKEN" \
    -H 'Content-Type: application/json' \
    -H 'X-Restli-Protocol-Version: 2.0.0' \
    -d "{
      \"registerUploadRequest\": {
        \"recipes\": [\"urn:li:digitalmediaRecipe:feedshare-image\"],
        \"owner\": \"urn:li:person:$PERSON_ID\",
        \"serviceRelationships\": [{
          \"relationshipType\": \"OWNER\",
          \"identifier\": \"urn:li:userGeneratedContent\"
        }]
      }
    }")

  UPLOAD_URL=$(echo "$REGISTER_RESPONSE" | grep -o '"uploadUrl":"[^"]*"' | sed 's/"uploadUrl":"//;s/"//')
  ASSET_URN=$(echo "$REGISTER_RESPONSE" | grep -o '"asset":"[^"]*"' | sed 's/"asset":"//;s/"//')

  curl -s -X PUT "$UPLOAD_URL" \
    -H "Authorization: Bearer $TOKEN" \
    --upload-file "$IMAGE_PATH"

  curl -s -X POST 'https://api.linkedin.com/v2/ugcPosts' \
    -H "Authorization: Bearer $TOKEN" \
    -H 'Content-Type: application/json' \
    -H 'X-Restli-Protocol-Version: 2.0.0' \
    -d "{
      \"author\": \"urn:li:person:$PERSON_ID\",
      \"lifecycleState\": \"PUBLISHED\",
      \"specificContent\": {
        \"com.linkedin.ugc.ShareContent\": {
          \"shareCommentary\": {\"text\": $CAPTION_JSON},
          \"shareMediaCategory\": \"IMAGE\",
          \"media\": [{\"status\": \"READY\", \"media\": \"$ASSET_URN\"}]
        }
      },
      \"visibility\": {\"com.linkedin.ugc.MemberNetworkVisibility\": \"PUBLIC\"}
    }"

else

  curl -s -X POST 'https://api.linkedin.com/v2/ugcPosts' \
    -H "Authorization: Bearer $TOKEN" \
    -H 'Content-Type: application/json' \
    -H 'X-Restli-Protocol-Version: 2.0.0' \
    -d "{
      \"author\": \"urn:li:person:$PERSON_ID\",
      \"lifecycleState\": \"PUBLISHED\",
      \"specificContent\": {
        \"com.linkedin.ugc.ShareContent\": {
          \"shareCommentary\": {\"text\": $CAPTION_JSON},
          \"shareMediaCategory\": \"NONE\"
        }
      },
      \"visibility\": {\"com.linkedin.ugc.MemberNetworkVisibility\": \"PUBLIC\"}
    }"

fi

Make it executable:

chmod +x linkedin-post.sh

Step 7: Post!

# Text only
./linkedin-post.sh "Hello from the command line!"

# With image
./linkedin-post.sh "Hello from the command line!" ~/Desktop/photo.jpg

# Multi-paragraph post
./linkedin-post.sh $'Paragraph one.\n\nParagraph two.'

Notes