← Skills

linkedin-api

LinkedIn API integration for profile data and messaging

Used by


name: linkedin description: | LinkedIn API integration with managed OAuth. Share posts, manage profile, run ads, and access LinkedIn features. Use this skill when users want to share content on LinkedIn, manage ad campaigns, get profile/organization information, or interact with LinkedIn's platform. For other third party apps, use the api-gateway skill (https://clawhub.ai/byungkyu/api-gateway). Requires network access and valid Maton API key. metadata: author: maton version: "1.0" clawdbot: emoji: 🧠 requires: env: - MATON_API_KEY

LinkedIn

Access the LinkedIn API with managed OAuth authentication. Share posts, manage advertising campaigns, retrieve profile and organization information, upload media, and access the Ad Library.

Quick Start

# Get current user profile python <<'EOF' import urllib.request, os, json req = urllib.request.Request('https://gateway.maton.ai/linkedin/rest/me') req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}') req.add_header('LinkedIn-Version', '202506') print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2)) EOF

Base URL

https://gateway.maton.ai/linkedin/rest/{resource}

The gateway proxies requests to api.linkedin.com and automatically injects your OAuth token.

Authentication

All requests require the Maton API key in the Authorization header:

Authorization: Bearer $MATON_API_KEY

Environment Variable: Set your API key as MATON_API_KEY:

export MATON_API_KEY="YOUR_API_KEY"

Getting Your API Key

  1. Sign in or create an account at maton.ai
  2. Go to maton.ai/settings
  3. Copy your API key

Required Headers

LinkedIn REST API requires the version header:

LinkedIn-Version: 202506

Connection Management

Manage your LinkedIn OAuth connections at https://ctrl.maton.ai.

List Connections

python <<'EOF' import urllib.request, os, json req = urllib.request.Request('https://ctrl.maton.ai/connections?app=linkedin&status=ACTIVE') req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}') print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2)) EOF

Create Connection

python <<'EOF' import urllib.request, os, json data = json.dumps({'app': 'linkedin'}).encode() req = urllib.request.Request('https://ctrl.maton.ai/connections', data=data, method='POST') req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}') req.add_header('Content-Type', 'application/json') print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2)) EOF

Get Connection

python <<'EOF' import urllib.request, os, json req = urllib.request.Request('https://ctrl.maton.ai/connections/{connection_id}') req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}') print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2)) EOF

Response:

{ "connection": { "connection_id": "ba10eb9e-b590-4e95-8c2e-3901ff94642a", "status": "ACTIVE", "creation_time": "2026-02-07T08:00:24.372659Z", "last_updated_time": "2026-02-07T08:05:16.609085Z", "url": "https://connect.maton.ai/?session_token=...", "app": "linkedin", "metadata": {} } }

Open the returned url in a browser to complete OAuth authorization.

Delete Connection

python <<'EOF' import urllib.request, os, json req = urllib.request.Request('https://ctrl.maton.ai/connections/{connection_id}', method='DELETE') req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}') print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2)) EOF

Specifying Connection

If you have multiple LinkedIn connections, specify which one to use with the Maton-Connection header:

python <<'EOF' import urllib.request, os, json req = urllib.request.Request('https://gateway.maton.ai/linkedin/rest/me') req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}') req.add_header('LinkedIn-Version', '202506') req.add_header('Maton-Connection', 'ba10eb9e-b590-4e95-8c2e-3901ff94642a') print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2)) EOF

If omitted, the gateway uses the default (oldest) active connection.

API Reference

Profile

Get Current User Profile

GET /linkedin/rest/me LinkedIn-Version: 202506

Example:

python <<'EOF' import urllib.request, os, json req = urllib.request.Request('https://gateway.maton.ai/linkedin/rest/me') req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}') req.add_header('LinkedIn-Version', '202506') print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2)) EOF

Response:

{ "firstName": { "localized": {"en_US": "John"}, "preferredLocale": {"country": "US", "language": "en"} }, "localizedFirstName": "John", "lastName": { "localized": {"en_US": "Doe"}, "preferredLocale": {"country": "US", "language": "en"} }, "localizedLastName": "Doe", "id": "yrZCpj2Z12", "vanityName": "johndoe", "localizedHeadline": "Software Engineer at Example Corp", "profilePicture": { "displayImage": "urn:li:digitalmediaAsset:C4D00AAAAbBCDEFGhiJ" } }

Sharing Posts

Create a Text Post

POST /linkedin/rest/posts Content-Type: application/json LinkedIn-Version: 202506 { "author": "urn:li:person:{personId}", "lifecycleState": "PUBLISHED", "visibility": "PUBLIC", "commentary": "Hello LinkedIn! This is my first API post.", "distribution": { "feedDistribution": "MAIN_FEED" } }

Response: 201 Created with x-restli-id header containing the post URN.

Create an Article/URL Share

POST /linkedin/rest/posts Content-Type: application/json LinkedIn-Version: 202506 { "author": "urn:li:person:{personId}", "lifecycleState": "PUBLISHED", "visibility": "PUBLIC", "commentary": "Check out this great article!", "distribution": { "feedDistribution": "MAIN_FEED" }, "content": { "article": { "source": "https://example.com/article", "title": "Article Title", "description": "Article description here" } } }

Create an Image Post

First, initialize the image upload, then upload the image, then create the post.

Step 1: Initialize Image Upload

POST /linkedin/rest/images?action=initializeUpload Content-Type: application/json LinkedIn-Version: 202506 { "initializeUploadRequest": { "owner": "urn:li:person:{personId}" } }

