Skip to content

LinkedIn API

Schedule and automate LinkedIn posts with Zernio API - Personal profiles, company pages, images, videos, documents, and multi-organization posting

Quick Reference

PropertyValue
Character limit3,000
Images per post20
Videos per post1
Documents per post1 (PDF, PPT, PPTX, DOC, DOCX)
Image formatsJPEG, PNG, GIF
Image max size8 MB
Video formatsMP4, MOV, AVI
Video max size5 GB
Video max duration10 min (personal), 30 min (company page)
Post typesText, Image, Multi-image, Video, Document
SchedulingYes
Inbox (Comments)Yes (company pages only)
Inbox (DMs)No (LinkedIn blocks third-party DM access)
AnalyticsYes

Before You Start

LinkedIn actively suppresses posts containing external links. Your post's organic reach can drop 40-50% if you include a URL in the caption. Best practice: put your link in the first comment using the firstComment field. Also: LinkedIn rejects duplicate content with a 422 error. You cannot post the same text twice, even across different time periods.

Additional details:

  • Works with personal profiles AND company pages
  • Company pages: full analytics, 30-min video, comments API
  • Personal profiles: limited analytics, 10-min video
  • Cannot mix media types (images + videos or images + documents in the same post)
  • First ~210 characters of your post are visible before the "see more" fold

Quick Start

Post to LinkedIn in under 60 seconds:

typescript
const { post } = await zernio.posts.createPost({
  content: 'Excited to share our latest update!\n\nWe have been working hard on this feature...',
  platforms: [
    { platform: 'linkedin', accountId: 'YOUR_ACCOUNT_ID' }
  ],
  publishNow: true
});
console.log('Posted to LinkedIn!', post._id);
python
result = client.posts.create_post(
    content="Excited to share our latest update!\n\nWe have been working hard on this feature...",
    platforms=[
        {"platform": "linkedin", "accountId": "YOUR_ACCOUNT_ID"}
    ],
    publish_now=True
)
post = result.post
print(f"Posted to LinkedIn! {post['_id']}")
bash
curl -X POST https://zernio.com/api/v1/posts \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Excited to share our latest update!\n\nWe have been working hard on this feature...",
    "platforms": [
      {"platform": "linkedin", "accountId": "YOUR_ACCOUNT_ID"}
    ],
    "publishNow": true
  }'

Content Types

Text-Only Post

Text posts get the highest organic reach on LinkedIn. The first ~210 characters appear before the "see more" fold, so lead with your hook. Up to 3,000 characters total.

typescript
const { post } = await zernio.posts.createPost({
  content: 'I spent 3 years building the wrong product.\n\nHere is what I learned about validating ideas before writing code...',
  platforms: [
    { platform: 'linkedin', accountId: 'YOUR_ACCOUNT_ID' }
  ],
  publishNow: true
});
console.log('Posted to LinkedIn!', post._id);
python
result = client.posts.create_post(
    content="I spent 3 years building the wrong product.\n\nHere is what I learned about validating ideas before writing code...",
    platforms=[
        {"platform": "linkedin", "accountId": "YOUR_ACCOUNT_ID"}
    ],
    publish_now=True
)
post = result.post
print(f"Posted to LinkedIn! {post['_id']}")
bash
curl -X POST https://zernio.com/api/v1/posts \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "I spent 3 years building the wrong product.\n\nHere is what I learned about validating ideas before writing code...",
    "platforms": [
      {"platform": "linkedin", "accountId": "YOUR_ACCOUNT_ID"}
    ],
    "publishNow": true
  }'

Single Image Post

Attach one image to a post. Recommended size is 1200 x 627 px (landscape).

