Install any skill in seconds. Free to start, no credit card required.
Get Started Free →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.
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-13 | ✗→✓ | ▲ Improved | — | — |
| case-06 | ✗→✓ | ▲ Improved | — | — |
| case-17 | ✗→✓ | ▲ Improved | — | — |
| case-14 | ✗→✓ | ▲ Improved | — | — |
| case-05 | ✗→✓ | ▲ Improved | — | — |
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.
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
https://gateway.maton.ai/linkedin/rest/{resource}The gateway proxies requests to api.linkedin.com and automatically injects your OAuth token.
All requests require the Maton API key in the Authorization header:
Authorization: Bearer $MATON_API_KEYEnvironment Variable: Set your API key as MATON_API_KEY:
bashexport MATON_API_KEY="YOUR_API_KEY"
LinkedIn REST API requires the version header:
LinkedIn-Version: 202506Manage your LinkedIn OAuth connections at https://ctrl.maton.ai.
bashpython <<'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
bashpython <<'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
bashpython <<'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.
bashpython <<'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
If you have multiple LinkedIn connections, specify which one to use with the Maton-Connection header:
bashpython <<'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.
bashGET /linkedin/rest/me LinkedIn-Version: 202506
Example:
bashpython <<'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" } }
bashPOST /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.
bashPOST /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" } } }
First, initialize the image upload, then upload the image, then create the post.
Step 1: Initialize Image Upload
bashPOST /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
bashPUT {uploadUrl from step 1} Content-Type: image/png {binary image data}
Step 3: Create Image Post
bashPOST /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" } } }
| Value | Description | |-------|-------------| | PUBLIC | Viewable by anyone on LinkedIn | | CONNECTIONS | Viewable by 1st-degree connections only |
| Value | Description | |-------|-------------| | NONE | Text-only post | | ARTICLE | URL/article share | | IMAGE | Image post | | VIDEO | Video post |
The Ad Library API provides access to public advertising data on LinkedIn. These endpoints use the REST API with version headers.
LinkedIn-Version: 202506bashGET /linkedin/rest/adLibrary?q=criteria&keyword={keyword}
Query parameters:
keyword (string): Search ad content (multiple keywords use AND logic)advertiser (string): Search by advertiser namecountries (array): Filter by ISO 3166-1 alpha-2 country codesdateRange (object): Filter by served datesstart (integer): Pagination offsetcount (integer): Results per page (max 25)Example - Search ads by keyword:
bashGET /linkedin/rest/adLibrary?q=criteria&keyword=linkedin
Example - Search ads by advertiser:
bashGET /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 } ] }
bashGET /linkedin/rest/jobLibrary?q=criteria&keyword={keyword}
Note: Job Library requires version 202506.
Query parameters:
keyword (string): Search job contentorganization (string): Filter by company namecountries (array): Filter by country codesdateRange (object): Filter by posting datesstart (integer): Pagination offsetcount (integer): Results per page (max 24)Example:
bashGET /linkedin/rest/jobLibrary?q=criteria&keyword=software&organization=google
Response includes:
jobPostingUrl: Link to job listingjobDetails: Title, location, description, salary, benefitsstatistics: Impression dataThe Marketing API provides access to LinkedIn's advertising platform. These endpoints use the versioned REST API.
LinkedIn-Version: 202506bashGET /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" } ] }
bashGET /linkedin/rest/adAccounts/{adAccountId}
bashPOST /linkedin/rest/adAccounts Content-Type: application/json { "name": "New Ad Account", "currency": "USD", "reference": "urn:li:organization:{orgId}", "type": "BUSINESS" }
bashPOST /linkedin/rest/adAccounts/{adAccountId} Content-Type: application/json X-RestLi-Method: PARTIAL_UPDATE { "patch": { "$set": { "name": "Updated Account Name" } } }
Campaign groups are nested under ad accounts:
bashGET /linkedin/rest/adAccounts/{adAccountId}/adCampaignGroups
bashPOST /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" } }
bashGET /linkedin/rest/adAccounts/{adAccountId}/adCampaignGroups/{campaignGroupId}
bashPOST /linkedin/rest/adAccounts/{adAccountId}/adCampaignGroups/{campaignGroupId} Content-Type: application/json X-RestLi-Method: PARTIAL_UPDATE { "patch": { "$set": { "status": "ACTIVE" } } }
bashDELETE /linkedin/rest/adAccounts/{adAccountId}/adCampaignGroups/{campaignGroupId}
Campaigns are also nested under ad accounts:
bashGET /linkedin/rest/adAccounts/{adAccountId}/adCampaigns
bashPOST /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" } }
bashGET /linkedin/rest/adAccounts/{adAccountId}/adCampaigns/{campaignId}
bashPOST /linkedin/rest/adAccounts/{adAccountId}/adCampaigns/{campaignId} Content-Type: application/json X-RestLi-Method: PARTIAL_UPDATE { "patch": { "$set": { "status": "ACTIVE" } } }
bashDELETE /linkedin/rest/adAccounts/{adAccountId}/adCampaigns/{campaignId}
| 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 |
| 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 |
Get organizations the authenticated user has access to:
bashGET /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" } ] }
bashGET /linkedin/rest/organizations/{organizationId} LinkedIn-Version: 202506
bashGET /linkedin/rest/organizations?q=vanityName&vanityName={vanityName}
Example:
bashGET /linkedin/rest/organizations?q=vanityName&vanityName=microsoft
Response:
json{ "elements": [ { "vanityName": "microsoft", "localizedName": "Microsoft", "website": { "localized": {"en_US": "https://news.microsoft.com/"} } } ] }
bashGET /linkedin/rest/organizationalEntityShareStatistics?q=organizationalEntity&organizationalEntity={orgUrn}
Example:
bashGET /linkedin/rest/organizationalEntityShareStatistics?q=organizationalEntity&organizationalEntity=urn:li:organization:12345
bashGET /linkedin/rest/posts?q=author&author={orgUrn}
Example:
bashGET /linkedin/rest/posts?q=author&author=urn:li:organization:12345
The REST API provides modern media upload endpoints. All require version header LinkedIn-Version: 202506.
bashPOST /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.
bashPOST /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/..."} ] } }
bashPOST /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" } }
bashGET /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 skillsindustries - Industry categoriestitles - Job titlesseniorities - Seniority levelsdegrees - Educational degreesschools - Educational institutionsemployers / employersPast - Current/past employerslocations / geoLocations - Geographic targetingcompanySize - Company size rangesgenders - Gender targetingageRanges - Age range targetingTo create posts, you need your LinkedIn person ID. Get it from the /rest/me endpoint:
bashpython <<'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
javascriptconst 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' } }) } );
pythonimport 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' } } )
| Throttle Type | Daily Limit (UTC) | |---------------|-------------------| | Member | 150 requests/day | | Application | 100,000 requests/day |
author field must use URN format: urn:li:person:{personId}lifecycleState: "PUBLISHED"LinkedIn-Version: 202506 header for all REST API callscurl -g when URLs contain brackets to disable glob parsingjq or other commands, environment variables like $MATON_API_KEY may not expand correctly in some shell environments| 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 |
json{ "status": 403, "serviceErrorCode": 100, "code": "ACCESS_DENIED", "message": "Not enough permissions to access resource" }
MATON_API_KEY environment variable is set:bashecho $MATON_API_KEY
bashpython <<'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
linkedin. For example:https://gateway.maton.ai/linkedin/rest/mehttps://gateway.maton.ai/rest/me| Scope | Description | |-------|-------------| | openid | OpenID Connect authentication | | profile | Read basic profile | | email | Read email address | | w_member_social | Create, modify, and delete posts |
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-03 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-24 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-23 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 24 cases were attempted, and 21 counted toward the lift figure. The other 3 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +63 percentage points is the difference between those two pass rates over the 21 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.