API GuidesAugust 4, 202610 min readingMarcel Czuryszkiewicz

LinkedIn API Permissions

LinkedIn access has three independent axes: the product your app was approved for, the scopes in the token, and the user role on the organization. All three have to line up, and a 403 tells you nothing about which failed. Maps all three, plus the roles beyond Super admin that can actually publish.

LinkedIn API permissions are three separate systems that people treat as one. Your app is approved for a product, the token carries scopes, and the user has a role on the organization. All three have to line up before a call succeeds, and when one does not, LinkedIn returns 403 without telling you which. This maps all three axes, lists the roles that can publish (there are more than one), and gives you a table that turns a 403 into an actual fix.

Close-up of a rain-beaded four-dial combination lock coiled in its steel cable, every dial set to a different digit
Independent dials. Same lock.

Three axes

Most guides describe scopes and stop. Scopes are one axis of three, and on their own they guarantee nothing.

Axis 1: the product. LinkedIn gates access at the app level through products you request in the developer portal. Sign In with LinkedIn is self-serve. Share on LinkedIn is self-serve. The Community Management API, which is what you need to publish to and read from company pages properly, requires a review. Without the product, the scopes it governs cannot even be requested; they will not appear in the consent screen.

Axis 2: the scope. What the user granted, carried in the token. Even with the product approved, a user who declined a scope, or an authorisation request that never asked for it, produces a token that cannot do the thing.

Axis 3: the role. For anything involving a company page, the authenticated member must hold a role on that organization. This axis is invisible from your app's configuration entirely. It lives in the customer's LinkedIn account, changes without notifying you, and is the most common cause of a 403 on a correctly configured integration.

The practical consequence: your app configuration can be perfect and the call still fails, because of something in the customer's account you cannot see. Any error message that says "reconnect your LinkedIn account" for an axis-3 problem sends the customer into a loop that cannot resolve.

Products

ProductWhat it unlocksReview?
Sign In with LinkedIn using OpenID ConnectAuthentication, basic profileNo
Share on LinkedInPost as the authenticated memberNo
Advertising APIAd accounts, campaigns, sponsored contentYes
Community Management APICompany page posting, page analytics, comments, follower dataYes
Marketing Developer PlatformBroader marketing surface, includes organic org accessYes

The reviewed products are where the real capability lives, and the review is a business review rather than a technical one. LinkedIn wants to know what the app does, who uses it, and that you have a company page and a working product. Approval times vary widely, and rejections are frequently about the description of the use case rather than anything in the code.

Two things worth planning around.

Request products before you need them. The review runs on LinkedIn's calendar, not yours, and it is fully independent of your development work. There is no reason to serialise it.

Building a demo on self-serve products first is a reasonable strategy. Sign In and Share on LinkedIn let you build and show member-level posting immediately, which gives the reviewer something concrete to look at when you apply for Community Management.

Scopes

The set we request in production, with what each one is for:

ScopeWhat it allows
w_member_socialPost, comment and react as the authenticated member
w_organization_socialPost, comment and react as a company page
r_organization_socialRead a company page's posts, comments and reactions
rw_organization_adminRead and manage the pages the member administers. This is how you list the pages a user can post to
r_organization_followersFollower counts and demographics
r_member_profileAnalyticsAnalytics on the member's own profile
r_member_postAnalyticsAnalytics on the member's own posts
w_member_social_feed, w_organization_social_feedFeed-level write operations
r_organization_social_feedFeed-level read operations
r_basicprofileName, headline, profile picture
r_1st_connections_sizeConnection count

Three notes from running these.

rw_organization_admin is the one that makes company page posting usable, because without it you cannot enumerate which pages the member administers. You end up asking the user to paste an organization URN, which is a bad experience and a source of support tickets.

Member posting and organization posting are entirely separate scopes. A product that offers both needs both, and the two paths have different URN formats in the request body.

Requesting a scope whose product is not approved does not error clearly. The scope is silently absent from the granted set. Read back what was actually granted after the callback rather than assuming the request was honoured.

A single brass padlock closed on the hasp of a plain blue metal door
One lock on the door, and no sign of which key is wrong.

The roles that can publish

This is the axis nobody documents properly, and it caused a real bug in our own product.

LinkedIn organization roles are not a simple hierarchy. The obvious implementation is to filter for ADMINISTRATOR when listing pages a user can post to. That is what "Super admin" maps to in the LinkedIn UI, and it seems like the right check.

It is wrong. It hides pages the user can legitimately post to.

Role in the APIShown in the LinkedIn UI asCan publish?
ADMINISTRATORSuper adminYes
CONTENT_ADMINISTRATORContent adminYes
DIRECT_SPONSORED_CONTENT_POSTERSponsored content posterYes
ANALYSTAnalystNo
CURATORCuratorNo
RECRUITING_POSTERRecruiting posterNo, not for organic posts

Content admin is a common role in any organization that separates page ownership from day-to-day posting. Agencies get it constantly. Filtering to ADMINISTRATOR alone means those users connect their account, see none of the pages they manage, and conclude your product is broken.

We filter on all three publishing roles. If you only take one thing from this article, take that list.