Response:

{ "value": { "uploadUrlExpiresAt": 1770541529250, "uploadUrl": "https://www.linkedin.com/dms-uploads/...", "image": "urn:li:image:D4D10AQH4GJAjaFCkHQ" } }

Step 2: Upload Image Binary

PUT {uploadUrl from step 1} Content-Type: image/png {binary image data}

Step 3: Create Image Post

POST /linkedin/rest/posts Content-Type: application/json LinkedIn-Version: 202506 { "author": "urn:li:person:{personId}", "lifecycleState": "PUBLISHED", "visibility": "PUBLIC", "commentary": "Check out this image!", "distribution": { "feedDistribution": "MAIN_FEED" }, "content": { "media": { "id": "urn:li:image:D4D10AQH4GJAjaFCkHQ", "title": "Image Title" } } }

Visibility Options

ValueDescription
PUBLICViewable by anyone on LinkedIn
CONNECTIONSViewable by 1st-degree connections only

Share Media Categories

ValueDescription
NONEText-only post
ARTICLEURL/article share
IMAGEImage post
VIDEOVideo post

Ad Library (Public Data)

The Ad Library API provides access to public advertising data on LinkedIn. These endpoints use the REST API with version headers.

Required Headers for Ad Library

LinkedIn-Version: 202506

Search Ads

GET /linkedin/rest/adLibrary?q=criteria&keyword={keyword}

Query parameters:

  • keyword (string): Search ad content (multiple keywords use AND logic)
  • advertiser (string): Search by advertiser name
  • countries (array): Filter by ISO 3166-1 alpha-2 country codes
  • dateRange (object): Filter by served dates
  • start (integer): Pagination offset
  • count (integer): Results per page (max 25)

Example - Search ads by keyword:

GET /linkedin/rest/adLibrary?q=criteria&keyword=linkedin

Example - Search ads by advertiser:

GET /linkedin/rest/adLibrary?q=criteria&advertiser=microsoft

Response:

{ "paging": { "start": 0, "count": 10, "total": 11619543, "links": [...] }, "elements": [ { "adUrl": "https://www.linkedin.com/ad-library/detail/...", "details": { "advertiser": {...}, "adType": "TEXT_AD", "targeting": {...}, "statistics": { "firstImpressionDate": 1704067200000, "latestImpressionDate": 1706745600000, "impressionsFrom": 1000, "impressionsTo": 5000 } }, "isRestricted": false } ] }

Search Job Postings

GET /linkedin/rest/jobLibrary?q=criteria&keyword={keyword}

Note: Job Library requires version 202506.

Query parameters:

  • keyword (string): Search job content
  • organization (string): Filter by company name
  • countries (array): Filter by country codes
  • dateRange (object): Filter by posting dates
  • start (integer): Pagination offset
  • count (integer): Results per page (max 24)

Example:

GET /linkedin/rest/jobLibrary?q=criteria&keyword=software&organization=google

Response includes:

  • jobPostingUrl: Link to job listing
  • jobDetails: Title, location, description, salary, benefits
  • statistics: Impression data

Marketing API (Advertising)

The Marketing API provides access to LinkedIn's advertising platform. These endpoints use the versioned REST API.

Required Headers for Marketing API

LinkedIn-Version: 202506

List Ad Accounts

GET /linkedin/rest/adAccounts?q=search

Returns all ad accounts accessible by the authenticated user.

Response:

{ "paging": { "start": 0, "count": 10, "links": [] }, "elements": [ { "id": 123456789, "name": "My Ad Account", "status": "ACTIVE", "type": "BUSINESS", "currency": "USD", "reference": "urn:li:organization:12345" } ] }

Get Ad Account

GET /linkedin/rest/adAccounts/{adAccountId}

Create Ad Account

POST /linkedin/rest/adAccounts Content-Type: application/json { "name": "New Ad Account", "currency": "USD", "reference": "urn:li:organization:{orgId}", "type": "BUSINESS" }

Update Ad Account

POST /linkedin/rest/adAccounts/{adAccountId} Content-Type: application/json X-RestLi-Method: PARTIAL_UPDATE { "patch": { "$set": { "name": "Updated Account Name" } } }

List Campaign Groups

Campaign groups are nested under ad accounts:

GET /linkedin/rest/adAccounts/{adAccountId}/adCampaignGroups

Create Campaign Group

POST /linkedin/rest/adAccounts/{adAccountId}/adCampaignGroups Content-Type: application/json { "name": "Q1 2026 Campaigns", "status": "DRAFT", "runSchedule": { "start": 1704067200000, "end": 1711929600000 }, "totalBudget": { "amount": "10000", "currencyCode": "USD" } }

Get Campaign Group

GET /linkedin/rest/adAccounts/{adAccountId}/adCampaignGroups/{campaignGroupId}

Update Campaign Group

POST /linkedin/rest/adAccounts/{adAccountId}/adCampaignGroups/{campaignGroupId} Content-Type: application/json X-RestLi-Method: PARTIAL_UPDATE { "patch": { "$set": { "status": "ACTIVE" } } }

Delete Campaign Group

DELETE /linkedin/rest/adAccounts/{adAccountId}/adCampaignGroups/{campaignGroupId}

List Campaigns

Campaigns are also nested under ad accounts:

GET /linkedin/rest/adAccounts/{adAccountId}/adCampaigns

Create Campaign

