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.

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
| Product | What it unlocks | Review? |
|---|---|---|
| Sign In with LinkedIn using OpenID Connect | Authentication, basic profile | No |
| Share on LinkedIn | Post as the authenticated member | No |
| Advertising API | Ad accounts, campaigns, sponsored content | Yes |
| Community Management API | Company page posting, page analytics, comments, follower data | Yes |
| Marketing Developer Platform | Broader marketing surface, includes organic org access | Yes |
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:
| Scope | What it allows |
|---|---|
w_member_social | Post, comment and react as the authenticated member |
w_organization_social | Post, comment and react as a company page |
r_organization_social | Read a company page's posts, comments and reactions |
rw_organization_admin | Read and manage the pages the member administers. This is how you list the pages a user can post to |
r_organization_followers | Follower counts and demographics |
r_member_profileAnalytics | Analytics on the member's own profile |
r_member_postAnalytics | Analytics on the member's own posts |
w_member_social_feed, w_organization_social_feed | Feed-level write operations |
r_organization_social_feed | Feed-level read operations |
r_basicprofile | Name, headline, profile picture |
r_1st_connections_size | Connection 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.

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 API | Shown in the LinkedIn UI as | Can publish? |
|---|---|---|
ADMINISTRATOR | Super admin | Yes |
CONTENT_ADMINISTRATOR | Content admin | Yes |
DIRECT_SPONSORED_CONTENT_POSTER | Sponsored content poster | Yes |
ANALYST | Analyst | No |
CURATOR | Curator | No |
RECRUITING_POSTER | Recruiting poster | No, 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

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 contains | Actual cause | Fix |
|---|---|---|
insufficient permissions / not enough permissions | The token lacks a required scope | Re-authorise requesting the missing scope. Check the product is approved first, or the scope will silently not be granted |
accessing ... resource is forbidden | The member has no publishing role on that organization | Not fixable by reconnecting. A page Super admin has to grant Content admin or Super admin |
member is restricted | LinkedIn has restricted the member account | Only the user can resolve this with LinkedIn |
token ... revoked | The user revoked the app, or changed their password | Re-authorisation |
| Empty or generic 403 on a versioned endpoint | Missing or retired LinkedIn-Version header | Set 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.