typescript
const { post } = await zernio.posts.createPost({
  content: 'Our new office setup is looking great!',
  mediaItems: [
    { type: 'image', url: 'https://cdn.example.com/office.jpg' }
  ],
  platforms: [
    { platform: 'linkedin', accountId: 'YOUR_ACCOUNT_ID' }
  ],
  publishNow: true
});
console.log('Posted to LinkedIn!', post._id);
python
result = client.posts.create_post(
    content="Our new office setup is looking great!",
    media_items=[
        {"type": "image", "url": "https://cdn.example.com/office.jpg"}
    ],
    platforms=[
        {"platform": "linkedin", "accountId": "YOUR_ACCOUNT_ID"}
    ],
    publish_now=True
)
post = result.post
print(f"Posted to LinkedIn! {post['_id']}")
bash
curl -X POST https://zernio.com/api/v1/posts \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Our new office setup is looking great!",
    "mediaItems": [
      {"type": "image", "url": "https://cdn.example.com/office.jpg"}
    ],
    "platforms": [
      {"platform": "linkedin", "accountId": "YOUR_ACCOUNT_ID"}
    ],
    "publishNow": true
  }'

Multi-Image Post

LinkedIn supports up to 20 images in a single post. Cannot include videos or documents alongside images.

typescript
const { post } = await zernio.posts.createPost({
  content: 'Highlights from our team retreat!',
  mediaItems: [
    { type: 'image', url: 'https://cdn.example.com/photo1.jpg' },
    { type: 'image', url: 'https://cdn.example.com/photo2.jpg' },
    { type: 'image', url: 'https://cdn.example.com/photo3.jpg' }
  ],
  platforms: [
    { platform: 'linkedin', accountId: 'YOUR_ACCOUNT_ID' }
  ],
  publishNow: true
});
console.log('Posted to LinkedIn!', post._id);
python
result = client.posts.create_post(
    content="Highlights from our team retreat!",
    media_items=[
        {"type": "image", "url": "https://cdn.example.com/photo1.jpg"},
        {"type": "image", "url": "https://cdn.example.com/photo2.jpg"},
        {"type": "image", "url": "https://cdn.example.com/photo3.jpg"}
    ],
    platforms=[
        {"platform": "linkedin", "accountId": "YOUR_ACCOUNT_ID"}
    ],
    publish_now=True
)
post = result.post
print(f"Posted to LinkedIn! {post['_id']}")
bash
curl -X POST https://zernio.com/api/v1/posts \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Highlights from our team retreat!",
    "mediaItems": [
      {"type": "image", "url": "https://cdn.example.com/photo1.jpg"},
      {"type": "image", "url": "https://cdn.example.com/photo2.jpg"},
      {"type": "image", "url": "https://cdn.example.com/photo3.jpg"}
    ],
    "platforms": [
      {"platform": "linkedin", "accountId": "YOUR_ACCOUNT_ID"}
    ],
    "publishNow": true
  }'

Video Post

One video per post. Personal profiles support up to 10 minutes, company pages up to 30 minutes. MP4, MOV, and AVI formats.

typescript
const { post } = await zernio.posts.createPost({
  content: 'Watch our latest product demo',
  mediaItems: [
    { type: 'video', url: 'https://cdn.example.com/demo.mp4' }
  ],
  platforms: [
    { platform: 'linkedin', accountId: 'YOUR_ACCOUNT_ID' }
  ],
  publishNow: true
});
console.log('Posted to LinkedIn!', post._id);
python
result = client.posts.create_post(
    content="Watch our latest product demo",
    media_items=[
        {"type": "video", "url": "https://cdn.example.com/demo.mp4"}
    ],
    platforms=[
        {"platform": "linkedin", "accountId": "YOUR_ACCOUNT_ID"}
    ],
    publish_now=True
)
post = result.post
print(f"Posted to LinkedIn! {post['_id']}")
bash
curl -X POST https://zernio.com/api/v1/posts \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Watch our latest product demo",
    "mediaItems": [
      {"type": "video", "url": "https://cdn.example.com/demo.mp4"}
    ],
    "platforms": [
      {"platform": "linkedin", "accountId": "YOUR_ACCOUNT_ID"}
    ],
    "publishNow": true
  }'

Document/Carousel Post

LinkedIn uniquely supports document uploads (PDF, PPT, PPTX, DOC, DOCX) that display as swipeable carousels. Max 100 MB, up to 300 pages. Cannot include images or videos alongside a document.

