Skip to content

How to post to LinkedIn with an API, and schedule it

By PostPen Team ·

To post to LinkedIn with an API, call LinkedIn's Posts API (POST https://api.linkedin.com/rest/posts) with an OAuth access token that has the w_member_social scope. The Posts API has no scheduling field, and PUBLISHED is the only lifecycle state it accepts when you create a post, so scheduling means running your own queue and job runner. To skip OAuth and the queue, send the post to PostPen's API with one bearer key: call save_draft, then schedule_post, and PostPen publishes it on time.

Start free. The Free plan includes all eleven tools and 10 published posts a month.

This guide shows both ways to make a LinkedIn API post, with working request shapes. Pick the one that matches how much infrastructure you want to own.

Option 1: LinkedIn's Posts API directly

What you need

Before your first call, you need four things:

  1. A LinkedIn developer app, created in the LinkedIn Developer Portal.
  2. The Share on LinkedIn product added to that app, which grants the w_member_social permission, per LinkedIn's Share on LinkedIn docs.
  3. A 3-legged OAuth flow that sends the member to LinkedIn's consent screen and requests w_member_social.
  4. Somewhere safe to store the access token and track when it expires.

LinkedIn's Authorization Code Flow docs state that access tokens are currently issued with a 60-day lifespan. Programmatic refresh tokens are only available to a limited set of partners, so for most apps the member has to sign in again before the token expires.

The request

The Posts API needs two headers on every call: Linkedin-Version in YYYYMM format and X-Restli-Protocol-Version: 2.0.0. This text-only example is copied from the Posts API docs on Microsoft Learn, with the author changed from an organization to a person:

curl -X POST 'https://api.linkedin.com/rest/posts' \
-H 'Authorization: Bearer {INSERT_TOKEN}' \
-H 'X-Restli-Protocol-Version: 2.0.0' \
-H 'Linkedin-Version: {version number in the format YYYYMM}' \
-H 'Content-Type: application/json' \
--data '{
  "author": "urn:li:person:{id}",
  "commentary": "Sample text Post",
  "visibility": "PUBLIC",
  "distribution": {
    "feedDistribution": "MAIN_FEED",
    "targetEntities": [],
    "thirdPartyDistributionChannels": []
  },
  "lifecycleState": "PUBLISHED",
  "isReshareDisabledByAuthor": false
}'

A successful call returns 201. The post ID, such as urn:li:share:..., comes back in the x-restli-id response header, not in a JSON body.

Per the Post API schema, the person URN is built from the member's id as urn:li:person:{id}. Images, videos and documents each need a separate upload first, through the Images, Videos or Documents API, to get an asset URN.

Watch the version header. Microsoft Learn shows a notice that Marketing Version 202510 will be sunset on October 15, 2026, so pin a current version.

Posting as a company page

To post as a company page, set author to the organization URN and request w_organization_social. The Posts API docs restrict that permission to members with an ADMINISTRATOR, CONTENT_ADMIN or DIRECT_SPONSORED_CONTENT_POSTER role on the page.

That route has its own setup and access steps, covered in the Posts API docs.

Scheduling: what the Posts API doesn't do

The Posts API creates a post now. Its documentation describes no field for a future publish time.

The schema is explicit about one thing: for lifecycleState, "PUBLISHED is the only accepted field during creation." The other states (DRAFT, PUBLISH_REQUESTED, PUBLISH_FAILED) only appear in responses. So a scheduled post has to wait in your system until it's time to call the API. The schema also has an optional publishedAt field, which records when content was published; it isn't a scheduling field.

A self-built LinkedIn post scheduler needs at least:

  • Storage for queued posts, their target author and their publish time.
  • A job runner (cron, a queue worker or a cloud scheduler) that wakes up and sends due posts.
  • Token expiry handling, since a token can expire between the day you queue a post and the day it goes out, and most apps must send the member back through sign-in.
  • Retries and error handling for failed calls, plus a way to tell a person when a post didn't publish.
  • Media handling that uploads assets and keeps their URNs ready for publish time.