POST /linkedin/rest/adAccounts/{adAccountId}/adCampaigns Content-Type: application/json { "campaignGroup": "urn:li:sponsoredCampaignGroup:123456", "name": "Brand Awareness Campaign", "status": "DRAFT", "type": "SPONSORED_UPDATES", "objectiveType": "BRAND_AWARENESS", "dailyBudget": { "amount": "100", "currencyCode": "USD" }, "costType": "CPM", "unitCost": { "amount": "5", "currencyCode": "USD" }, "locale": { "country": "US", "language": "en" } }

Get Campaign

GET /linkedin/rest/adAccounts/{adAccountId}/adCampaigns/{campaignId}

Update Campaign

POST /linkedin/rest/adAccounts/{adAccountId}/adCampaigns/{campaignId} Content-Type: application/json X-RestLi-Method: PARTIAL_UPDATE { "patch": { "$set": { "status": "ACTIVE" } } }

Delete Campaign

DELETE /linkedin/rest/adAccounts/{adAccountId}/adCampaigns/{campaignId}

Campaign Status Values

StatusDescription
DRAFTCampaign is in draft mode
ACTIVECampaign is running
PAUSEDCampaign is paused
ARCHIVEDCampaign is archived
COMPLETEDCampaign has ended
CANCELEDCampaign was canceled

Campaign Objective Types

ObjectiveDescription
BRAND_AWARENESSIncrease brand visibility
WEBSITE_VISITSDrive traffic to website
ENGAGEMENTIncrease post engagement
VIDEO_VIEWSMaximize video views
LEAD_GENERATIONCollect leads via Lead Gen Forms
WEBSITE_CONVERSIONSDrive website conversions
JOB_APPLICANTSAttract job applications

Organizations

List Organization ACLs

Get organizations the authenticated user has access to:

GET /linkedin/rest/organizationAcls?q=roleAssignee LinkedIn-Version: 202506

Response:

{ "paging": { "start": 0, "count": 10, "total": 2 }, "elements": [ { "role": "ADMINISTRATOR", "organization": "urn:li:organization:12345", "state": "APPROVED" } ] }

Get Organization

GET /linkedin/rest/organizations/{organizationId} LinkedIn-Version: 202506

Lookup Organization by Vanity Name

GET /linkedin/rest/organizations?q=vanityName&vanityName={vanityName}

Example:

GET /linkedin/rest/organizations?q=vanityName&vanityName=microsoft

Response:

{ "elements": [ { "vanityName": "microsoft", "localizedName": "Microsoft", "website": { "localized": {"en_US": "https://news.microsoft.com/"} } } ] }

Get Organization Share Statistics

GET /linkedin/rest/organizationalEntityShareStatistics?q=organizationalEntity&organizationalEntity={orgUrn}

Example:

GET /linkedin/rest/organizationalEntityShareStatistics?q=organizationalEntity&organizationalEntity=urn:li:organization:12345

Get Organization Posts

GET /linkedin/rest/posts?q=author&author={orgUrn}

Example:

GET /linkedin/rest/posts?q=author&author=urn:li:organization:12345

Media Upload (REST API)

The REST API provides modern media upload endpoints. All require version header LinkedIn-Version: 202506.

Initialize Image Upload

POST /linkedin/rest/images?action=initializeUpload Content-Type: application/json LinkedIn-Version: 202506 { "initializeUploadRequest": { "owner": "urn:li:person:{personId}" } }

Response:

{ "value": { "uploadUrlExpiresAt": 1770541529250, "uploadUrl": "https://www.linkedin.com/dms-uploads/...", "image": "urn:li:image:D4D10AQH4GJAjaFCkHQ" } }

Use the uploadUrl to PUT your image binary, then use the image URN in your post.

Initialize Video Upload

POST /linkedin/rest/videos?action=initializeUpload Content-Type: application/json LinkedIn-Version: 202506 { "initializeUploadRequest": { "owner": "urn:li:person:{personId}", "fileSizeBytes": 10000000, "uploadCaptions": false, "uploadThumbnail": false } }

Response:

{ "value": { "uploadUrlsExpireAt": 1770541530110, "video": "urn:li:video:D4D10AQE_p-P_odQhXQ", "uploadInstructions": [ {"uploadUrl": "https://www.linkedin.com/dms-uploads/..."} ] } }

Initialize Document Upload

POST /linkedin/rest/documents?action=initializeUpload Content-Type: application/json LinkedIn-Version: 202506 { "initializeUploadRequest": { "owner": "urn:li:person:{personId}" } }

Response:

{ "value": { "uploadUrlExpiresAt": 1770541530896, "uploadUrl": "https://www.linkedin.com/dms-uploads/...", "document": "urn:li:document:D4D10AQHr-e30QZCAjQ" } }

Ad Targeting

Get Available Targeting Facets

GET /linkedin/rest/adTargetingFacets

Returns all available targeting facets for ad campaigns (31 facets including employers, degrees, skills, locations, industries, etc.).

Response:

{ "elements": [ { "facetName": "skills", "adTargetingFacetUrn": "urn:li:adTargetingFacet:skills", "entityTypes": ["SKILL"], "availableEntityFinders": ["AD_TARGETING_FACET", "TYPEAHEAD"] }, { "facetName": "industries", "adTargetingFacetUrn": "urn:li:adTargetingFacet:industries" } ] }