typescript
const { post } = await zernio.posts.createPost({
  content: 'Download our 2024 Industry Report',
  mediaItems: [
    { type: 'document', url: 'https://cdn.example.com/report.pdf' }
  ],
  platforms: [
    { platform: 'linkedin', accountId: 'YOUR_ACCOUNT_ID' }
  ],
  publishNow: true
});
console.log('Posted to LinkedIn!', post._id);
python
result = client.posts.create_post(
    content="Download our 2024 Industry Report",
    media_items=[
        {"type": "document", "url": "https://cdn.example.com/report.pdf"}
    ],
    platforms=[
        {"platform": "linkedin", "accountId": "YOUR_ACCOUNT_ID"}
    ],
    publish_now=True
)
post = result.post
print(f"Posted to LinkedIn! {post['_id']}")
bash
curl -X POST https://zernio.com/api/v1/posts \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Download our 2024 Industry Report",
    "mediaItems": [
      {"type": "document", "url": "https://cdn.example.com/report.pdf"}
    ],
    "platforms": [
      {"platform": "linkedin", "accountId": "YOUR_ACCOUNT_ID"}
    ],
    "publishNow": true
  }'

Document tips:

  • First page is the cover/preview -- design it to grab attention
  • Design for mobile viewing (large fonts, minimal text per slide)
  • Ideal length for engagement: 10-15 pages
  • Password-protected PDFs will not work

Media Requirements

Images

PropertyRequirement
Max images20 per post
FormatsJPEG, PNG, GIF
Max file size8 MB per image
Recommended1200 x 627 px
Min dimensions552 x 276 px
Max dimensions8192 x 8192 px

Aspect Ratios

TypeRatioDimensionsUse Case
Landscape1.91:11200 x 627 pxLink shares, standard
Square1:11080 x 1080 pxEngagement
Portrait1:1.251080 x 1350 pxMobile feed

Videos

PropertyRequirement
Max videos1 per post
FormatsMP4, MOV, AVI
Max file size5 GB
Max duration10 min (personal), 30 min (company page)
Min duration3 seconds
Resolution256 x 144 px to 4096 x 2304 px
Aspect ratio1:2.4 to 2.4:1
Frame rate10-60 fps
PropertyRecommended
Resolution1920 x 1080 px (1080p)
Aspect ratio16:9 (landscape) or 1:1 (square)
Frame rate30 fps
CodecH.264
AudioAAC, 192 kbps
Bitrate10-30 Mbps

Documents

PropertyRequirement
Max documents1 per post
FormatsPDF, PPT, PPTX, DOC, DOCX
Max file size100 MB
Max pages300 pages

Platform-Specific Fields

All fields go inside platformSpecificData on the LinkedIn platform entry.

FieldTypeDescription
documentTitlestringTitle displayed on LinkedIn document (PDF/carousel) posts. Required by LinkedIn for document posts. If omitted, falls back to the media item title, then the filename.
organizationUrnstringPost to a specific LinkedIn company page. Format: urn:li:organization:123456. Get available orgs via GET /v1/accounts/{accountId}/linkedin-organizations. If omitted, posts to default org or personal profile.
firstCommentstringAuto-posted as first comment after publish. Best practice: put external links here since LinkedIn suppresses link posts by 40-50%.
disableLinkPreviewbooleanSet to true to suppress the automatic URL preview card. Default: false.
geoRestrictionobjectRestrict post visibility to specific countries. Organization pages only, requires 300+ targeted followers. See Geo-Restriction below.

Geo-Restriction

Restrict who can see your LinkedIn post by country. This is a hard visibility restriction: only followers in the specified countries see the post. Organization pages only (not personal profiles). LinkedIn requires the targeted audience to exceed 300 followers or the post will be rejected.

json
{
  "platforms": [{
    "platform": "linkedin",
    "accountId": "YOUR_ORG_ACCOUNT_ID",
    "platformSpecificData": {
      "geoRestriction": {
        "countries": ["US", "ES"]
      }
    }
  }]
}