That is a reasonable project if LinkedIn posting is core to your product. If it isn't, option 2 removes most of it.

Option 2: the PostPen HTTP API

PostPen is LinkedIn scheduling and publishing for AI agents and bots. It works as a LinkedIn scheduler API: you send the post, PostPen holds the LinkedIn connection, queues the post and publishes it at the time you set.

The API exposes the same eleven tools as PostPen's MCP server. The spec is at https://postpen.ai/openapi.json ("PostPen Agent API", v1.1.0).

  • Base URL: https://pdb.postpen.ai/functions/v1/agent-api
  • Paths: one path per tool, such as /save_draft and /schedule_post
  • Method: every operation is POST with a JSON body
  • Auth: Authorization: Bearer pp_...

Get a key

  1. Sign in to PostPen and connect LinkedIn once. Company pages you admin come from the same login.
  2. Open https://app.postpen.ai/agents.
  3. Create an API key. It starts with pp_.
  4. Store it like any other secret.

You don't need a LinkedIn developer app. You never handle a LinkedIn token either: no tool accepts or returns one. Create an API key.

Find where you can post: list_destinations

list_destinations takes an empty body. Each destination it returns has an id, a network (linkedin), a type (personal or organization) and a name.

curl -X POST https://pdb.postpen.ai/functions/v1/agent-api/list_destinations \
  -H "Authorization: Bearer pp_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'

"personal" is always your own profile. Company pages use the id from this list, usually the organization URN.

Save a draft: save_draft

save_draft creates a draft from content, up to 3,000 characters. Longer text is refused. Set destination to an id from list_destinations, or leave it as "personal".

curl -X POST https://pdb.postpen.ai/functions/v1/agent-api/save_draft \
  -H "Authorization: Bearer pp_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"content": "Changelog 2.5 is out.", "destination": "personal", "first_comment": "Full notes: https://example.com/changelog"}'

The response includes the post id, which you pass to schedule_post. The optional first_comment (up to 1,250 characters) is posted right after the post publishes. To update a draft in place, send its id with the fields you want to change.

Schedule it: schedule_post

schedule_post takes the draft id and scheduled_for. scheduled_for must be a future ISO-8601 time with a timezone.

curl -X POST https://pdb.postpen.ai/functions/v1/agent-api/schedule_post \
  -H "Authorization: Bearer pp_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"id": "DRAFT_ID", "scheduled_for": "2026-10-15T09:00:00-04:00"}'

That's the whole LinkedIn post scheduler API flow. There's no cron job on your side, and PostPen publishes the post when it's due. The post also appears on the PostPen calendar at app.postpen.ai, where a person can edit, move or delete it before it goes out.

Attach media: create_upload

For larger files, use a three-step upload:

  1. Call create_upload with a type of image, video or document. Pass the optional content_type (for example image/png) and size_bytes to check the file against the limit first. It returns a short-lived upload_url, the headers to send with it, and an upload_id.
  2. PUT the raw file to the upload_url, using the headers that create_upload returned. The URL expires after 2 hours.
  3. Pass media: [{ "upload_id": "UPLOAD_ID" }] to save_draft.
curl -X POST https://pdb.postpen.ai/functions/v1/agent-api/create_upload \
  -H "Authorization: Bearer pp_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "image", "content_type": "image/png"}'

Send the file bytes with a PUT to the upload_url before it expires. Don't hardcode a Content-Type: copy the headers from the create_upload response.

curl -X PUT "UPLOAD_URL" \
  -H "HEADER_FROM_CREATE_UPLOAD: VALUE" \
  --upload-file chart.png
curl -X POST https://pdb.postpen.ai/functions/v1/agent-api/save_draft \
  -H "Authorization: Bearer pp_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"content": "Q3 in one chart.", "media": [{"upload_id": "UPLOAD_ID"}]}'

Size limits are 5MB per image, 75KB to 200MB for an mp4, and 100MB for a PDF. media also accepts a public HTTPS url, or base64 data for files up to 4MB.

Publish now, carefully: publish_now

publish_now sends a saved post to LinkedIn right away. It is refused unless the body includes confirm: true.