Available targeting facets include:

  • skills - Member skills
  • industries - Industry categories
  • titles - Job titles
  • seniorities - Seniority levels
  • degrees - Educational degrees
  • schools - Educational institutions
  • employers / employersPast - Current/past employers
  • locations / geoLocations - Geographic targeting
  • companySize - Company size ranges
  • genders - Gender targeting
  • ageRanges - Age range targeting

Getting Your Person ID

To create posts, you need your LinkedIn person ID. Get it from the /rest/me endpoint:

python <<'EOF' import urllib.request, os, json req = urllib.request.Request('https://gateway.maton.ai/linkedin/rest/me') req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}') req.add_header('LinkedIn-Version', '202506') result = json.load(urllib.request.urlopen(req)) print(f"Your person URN: urn:li:person:{result['id']}") EOF

Code Examples

JavaScript - Create Text Post

const personId = 'YOUR_PERSON_ID'; const response = await fetch( 'https://gateway.maton.ai/linkedin/rest/posts', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.MATON_API_KEY}`, 'Content-Type': 'application/json', 'LinkedIn-Version': '202506' }, body: JSON.stringify({ author: `urn:li:person:${personId}`, lifecycleState: 'PUBLISHED', visibility: 'PUBLIC', commentary: 'Hello from the API!', distribution: { feedDistribution: 'MAIN_FEED' } }) } );

Python - Create Text Post

import os import requests person_id = 'YOUR_PERSON_ID' response = requests.post( 'https://gateway.maton.ai/linkedin/rest/posts', headers={ 'Authorization': f'Bearer {os.environ["MATON_API_KEY"]}', 'Content-Type': 'application/json', 'LinkedIn-Version': '202506' }, json={ 'author': f'urn:li:person:{person_id}', 'lifecycleState': 'PUBLISHED', 'visibility': 'PUBLIC', 'commentary': 'Hello from the API!', 'distribution': { 'feedDistribution': 'MAIN_FEED' } } )

Rate Limits

Throttle TypeDaily Limit (UTC)
Member150 requests/day
Application100,000 requests/day

Notes

  • Person IDs are unique per application and not transferable across apps
  • The author field must use URN format: urn:li:person:{personId}
  • All posts require lifecycleState: "PUBLISHED"
  • Image/video uploads are a 3-step process: initialize upload, upload binary, create post
  • Include LinkedIn-Version: 202506 header for all REST API calls
  • Profile picture URLs may expire; re-fetch if needed
  • IMPORTANT: When using curl commands, use curl -g when URLs contain brackets to disable glob parsing
  • IMPORTANT: When piping curl output to jq or other commands, environment variables like $MATON_API_KEY may not expand correctly in some shell environments

Error Handling

StatusMeaning
400Missing LinkedIn connection or invalid request
401Invalid or missing Maton API key
403Insufficient permissions (check OAuth scopes)
404Resource not found
422Invalid request body or URN format
429Rate limited
4xx/5xxPassthrough error from LinkedIn API

Error Response Format

{ "status": 403, "serviceErrorCode": 100, "code": "ACCESS_DENIED", "message": "Not enough permissions to access resource" }

Troubleshooting: API Key Issues

  1. Check that the MATON_API_KEY environment variable is set:
echo $MATON_API_KEY
  1. Verify the API key is valid by listing connections:
python <<'EOF' import urllib.request, os, json req = urllib.request.Request('https://ctrl.maton.ai/connections') req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}') print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2)) EOF

Troubleshooting: Invalid App Name

  1. Ensure your URL path starts with linkedin. For example:
  • Correct: https://gateway.maton.ai/linkedin/rest/me
  • Incorrect: https://gateway.maton.ai/rest/me

OAuth Scopes

ScopeDescription
openidOpenID Connect authentication
profileRead basic profile
emailRead email address
w_member_socialCreate, modify, and delete posts

Resources

View raw SKILL.md
---
name: linkedin
description: |
  LinkedIn API integration with managed OAuth. Share posts, manage profile, run ads, and access LinkedIn features.
  Use this skill when users want to share content on LinkedIn, manage ad campaigns, get profile/organization information, or interact with LinkedIn's platform.
  For other third party apps, use the api-gateway skill (https://clawhub.ai/byungkyu/api-gateway).
  Requires network access and valid Maton API key.
metadata:
  author: maton
  version: "1.0"
  clawdbot:
    emoji: 🧠
    requires:
      env:
        - MATON_API_KEY
---

# LinkedIn

Access the LinkedIn API with managed OAuth authentication. Share posts, manage advertising campaigns, retrieve profile and organization information, upload media, and access the Ad Library.

## Quick Start

```bash
# Get current user profile
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://gateway.maton.ai/linkedin/rest/me')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
req.add_header('LinkedIn-Version', '202506')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
```

## Base URL

```
https://gateway.maton.ai/linkedin/rest/{resource}
```

The gateway proxies requests to `api.linkedin.com` and automatically injects your OAuth token.

## Authentication

All requests require the Maton API key in the Authorization header:

```
Authorization: Bearer $MATON_API_KEY
```

**Environment Variable:** Set your API key as `MATON_API_KEY`:

```bash
export MATON_API_KEY="YOUR_API_KEY"
```

### Getting Your API Key

1. Sign in or create an account at [maton.ai](https://maton.ai)
2. Go to [maton.ai/settings](https://maton.ai/settings)
3. Copy your API key

### Required Headers

LinkedIn REST API requires the version header:

```
LinkedIn-Version: 202506
```

## Connection Management

Manage your LinkedIn OAuth connections at `https://ctrl.maton.ai`.

### List Connections