geoRestriction.countries accepts up to 25 uppercase ISO 3166-1 alpha-2 country codes. Zernio automatically resolves country codes to LinkedIn geo URNs. Currently supports 44 countries; unsupported country codes are silently skipped.

LinkedIn Document Titles

LinkedIn requires a title for document (PDF/carousel) posts. Set platformSpecificData.documentTitle to control the title shown on LinkedIn.

> Note: If documentTitle is omitted, Zernio falls back to the media item title, then the filename.

bash
curl -X POST https://zernio.com/api/v1/posts \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Download our 2024 Industry Report",
    "mediaItems": [
      {"type": "document", "url": "https://cdn.example.com/report.pdf"}
    ],
    "platforms": [{
      "platform": "linkedin",
      "accountId": "YOUR_ACCOUNT_ID",
      "platformSpecificData": {
        "documentTitle": "2024 Industry Report"
      }
    }],
    "publishNow": true
  }'
typescript
const { post } = await zernio.posts.createPost({
  content: 'Download our 2024 Industry Report',
  mediaItems: [
    { type: 'document', url: 'https://cdn.example.com/report.pdf' }
  ],
  platforms: [{
    platform: 'linkedin',
    accountId: 'YOUR_ACCOUNT_ID',
    platformSpecificData: {
      documentTitle: '2024 Industry Report'
    }
  }],
  publishNow: true
});
console.log('Posted to LinkedIn!', post._id);
python
result = client.posts.create_post(
    content="Download our 2024 Industry Report",
    media_items=[
        {"type": "document", "url": "https://cdn.example.com/report.pdf"}
    ],
    platforms=[{
        "platform": "linkedin",
        "accountId": "YOUR_ACCOUNT_ID",
        "platformSpecificData": {
            "documentTitle": "2024 Industry Report"
        }
    }],
    publish_now=True
)
post = result.post
print(f"Posted to LinkedIn! {post['_id']}")

Multi-Organization Posting

If your connected LinkedIn account manages multiple organizations (company pages), you can post to different organizations from the same account connection.

List available organizations:

typescript
const organizations = await zernio.connect.getLinkedInOrganizations('YOUR_ACCOUNT_ID');
console.log('Available organizations:', organizations);
python
organizations = client.connect.list_linked_in_organizations("YOUR_ACCOUNT_ID")
print("Available organizations:", organizations)
bash
curl -X GET https://zernio.com/api/v1/accounts/YOUR_ACCOUNT_ID/linkedin-organizations \
  -H "Authorization: Bearer YOUR_API_KEY"

Post to multiple organizations using the same accountId with different organizationUrn values:

typescript
const { post } = await zernio.posts.createPost({
  content: 'Exciting updates from our organization!',
  platforms: [
    {
      platform: 'linkedin',
      accountId: 'YOUR_ACCOUNT_ID',
      platformSpecificData: { organizationUrn: 'urn:li:organization:111111111' }
    },
    {
      platform: 'linkedin',
      accountId: 'YOUR_ACCOUNT_ID',
      platformSpecificData: { organizationUrn: 'urn:li:organization:222222222' }
    }
  ],
  publishNow: true
});
console.log('Posted to LinkedIn!', post._id);
python
result = client.posts.create_post(
    content="Exciting updates from our organization!",
    platforms=[
        {
            "platform": "linkedin",
            "accountId": "YOUR_ACCOUNT_ID",
            "platformSpecificData": {"organizationUrn": "urn:li:organization:111111111"}
        },
        {
            "platform": "linkedin",
            "accountId": "YOUR_ACCOUNT_ID",
            "platformSpecificData": {"organizationUrn": "urn:li:organization:222222222"}
        }
    ],
    publish_now=True
)
post = result.post
print(f"Posted to LinkedIn! {post['_id']}")
bash
curl -X POST https://zernio.com/api/v1/posts \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Exciting updates from our organization!",
    "platforms": [
      {
        "platform": "linkedin",
        "accountId": "YOUR_ACCOUNT_ID",
        "platformSpecificData": {
          "organizationUrn": "urn:li:organization:111111111"
        }
      },
      {
        "platform": "linkedin",
        "accountId": "YOUR_ACCOUNT_ID",
        "platformSpecificData": {
          "organizationUrn": "urn:li:organization:222222222"
        }
      }
    ],
    "publishNow": true
  }'