curl -X POST https://pdb.postpen.ai/functions/v1/agent-api/publish_now \
  -H "Authorization: Bearer pp_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"id": "DRAFT_ID", "confirm": true}'

Treat that flag as a safety pattern. If a bot builds requests automatically, have it ask a person before it sets confirm: true.

Read results: get_post_stats and get_insights

get_post_stats takes the id of a sent post. It returns impressions, reach, reactions, comments, reposts and engagement rate, plus clicks on company page posts.

curl -X POST https://pdb.postpen.ai/functions/v1/agent-api/get_post_stats \
  -H "Authorization: Bearer pp_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"id": "POST_ID"}'

get_insights takes an optional days value from 7 to 90 (default 30). It compares that period with the one directly before it, so your bot can see which posts beat the usual.

Using the same tools from an AI agent (MCP)

If your caller is an AI agent rather than a script, use MCP instead. Add https://mcp.postpen.ai to Claude, ChatGPT, Cursor, Claude Code, Codex, Gemini CLI or any MCP client.

The agent signs in through the browser, so there's no key to paste. It gets the same eleven tools. Per-agent steps are in the LinkedIn MCP setup guide.

LinkedIn Posts API vs PostPen API at a glance

LinkedIn Posts APIPostPen API
Who holds the LinkedIn tokenYour appPostPen; your bot never sees it
Auth method3-legged OAuth, w_member_social (plus w_organization_social for pages)Authorization: Bearer pp_... from app.postpen.ai/agents
SchedulingNo scheduling field documented; you run the queueschedule_post with a future ISO-8601 time and timezone
DestinationsMember or organization URN as authorIds from list_destinations: profile, plus company pages on Organization (2) or Enterprise (3+)
StatsSeparate LinkedIn APIs and permissionsget_post_stats and get_insights

Limits and errors

These come from openapi.json and https://postpen.ai/llms.txt.

  • Text: content is limited to 3,000 characters. first_comment is limited to 1,250.
  • Media: images up to 5MB each (up to 9), one mp4 video from 75KB to 200MB, or one PDF up to 100MB, shown as a carousel. Don't mix types in one post.
  • Tags: mentions takes up to 10 company page links. People can't be tagged, and tags aren't applied to PDF posts.
  • Confirm guards: publish_now, delete_draft and unschedule_post all require confirm: true.
  • Reconnect: if a stats tool returns needs_reconnect: true, reconnect LinkedIn in PostPen.

The documented error codes:

CodeMeaning
400Invalid input, or a confirm: true guard was not met
401Missing or invalid API key
402Plan limit reached, for example the Free plan's monthly post cap
403Destination not allowed for this account or plan

To cut off a key, choose Revoke next to it on app.postpen.ai/agents. Anything using that key stops working right away, per PostPen support.

FAQ

Can you schedule LinkedIn posts with the LinkedIn API?

LinkedIn's Posts API documentation describes no scheduling field, and PUBLISHED is the only lifecycle state accepted when you create a post. To schedule, you hold the post in your own queue and call the API at publish time, or send it to a service such as PostPen with schedule_post.

What permissions do I need to post to LinkedIn via API?

For a member's own profile, w_member_social, granted through the Share on LinkedIn product. For a company page, w_organization_social plus an administrator or content admin role on the page, per the Posts API docs.

Do I need a LinkedIn developer app to use PostPen's API?

No. You connect LinkedIn once inside PostPen, then use a PostPen API key from app.postpen.ai/agents.

Does my bot ever see the LinkedIn token?

No. The token stays on PostPen's server, and no tool accepts or returns one.

Can I post to a company page through PostPen's API?

Yes, on the Organization plan ($29/month, personal profile plus 2 company pages). Use the destination id from list_destinations. Enterprise covers more pages; see pricing.

Is there an MCP version?

Yes. The MCP server at https://mcp.postpen.ai has the same eleven tools, with browser sign-in instead of a key.

Send your first scheduled post

Create a key, call save_draft, then schedule_post. Check the calendar when you want to see what's queued.

Start free or create an API key.