```bash
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://ctrl.maton.ai/connections?app=linkedin&status=ACTIVE')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
```

### Create Connection

```bash
python <<'EOF'
import urllib.request, os, json
data = json.dumps({'app': 'linkedin'}).encode()
req = urllib.request.Request('https://ctrl.maton.ai/connections', data=data, method='POST')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
req.add_header('Content-Type', 'application/json')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
```

### Get Connection

```bash
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://ctrl.maton.ai/connections/{connection_id}')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
```

**Response:**
```json
{
  "connection": {
    "connection_id": "ba10eb9e-b590-4e95-8c2e-3901ff94642a",
    "status": "ACTIVE",
    "creation_time": "2026-02-07T08:00:24.372659Z",
    "last_updated_time": "2026-02-07T08:05:16.609085Z",
    "url": "https://connect.maton.ai/?session_token=...",
    "app": "linkedin",
    "metadata": {}
  }
}
```

Open the returned `url` in a browser to complete OAuth authorization.

### Delete Connection

```bash
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://ctrl.maton.ai/connections/{connection_id}', method='DELETE')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
```

### Specifying Connection

If you have multiple LinkedIn connections, specify which one to use with the `Maton-Connection` header:

```bash
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://gateway.maton.ai/linkedin/rest/me')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
req.add_header('LinkedIn-Version', '202506')
req.add_header('Maton-Connection', 'ba10eb9e-b590-4e95-8c2e-3901ff94642a')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
```

If omitted, the gateway uses the default (oldest) active connection.

## API Reference

### Profile

#### Get Current User Profile

```bash
GET /linkedin/rest/me
LinkedIn-Version: 202506
```

**Example:**
```bash
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://gateway.maton.ai/linkedin/rest/me')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
req.add_header('LinkedIn-Version', '202506')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
```

**Response:**
```json
{
  "firstName": {
    "localized": {"en_US": "John"},
    "preferredLocale": {"country": "US", "language": "en"}
  },
  "localizedFirstName": "John",
  "lastName": {
    "localized": {"en_US": "Doe"},
    "preferredLocale": {"country": "US", "language": "en"}
  },
  "localizedLastName": "Doe",
  "id": "yrZCpj2Z12",
  "vanityName": "johndoe",
  "localizedHeadline": "Software Engineer at Example Corp",
  "profilePicture": {
    "displayImage": "urn:li:digitalmediaAsset:C4D00AAAAbBCDEFGhiJ"
  }
}
```

### Sharing Posts

#### Create a Text Post

```bash
POST /linkedin/rest/posts
Content-Type: application/json
LinkedIn-Version: 202506

{
  "author": "urn:li:person:{personId}",
  "lifecycleState": "PUBLISHED",
  "visibility": "PUBLIC",
  "commentary": "Hello LinkedIn! This is my first API post.",
  "distribution": {
    "feedDistribution": "MAIN_FEED"
  }
}
```

**Response:** `201 Created` with `x-restli-id` header containing the post URN.

#### Create an Article/URL Share

```bash
POST /linkedin/rest/posts
Content-Type: application/json
LinkedIn-Version: 202506

{
  "author": "urn:li:person:{personId}",
  "lifecycleState": "PUBLISHED",
  "visibility": "PUBLIC",
  "commentary": "Check out this great article!",
  "distribution": {
    "feedDistribution": "MAIN_FEED"
  },
  "content": {
    "article": {
      "source": "https://example.com/article",
      "title": "Article Title",
      "description": "Article description here"
    }
  }
}
```

#### Create an Image Post

First, initialize the image upload, then upload the image, then create the post.

**Step 1: Initialize Image Upload**
```bash
POST /linkedin/rest/images?action=initializeUpload
Content-Type: application/json
LinkedIn-Version: 202506

{
  "initializeUploadRequest": {
    "owner": "urn:li:person:{personId}"
  }
}
```

**Response:**
```json
{
  "value": {
    "uploadUrlExpiresAt": 1770541529250,
    "uploadUrl": "https://www.linkedin.com/dms-uploads/...",
    "image": "urn:li:image:D4D10AQH4GJAjaFCkHQ"
  }
}
```

**Step 2: Upload Image Binary**
```bash
PUT {uploadUrl from step 1}
Content-Type: image/png

{binary image data}
```

**Step 3: Create Image Post**
```bash
POST /linkedin/rest/posts
Content-Type: application/json
LinkedIn-Version: 202506

{
  "author": "urn:li:person:{personId}",
  "lifecycleState": "PUBLISHED",
  "visibility": "PUBLIC",
  "commentary": "Check out this image!",
  "distribution": {
    "feedDistribution": "MAIN_FEED"
  },
  "content": {
    "media": {
      "id": "urn:li:image:D4D10AQH4GJAjaFCkHQ",
      "title": "Image Title"
    }
  }
}
```

### Visibility Options

| Value | Description |
|-------|-------------|
| `PUBLIC` | Viewable by anyone on LinkedIn |
| `CONNECTIONS` | Viewable by 1st-degree connections only |

### Share Media Categories

| Value | Description |
|-------|-------------|
| `NONE` | Text-only post |
| `ARTICLE` | URL/article share |
| `IMAGE` | Image post |
| `VIDEO` | Video post |

### Ad Library (Public Data)

The Ad Library API provides access to public advertising data on LinkedIn. These endpoints use the REST API with version headers.

#### Required Headers for Ad Library

```
LinkedIn-Version: 202506
```

#### Search Ads

```bash
GET /linkedin/rest/adLibrary?q=criteria&keyword={keyword}
```