The organizationUrn format is urn:li:organization: followed by the organization ID.

First Comment

Put external links in the first comment to avoid LinkedIn's link suppression algorithm:

typescript
const { post } = await zernio.posts.createPost({
  content: 'We just published our guide to API design patterns.\n\nLink in the first comment.',
  platforms: [{
    platform: 'linkedin',
    accountId: 'YOUR_ACCOUNT_ID',
    platformSpecificData: {
      firstComment: 'Read the full guide here: https://example.com/api-guide'
    }
  }],
  publishNow: true
});
console.log('Posted to LinkedIn!', post._id);
python
result = client.posts.create_post(
    content="We just published our guide to API design patterns.\n\nLink in the first comment.",
    platforms=[{
        "platform": "linkedin",
        "accountId": "YOUR_ACCOUNT_ID",
        "platformSpecificData": {
            "firstComment": "Read the full guide here: https://example.com/api-guide"
        }
    }],
    publish_now=True
)
post = result.post
print(f"Posted to LinkedIn! {post['_id']}")
bash
curl -X POST https://zernio.com/api/v1/posts \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "We just published our guide to API design patterns.\n\nLink in the first comment.",
    "platforms": [{
      "platform": "linkedin",
      "accountId": "YOUR_ACCOUNT_ID",
      "platformSpecificData": {
        "firstComment": "Read the full guide here: https://example.com/api-guide"
      }
    }],
    "publishNow": true
  }'

When posting text with URLs and no media, LinkedIn auto-generates a link preview card. To suppress it:

typescript
const { post } = await zernio.posts.createPost({
  content: 'Check out our latest blog post! https://example.com/blog/new-post',
  platforms: [{
    platform: 'linkedin',
    accountId: 'YOUR_ACCOUNT_ID',
    platformSpecificData: {
      disableLinkPreview: true
    }
  }],
  publishNow: true
});
console.log('Posted to LinkedIn!', post._id);
python
result = client.posts.create_post(
    content="Check out our latest blog post! https://example.com/blog/new-post",
    platforms=[{
        "platform": "linkedin",
        "accountId": "YOUR_ACCOUNT_ID",
        "platformSpecificData": {
            "disableLinkPreview": True
        }
    }],
    publish_now=True
)
post = result.post
print(f"Posted to LinkedIn! {post['_id']}")
bash
curl -X POST https://zernio.com/api/v1/posts \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Check out our latest blog post! https://example.com/blog/new-post",
    "platforms": [{
      "platform": "linkedin",
      "accountId": "YOUR_ACCOUNT_ID",
      "platformSpecificData": {
        "disableLinkPreview": true
      }
    }],
    "publishNow": true
  }'

Media URL Requirements

These do not work as media URLs:

  • Google Drive -- returns an HTML download page, not the file
  • Dropbox -- returns an HTML preview page
  • OneDrive / SharePoint -- returns HTML
  • iCloud -- returns HTML

Test your URL in an incognito browser window. If you see a webpage instead of the raw image or video, it will not work.

Media URLs must be:

  • Publicly accessible (no authentication required)
  • Returning actual media bytes with the correct Content-Type header
  • Not behind redirects that resolve to HTML pages
  • Hosted on a fast, reliable CDN

Supabase URLs: Zernio auto-proxies Supabase storage URLs, so they work without additional configuration.

Analytics

> Included — Analytics is bundled with every paid account on the Usage plan.

Available metrics via the Analytics API:

MetricAvailable
Impressions
Reach
Likes
Comments
Shares
Saves✅ (personal accounts only)
Sends✅ (personal accounts only, LinkedIn messaging)
Clicks✅ (organization accounts only)
Views✅ (video posts only)