The role is also fetched at connection time and can change afterwards. A user demoted from Content admin to Analyst next month keeps a valid token with valid scopes and starts getting 403 on a page that worked yesterday. Nothing on your side changed.

The version header

A dated permit sticker on a car windscreen, its validity period printed in small type
Valid until it quietly is not.

Every versioned LinkedIn API call requires a header:

curl -X POST "https://api.linkedin.com/rest/posts" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -H "LinkedIn-Version: 202605" \
  -H "X-Restli-Protocol-Version: 2.0.0" \
  -H "Content-Type: application/json" \
  -d '{ "author": "urn:li:organization:12345", "commentary": "Hello.", "visibility": "PUBLIC", "distribution": { "feedDistribution": "MAIN_FEED" }, "lifecycleState": "PUBLISHED" }'

LinkedIn-Version is YYYYMM and it is mandatory on the /rest/ endpoints. Three properties matter.

Versions expire. LinkedIn supports a rolling window of roughly a year and retires older versions on a schedule. A header that has worked for eighteen months stops working, and the failure is not always a clean error.

Bumping the version can change response shapes. It is a real upgrade, not a formality. Test it rather than incrementing it during an incident.

The header is per request, so it can be rolled out gradually. Change it for one endpoint, verify, then move the rest. There is no all-or-nothing switch.

Put the version in one constant, and put a calendar reminder against it. This is the single most avoidable source of a surprise outage on LinkedIn.

bundle.social

Product, scope and role. Three axes, one integration, already ours.

Publish to both without going through the partner process yourself.

Reading a 403

The table this page exists for. A 403 on LinkedIn has at least three distinct causes and the status code alone does not separate them.

Response text containsActual causeFix
insufficient permissions / not enough permissionsThe token lacks a required scopeRe-authorise requesting the missing scope. Check the product is approved first, or the scope will silently not be granted
accessing ... resource is forbiddenThe member has no publishing role on that organizationNot fixable by reconnecting. A page Super admin has to grant Content admin or Super admin
member is restrictedLinkedIn has restricted the member accountOnly the user can resolve this with LinkedIn
token ... revokedThe user revoked the app, or changed their passwordRe-authorisation
Empty or generic 403 on a versioned endpointMissing or retired LinkedIn-Version headerSet or update the header

The second row is the expensive one. Your UI says "permission denied", the user clicks reconnect, grants everything, and gets the same error, because the problem is a role in their company page settings. The message has to name the page and say who can fix it, or you own that support ticket forever.

Related failures that are not 403: a duplicate post is rejected outright and is terminal, and LinkedIn's image and document processing can fail after upload with a message that looks transient but is not. Both are covered in social media API error handling.

Token lifetime, briefly

LinkedIn access tokens last around 60 days. Refresh tokens exist but depend on the approved product, which means you cannot assume you have one. Where you do not, the only recovery is the user authorising again, so the practical requirement is to notice expiry before it happens and prompt while the account still works.

The cross-platform view of token lifetimes and refresh behaviour is in OAuth token refresh for social media APIs.

The shorter path

Three independent access axes, a product review on LinkedIn's timetable, a role model where the obvious filter is wrong, a mandatory version header that expires, and a 403 that means at least five different things. None of it is visible from your app's configuration screen.

bundle.social's LinkedIn API is approved for the products, requests the right scopes, filters the role list correctly so users see every page they can actually post to, and tracks the version header. Errors come back naming the axis that failed, so your UI can say "ask a page admin to make you a Content admin" instead of "permission denied".

For the publishing side (text, images, video, documents and the UGC migration), see LinkedIn posting API.

Frequently asked questions

Why does the LinkedIn API return 403 when I have the right scope?

Because scopes are only one of three requirements. The app also needs the product approved, and for company page operations the authenticated member needs a publishing role on that organization. A missing role produces a 403 that looks identical to a missing scope.

Which LinkedIn roles can post to a company page?

Super admin (ADMINISTRATOR), Content admin (CONTENT_ADMINISTRATOR) and Sponsored content poster (DIRECT_SPONSORED_CONTENT_POSTER). Filtering only for Super admin hides pages the user can legitimately post to, which is a common bug.

What scopes do I need to post to a LinkedIn company page?

w_organization_social to publish, and rw_organization_admin to list the pages the member administers. Both sit under the Community Management API, which requires review.

What is the LinkedIn-Version header and what happens if it is out of date?

A mandatory YYYYMM header on /rest/ endpoints. LinkedIn retires older versions on a rolling schedule of roughly a year, and a retired version starts failing without a clear error. Keep it in one constant and review it on a schedule.

How long does LinkedIn API approval take?

The self-serve products are instant. Community Management API and Marketing Developer Platform require a review with no fixed timeline, and rejections usually concern how the use case is described rather than anything technical. Apply before you need it.

Marcel Czuryszkiewicz
Written by

Marcel Czuryszkiewicz

Co-Founder

Marcel is one of the builders behind bundle.social, creating a social media API with a strong focus on developer experience, reliability, and real support. Before bundle.social, he worked at Samsung R&D, Reply AI, and Docplanner, building internal tools that helped companies grow. He brought that same practical, product-focused mindset to bundle.social, so you can trust that your project is in good hands.