Query parameters:
- `keyword` (string): Search ad content (multiple keywords use AND logic)
- `advertiser` (string): Search by advertiser name
- `countries` (array): Filter by ISO 3166-1 alpha-2 country codes
- `dateRange` (object): Filter by served dates
- `start` (integer): Pagination offset
- `count` (integer): Results per page (max 25)

**Example - Search ads by keyword:**
```bash
GET /linkedin/rest/adLibrary?q=criteria&keyword=linkedin
```

**Example - Search ads by advertiser:**
```bash
GET /linkedin/rest/adLibrary?q=criteria&advertiser=microsoft
```

**Response:**
```json
{
  "paging": {
    "start": 0,
    "count": 10,
    "total": 11619543,
    "links": [...]
  },
  "elements": [
    {
      "adUrl": "https://www.linkedin.com/ad-library/detail/...",
      "details": {
        "advertiser": {...},
        "adType": "TEXT_AD",
        "targeting": {...},
        "statistics": {
          "firstImpressionDate": 1704067200000,
          "latestImpressionDate": 1706745600000,
          "impressionsFrom": 1000,
          "impressionsTo": 5000
        }
      },
      "isRestricted": false
    }
  ]
}
```

#### Search Job Postings

```bash
GET /linkedin/rest/jobLibrary?q=criteria&keyword={keyword}
```

**Note:** Job Library requires version `202506`.

Query parameters:
- `keyword` (string): Search job content
- `organization` (string): Filter by company name
- `countries` (array): Filter by country codes
- `dateRange` (object): Filter by posting dates
- `start` (integer): Pagination offset
- `count` (integer): Results per page (max 24)

**Example:**
```bash
GET /linkedin/rest/jobLibrary?q=criteria&keyword=software&organization=google
```

**Response includes:**
- `jobPostingUrl`: Link to job listing
- `jobDetails`: Title, location, description, salary, benefits
- `statistics`: Impression data

### Marketing API (Advertising)

The Marketing API provides access to LinkedIn's advertising platform. These endpoints use the versioned REST API.

#### Required Headers for Marketing API

```
LinkedIn-Version: 202506
```

#### List Ad Accounts

```bash
GET /linkedin/rest/adAccounts?q=search
```

Returns all ad accounts accessible by the authenticated user.

**Response:**
```json
{
  "paging": {
    "start": 0,
    "count": 10,
    "links": []
  },
  "elements": [
    {
      "id": 123456789,
      "name": "My Ad Account",
      "status": "ACTIVE",
      "type": "BUSINESS",
      "currency": "USD",
      "reference": "urn:li:organization:12345"
    }
  ]
}
```

#### Get Ad Account

```bash
GET /linkedin/rest/adAccounts/{adAccountId}
```

#### Create Ad Account

```bash
POST /linkedin/rest/adAccounts
Content-Type: application/json

{
  "name": "New Ad Account",
  "currency": "USD",
  "reference": "urn:li:organization:{orgId}",
  "type": "BUSINESS"
}
```

#### Update Ad Account

```bash
POST /linkedin/rest/adAccounts/{adAccountId}
Content-Type: application/json
X-RestLi-Method: PARTIAL_UPDATE

{
  "patch": {
    "$set": {
      "name": "Updated Account Name"
    }
  }
}
```

#### List Campaign Groups

Campaign groups are nested under ad accounts:

```bash
GET /linkedin/rest/adAccounts/{adAccountId}/adCampaignGroups
```

#### Create Campaign Group

```bash
POST /linkedin/rest/adAccounts/{adAccountId}/adCampaignGroups
Content-Type: application/json

{
  "name": "Q1 2026 Campaigns",
  "status": "DRAFT",
  "runSchedule": {
    "start": 1704067200000,
    "end": 1711929600000
  },
  "totalBudget": {
    "amount": "10000",
    "currencyCode": "USD"
  }
}
```

#### Get Campaign Group

```bash
GET /linkedin/rest/adAccounts/{adAccountId}/adCampaignGroups/{campaignGroupId}
```

#### Update Campaign Group

```bash
POST /linkedin/rest/adAccounts/{adAccountId}/adCampaignGroups/{campaignGroupId}
Content-Type: application/json
X-RestLi-Method: PARTIAL_UPDATE

{
  "patch": {
    "$set": {
      "status": "ACTIVE"
    }
  }
}
```

#### Delete Campaign Group

```bash
DELETE /linkedin/rest/adAccounts/{adAccountId}/adCampaignGroups/{campaignGroupId}
```

#### List Campaigns

Campaigns are also nested under ad accounts:

```bash
GET /linkedin/rest/adAccounts/{adAccountId}/adCampaigns
```

#### Create Campaign

```bash
POST /linkedin/rest/adAccounts/{adAccountId}/adCampaigns
Content-Type: application/json

{
  "campaignGroup": "urn:li:sponsoredCampaignGroup:123456",
  "name": "Brand Awareness Campaign",
  "status": "DRAFT",
  "type": "SPONSORED_UPDATES",
  "objectiveType": "BRAND_AWARENESS",
  "dailyBudget": {
    "amount": "100",
    "currencyCode": "USD"
  },
  "costType": "CPM",
  "unitCost": {
    "amount": "5",
    "currencyCode": "USD"
  },
  "locale": {
    "country": "US",
    "language": "en"
  }
}
```

#### Get Campaign

```bash
GET /linkedin/rest/adAccounts/{adAccountId}/adCampaigns/{campaignId}
```