LinkedIn also provides dedicated analytics endpoints for deeper insights:

  • Personal Aggregate Analytics -- Totals or daily time series across a personal account's posts. Covers impressions, reach, reactions, comments, shares, saves, sends.
  • Organization Aggregate Analytics -- Impressions, clicks, reactions, engagement rate, follower gains (organic vs paid), and page-view metrics for organization pages. Requires the authenticated member to be an ADMINISTRATOR on the org and all three org scopes (r_organization_social + r_organization_followers + r_organization_admin).
  • Post Analytics -- Per-post metrics by URN. Works for both personal and organization accounts (saves and sends are personal-only, org returns 0).
  • Post Reactions -- Individual reactions with reactor profiles (organization accounts only).
typescript
const analytics = await zernio.analytics.getAnalytics({
  platform: 'linkedin',
  fromDate: '2024-01-01',
  toDate: '2024-01-31'
});
console.log(analytics.posts);
python
analytics = client.analytics.get_analytics(
    platform="linkedin",
    from_date="2024-01-01",
    to_date="2024-01-31"
)
print(analytics["posts"])
bash
curl "https://zernio.com/api/v1/analytics?platform=linkedin&fromDate=2024-01-01&toDate=2024-01-31" \
  -H "Authorization: Bearer YOUR_API_KEY"

What You Can't Do

These features are not available through LinkedIn's API:

  • Publish long-form articles (linkedin.com/article/new/)
  • Create polls
  • Create events
  • Send InMail or DMs
  • Create newsletters
  • Add reactions to other posts
  • Create hashtag-following or tag connections
  • Mix media types (images + videos or images + documents in the same post)

Common Errors

LinkedIn has a 9.5% failure rate across Zernio's platform (8,082 failures out of 85,512 attempts). Here are the most frequent errors and how to fix them:

ErrorWhat it meansHow to fix
"Content is a duplicate of urn:li:share:XXXX" (422)Identical or very similar content was already postedModify the text meaningfully. LinkedIn's duplicate detection is strict -- even minor rephrasing may not be enough.
"Publishing failed during preflight checks"Rate limiting or validation caught an issue before publishingSpace posts further apart. Check account health in the dashboard.
"Publishing failed due to max retries reached"All 3 retry attempts failedTemporary issue. Retry manually or check LinkedIn's status page.
Token expiredOAuth access token has expired or been revokedReconnect the LinkedIn account. Subscribe to the account.disconnected webhook to catch this proactively.
"Cannot mix media types"Post contains images + videos or images + documentsUse only one media type per post.
Video processing failedCodec, duration, or aspect ratio is out of specEnsure codec is H.264, duration is within limits (10 min personal / 30 min company), and aspect ratio is between 1:2.4 and 2.4:1.
Link preview showing wrong imageOpen Graph meta tags on the URL are incorrect or missingUpdate the og:image tag on your website. LinkedIn caches previews -- use the LinkedIn Post Inspector to refresh.

Inbox

> Included — Inbox (DMs, comments, reviews) is bundled with every paid account on the Usage plan.

LinkedIn supports comments only (no DMs via API).

Comments

FeatureSupported
List comments on posts
Reply to comments
Delete comments
Like comments❌ (API restricted)

Limitations

  • No DMs - LinkedIn's messaging API is not available for third-party apps
  • Organization accounts only - Comments require an organization (company page) account type
  • No comment likes - Liking/reacting to comments via API is restricted. However, you can read post reactions via GET /v1/accounts/{accountId}/linkedin-post-reactions (organization accounts only).

See Comments API Reference for endpoint details.

  • Connect LinkedIn Account - OAuth flow
  • Create Post - Post creation and scheduling
  • Upload Media - Image and video uploads
  • LinkedIn Organizations - List company pages
  • LinkedIn Mentions - Track brand mentions
  • LinkedIn Aggregate Analytics - Organization-level analytics
  • LinkedIn Post Analytics - Post-level performance metrics
  • LinkedIn Post Reactions - Who reacted to a post (organization accounts)
  • Comments - Inbox API