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:
- A LinkedIn developer app, created in the LinkedIn Developer Portal.
- The Share on LinkedIn product added to that app, which grants the
w_member_socialpermission, per LinkedIn's Share on LinkedIn docs. - A 3-legged OAuth flow that sends the member to LinkedIn's consent screen and requests
w_member_social. - 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_draftand/schedule_post - Method: every operation is
POSTwith a JSON body - Auth:
Authorization: Bearer pp_...
Get a key
- Sign in to PostPen and connect LinkedIn once. Company pages you admin come from the same login.
- Open
https://app.postpen.ai/agents. - Create an API key. It starts with
pp_. - 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:
- Call
create_uploadwith atypeofimage,videoordocument. Pass the optionalcontent_type(for exampleimage/png) andsize_bytesto check the file against the limit first. It returns a short-livedupload_url, the headers to send with it, and anupload_id. PUTthe raw file to theupload_url, using the headers thatcreate_uploadreturned. The URL expires after 2 hours.- Pass
media: [{ "upload_id": "UPLOAD_ID" }]tosave_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 API | PostPen API | |
|---|---|---|
| Who holds the LinkedIn token | Your app | PostPen; your bot never sees it |
| Auth method | 3-legged OAuth, w_member_social (plus w_organization_social for pages) | Authorization: Bearer pp_... from app.postpen.ai/agents |
| Scheduling | No scheduling field documented; you run the queue | schedule_post with a future ISO-8601 time and timezone |
| Destinations | Member or organization URN as author | Ids from list_destinations: profile, plus company pages on Organization (2) or Enterprise (3+) |
| Stats | Separate LinkedIn APIs and permissions | get_post_stats and get_insights |
Limits and errors
These come from openapi.json and https://postpen.ai/llms.txt.
- Text:
contentis limited to 3,000 characters.first_commentis 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:
mentionstakes up to 10 company page links. People can't be tagged, and tags aren't applied to PDF posts. - Confirm guards:
publish_now,delete_draftandunschedule_postall requireconfirm: true. - Reconnect: if a stats tool returns
needs_reconnect: true, reconnect LinkedIn in PostPen.
The documented error codes:
| Code | Meaning |
|---|---|
400 | Invalid input, or a confirm: true guard was not met |
401 | Missing or invalid API key |
402 | Plan limit reached, for example the Free plan's monthly post cap |
403 | Destination 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.