#### Update Campaign

```bash
POST /linkedin/rest/adAccounts/{adAccountId}/adCampaigns/{campaignId}
Content-Type: application/json
X-RestLi-Method: PARTIAL_UPDATE

{
  "patch": {
    "$set": {
      "status": "ACTIVE"
    }
  }
}
```

#### Delete Campaign

```bash
DELETE /linkedin/rest/adAccounts/{adAccountId}/adCampaigns/{campaignId}
```

### Campaign Status Values

| Status | Description |
|--------|-------------|
| `DRAFT` | Campaign is in draft mode |
| `ACTIVE` | Campaign is running |
| `PAUSED` | Campaign is paused |
| `ARCHIVED` | Campaign is archived |
| `COMPLETED` | Campaign has ended |
| `CANCELED` | Campaign was canceled |

### Campaign Objective Types

| Objective | Description |
|-----------|-------------|
| `BRAND_AWARENESS` | Increase brand visibility |
| `WEBSITE_VISITS` | Drive traffic to website |
| `ENGAGEMENT` | Increase post engagement |
| `VIDEO_VIEWS` | Maximize video views |
| `LEAD_GENERATION` | Collect leads via Lead Gen Forms |
| `WEBSITE_CONVERSIONS` | Drive website conversions |
| `JOB_APPLICANTS` | Attract job applications |

### Organizations

#### List Organization ACLs

Get organizations the authenticated user has access to:

```bash
GET /linkedin/rest/organizationAcls?q=roleAssignee
LinkedIn-Version: 202506
```

**Response:**
```json
{
  "paging": {
    "start": 0,
    "count": 10,
    "total": 2
  },
  "elements": [
    {
      "role": "ADMINISTRATOR",
      "organization": "urn:li:organization:12345",
      "state": "APPROVED"
    }
  ]
}
```

#### Get Organization

```bash
GET /linkedin/rest/organizations/{organizationId}
LinkedIn-Version: 202506
```

#### Lookup Organization by Vanity Name

```bash
GET /linkedin/rest/organizations?q=vanityName&vanityName={vanityName}
```

**Example:**
```bash
GET /linkedin/rest/organizations?q=vanityName&vanityName=microsoft
```

**Response:**
```json
{
  "elements": [
    {
      "vanityName": "microsoft",
      "localizedName": "Microsoft",
      "website": {
        "localized": {"en_US": "https://news.microsoft.com/"}
      }
    }
  ]
}
```

#### Get Organization Share Statistics

```bash
GET /linkedin/rest/organizationalEntityShareStatistics?q=organizationalEntity&organizationalEntity={orgUrn}
```

**Example:**
```bash
GET /linkedin/rest/organizationalEntityShareStatistics?q=organizationalEntity&organizationalEntity=urn:li:organization:12345
```

#### Get Organization Posts

```bash
GET /linkedin/rest/posts?q=author&author={orgUrn}
```

**Example:**
```bash
GET /linkedin/rest/posts?q=author&author=urn:li:organization:12345
```

### Media Upload (REST API)

The REST API provides modern media upload endpoints. All require version header `LinkedIn-Version: 202506`.

#### Initialize Image Upload

```bash
POST /linkedin/rest/images?action=initializeUpload
Content-Type: application/json
LinkedIn-Version: 202506

{
  "initializeUploadRequest": {
    "owner": "urn:li:person:{personId}"
  }
}
```

**Response:**
```json
{
  "value": {
    "uploadUrlExpiresAt": 1770541529250,
    "uploadUrl": "https://www.linkedin.com/dms-uploads/...",
    "image": "urn:li:image:D4D10AQH4GJAjaFCkHQ"
  }
}
```

Use the `uploadUrl` to PUT your image binary, then use the `image` URN in your post.

#### Initialize Video Upload

```bash
POST /linkedin/rest/videos?action=initializeUpload
Content-Type: application/json
LinkedIn-Version: 202506

{
  "initializeUploadRequest": {
    "owner": "urn:li:person:{personId}",
    "fileSizeBytes": 10000000,
    "uploadCaptions": false,
    "uploadThumbnail": false
  }
}
```

**Response:**
```json
{
  "value": {
    "uploadUrlsExpireAt": 1770541530110,
    "video": "urn:li:video:D4D10AQE_p-P_odQhXQ",
    "uploadInstructions": [
      {"uploadUrl": "https://www.linkedin.com/dms-uploads/..."}
    ]
  }
}
```

#### Initialize Document Upload

```bash
POST /linkedin/rest/documents?action=initializeUpload
Content-Type: application/json
LinkedIn-Version: 202506

{
  "initializeUploadRequest": {
    "owner": "urn:li:person:{personId}"
  }
}
```

**Response:**
```json
{
  "value": {
    "uploadUrlExpiresAt": 1770541530896,
    "uploadUrl": "https://www.linkedin.com/dms-uploads/...",
    "document": "urn:li:document:D4D10AQHr-e30QZCAjQ"
  }
}
```

### Ad Targeting

#### Get Available Targeting Facets

```bash
GET /linkedin/rest/adTargetingFacets
```

Returns all available targeting facets for ad campaigns (31 facets including employers, degrees, skills, locations, industries, etc.).

**Response:**
```json
{
  "elements": [
    {
      "facetName": "skills",
      "adTargetingFacetUrn": "urn:li:adTargetingFacet:skills",
      "entityTypes": ["SKILL"],
      "availableEntityFinders": ["AD_TARGETING_FACET", "TYPEAHEAD"]
    },
    {
      "facetName": "industries",
      "adTargetingFacetUrn": "urn:li:adTargetingFacet:industries"
    }
  ]
}
```

Available targeting facets include:
- `skills` - Member skills
- `industries` - Industry categories
- `titles` - Job titles
- `seniorities` - Seniority levels
- `degrees` - Educational degrees
- `schools` - Educational institutions
- `employers` / `employersPast` - Current/past employers
- `locations` / `geoLocations` - Geographic targeting
- `companySize` - Company size ranges
- `genders` - Gender targeting
- `ageRanges` - Age range targeting

## Getting Your Person ID

To create posts, you need your LinkedIn person ID. Get it from the `/rest/me` endpoint:

```bash
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://gateway.maton.ai/linkedin/rest/me')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
req.add_header('LinkedIn-Version', '202506')
result = json.load(urllib.request.urlopen(req))
print(f"Your person URN: urn:li:person:{result['id']}")
EOF
```

## Code Examples

### JavaScript - Create Text Post

```javascript
const personId = 'YOUR_PERSON_ID';

const response = await fetch(
  'https://gateway.maton.ai/linkedin/rest/posts',
  {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.MATON_API_KEY}`,
      'Content-Type': 'application/json',
      'LinkedIn-Version': '202506'
    },
    body: JSON.stringify({
      author: `urn:li:person:${personId}`,
      lifecycleState: 'PUBLISHED',
      visibility: 'PUBLIC',
      commentary: 'Hello from the API!',
      distribution: {
        feedDistribution: 'MAIN_FEED'
      }
    })
  }
);
```

### Python - Create Text Post

```python
import os
import requests

person_id = 'YOUR_PERSON_ID'

response = requests.post(
    'https://gateway.maton.ai/linkedin/rest/posts',
    headers={
        'Authorization': f'Bearer {os.environ["MATON_API_KEY"]}',
        'Content-Type': 'application/json',
        'LinkedIn-Version': '202506'
    },
    json={
        'author': f'urn:li:person:{person_id}',
        'lifecycleState': 'PUBLISHED',
        'visibility': 'PUBLIC',
        'commentary': 'Hello from the API!',
        'distribution': {
            'feedDistribution': 'MAIN_FEED'
        }
    }
)
```

## Rate Limits

| Throttle Type | Daily Limit (UTC) |
|---------------|-------------------|
| Member | 150 requests/day |
| Application | 100,000 requests/day |

## Notes

- Person IDs are unique per application and not transferable across apps
- The `author` field must use URN format: `urn:li:person:{personId}`
- All posts require `lifecycleState: "PUBLISHED"`
- Image/video uploads are a 3-step process: initialize upload, upload binary, create post
- Include `LinkedIn-Version: 202506` header for all REST API calls
- Profile picture URLs may expire; re-fetch if needed
- IMPORTANT: When using curl commands, use `curl -g` when URLs contain brackets to disable glob parsing
- IMPORTANT: When piping curl output to `jq` or other commands, environment variables like `$MATON_API_KEY` may not expand correctly in some shell environments

## Error Handling

| Status | Meaning |
|--------|---------|
| 400 | Missing LinkedIn connection or invalid request |
| 401 | Invalid or missing Maton API key |
| 403 | Insufficient permissions (check OAuth scopes) |
| 404 | Resource not found |
| 422 | Invalid request body or URN format |
| 429 | Rate limited |
| 4xx/5xx | Passthrough error from LinkedIn API |

### Error Response Format

```json
{
  "status": 403,
  "serviceErrorCode": 100,
  "code": "ACCESS_DENIED",
  "message": "Not enough permissions to access resource"
}
```

### Troubleshooting: API Key Issues

1. Check that the `MATON_API_KEY` environment variable is set:

```bash
echo $MATON_API_KEY
```

2. Verify the API key is valid by listing connections:

```bash
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://ctrl.maton.ai/connections')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
```

### Troubleshooting: Invalid App Name

1. Ensure your URL path starts with `linkedin`. For example:

- Correct: `https://gateway.maton.ai/linkedin/rest/me`
- Incorrect: `https://gateway.maton.ai/rest/me`

## OAuth Scopes

| Scope | Description |
|-------|-------------|
| `openid` | OpenID Connect authentication |
| `profile` | Read basic profile |
| `email` | Read email address |
| `w_member_social` | Create, modify, and delete posts |

## Resources

- [LinkedIn API Overview](https://learn.microsoft.com/en-us/linkedin/)
- [Share on LinkedIn Guide](https://learn.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/share-on-linkedin)
- [Profile API](https://learn.microsoft.com/en-us/linkedin/shared/integrations/people/profile-api)
- [Sign In with LinkedIn](https://learn.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/sign-in-with-linkedin-v2)
- [Authentication Guide](https://learn.microsoft.com/en-us/linkedin/shared/authentication/authentication)
- [Marketing API](https://learn.microsoft.com/en-us/linkedin/marketing/)
- [Ad Accounts](https://learn.microsoft.com/en-us/linkedin/marketing/integrations/ads/account-structure/create-and-manage-accounts)
- [Campaign Management](https://learn.microsoft.com/en-us/linkedin/marketing/integrations/ads/account-structure/create-and-manage-campaigns)
- [Ad Library API](https://www.linkedin.com/ad-library/api/)
- [Maton Community](https://discord.com/invite/dBfFAcefs2)
- [Maton Support](mailto:support@maton.ai)