Posts
Publish text, image, multi-image, and video to a LinkedIn personal profile.
LinkedIn publishes to the connected member's profile through the shared posts surface. mediaKind is required on every post.
Quick Start
Publish a post in under a minute:
import { Configuration, PostsApi } from "@onepostly/sdk";
const postsApi = new PostsApi(
new Configuration({ apiKey: process.env.ONEPOSTLY_API_KEY })
);
const response = await postsApi.createPost({
createPostBody: {
"text": "Ship day",
"mediaUrls": [
"https://cdn.onepostly.com/media/product.jpg"
],
"mediaKind": "image",
"destinations": [
{
"accountId": "a1b2c3d4-e5f6-4a7b-8c9d-e0f1a2b3c4d5"
}
]
},
});
console.log(response);{
"post": {
"id": "b3e1a7c2-8f4d-4e6a-9c1b-2d3e4f5a6b7c",
"text": "Ship day",
"mediaUrls": [
"https://cdn.onepostly.com/media/product.jpg"
],
"mediaKind": "image",
"status": "queued",
"scheduledFor": null,
"timezone": null,
"destinations": [
{
"id": "d4e5f6a7-8b9c-4d0e-8f1a-2b3c4d5e6f7a",
"accountId": "a1b2c3d4-e5f6-4a7b-8c9d-e0f1a2b3c4d5",
"platform": "linkedin",
"status": "draft",
"externalPostId": "17895695668004550",
"externalUrl": "https://www.linkedin.com/feed/update/17895695668004550",
"errorCode": null,
"errorMessage": null,
"publishedAt": "2026-08-25T10:00:00.000Z",
"metrics": null
}
],
"createdAt": "2026-08-25T10:00:00.000Z",
"updatedAt": "2026-08-25T10:00:00.000Z"
}
}The response is 202 Accepted with status: "queued". Poll GET /v1/posts/:id until the destination is published or failed, or receive webhooks instead of polling.
Content Types
mediaKind | Content | Media count | Limits |
|---|---|---|---|
text | Text-only post | No media | Text required |
image | Single image | Exactly 1 URL | Caption optional |
multi-image | Image carousel | 2 to 20 URLs | Images only. Video URLs fail with INVALID_MEDIA |
video | Single video | Exactly 1 URL | MP4, 75 KB to 500 MB |
document | Document carousel | Exactly 1 URL | PDF, PPT, PPTX, DOC, DOCX — max 100 MB |
stories | No | No | Not supported on LinkedIn |
Text 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:
import { Configuration, PostsApi } from "@onepostly/sdk";
const postsApi = new PostsApi(
new Configuration({ apiKey: process.env.ONEPOSTLY_API_KEY })
);
const response = await postsApi.createPost({
createPostBody: {
"text": "Hiring: senior backend engineer",
"mediaUrls": [],
"mediaKind": "text",
"destinations": [
{
"accountId": "a1b2c3d4-e5f6-4a7b-8c9d-e0f1a2b3c4d5"
}
]
},
});
console.log(response);{
"post": {
"id": "b3e1a7c2-8f4d-4e6a-9c1b-2d3e4f5a6b7c",
"text": "Hiring: senior backend engineer",
"mediaUrls": [],
"mediaKind": "text",
"status": "queued",
"scheduledFor": null,
"timezone": null,
"destinations": [
{
"id": "d4e5f6a7-8b9c-4d0e-8f1a-2b3c4d5e6f7a",
"accountId": "a1b2c3d4-e5f6-4a7b-8c9d-e0f1a2b3c4d5",
"platform": "linkedin",
"status": "draft",
"externalPostId": "17895695668004550",
"externalUrl": "https://www.linkedin.com/feed/update/17895695668004550",
"errorCode": null,
"errorMessage": null,
"publishedAt": "2026-08-25T10:00:00.000Z",
"metrics": null
}
],
"createdAt": "2026-08-25T10:00:00.000Z",
"updatedAt": "2026-08-25T10:00:00.000Z"
}
}Single Image
import { Configuration, PostsApi } from "@onepostly/sdk";
const postsApi = new PostsApi(
new Configuration({ apiKey: process.env.ONEPOSTLY_API_KEY })
);
const response = await postsApi.createPost({
createPostBody: {
"text": "Ship day",
"mediaUrls": [
"https://cdn.onepostly.com/media/product.jpg"
],
"mediaKind": "image",
"destinations": [
{
"accountId": "a1b2c3d4-e5f6-4a7b-8c9d-e0f1a2b3c4d5"
}
]
},
});
console.log(response);{
"post": {
"id": "b3e1a7c2-8f4d-4e6a-9c1b-2d3e4f5a6b7c",
"text": "Ship day",
"mediaUrls": [
"https://cdn.onepostly.com/media/product.jpg"
],
"mediaKind": "image",
"status": "queued",
"scheduledFor": null,
"timezone": null,
"destinations": [
{
"id": "d4e5f6a7-8b9c-4d0e-8f1a-2b3c4d5e6f7a",
"accountId": "a1b2c3d4-e5f6-4a7b-8c9d-e0f1a2b3c4d5",
"platform": "linkedin",
"status": "draft",
"externalPostId": "17895695668004550",
"externalUrl": "https://www.linkedin.com/feed/update/17895695668004550",
"errorCode": null,
"errorMessage": null,
"publishedAt": "2026-08-25T10:00:00.000Z",
"metrics": null
}
],
"createdAt": "2026-08-25T10:00:00.000Z",
"updatedAt": "2026-08-25T10:00:00.000Z"
}
}Multi-Image
Requires between 2 and 20 image URLs. Video URLs are rejected:
import { Configuration, PostsApi } from "@onepostly/sdk";
const postsApi = new PostsApi(
new Configuration({ apiKey: process.env.ONEPOSTLY_API_KEY })
);
const response = await postsApi.createPost({
createPostBody: {
"text": "Highlights from our team retreat!",
"mediaUrls": [
"https://cdn.example.com/photo1.jpg",
"https://cdn.example.com/photo2.jpg",
"https://cdn.example.com/photo3.jpg"
],
"mediaKind": "multi-image",
"destinations": [
{
"accountId": "YOUR_LINKEDIN_ACCOUNT_ID"
}
]
},
});
console.log(response);{
"post": {
"id": "b3e1a7c2-8f4d-4e6a-9c1b-2d3e4f5a6b7c",
"text": "Highlights from our team retreat!",
"mediaUrls": [
"https://cdn.example.com/photo1.jpg",
"https://cdn.example.com/photo2.jpg",
"https://cdn.example.com/photo3.jpg"
],
"mediaKind": "multi-image",
"status": "queued",
"scheduledFor": null,
"timezone": null,
"destinations": [
{
"id": "d4e5f6a7-8b9c-4d0e-8f1a-2b3c4d5e6f7a",
"accountId": "YOUR_LINKEDIN_ACCOUNT_ID",
"platform": "linkedin",
"status": "draft",
"externalPostId": "17895695668004550",
"externalUrl": "https://www.linkedin.com/feed/update/17895695668004550",
"errorCode": null,
"errorMessage": null,
"publishedAt": "2026-08-25T10:00:00.000Z",
"metrics": null
}
],
"createdAt": "2026-08-25T10:00:00.000Z",
"updatedAt": "2026-08-25T10:00:00.000Z"
}
}Video
Videos process before posting
LinkedIn processes the video after upload and before the post is created. Large videos can take a few minutes. The destination stays queued until the media is ready. If LinkedIn is still processing after several minutes the publish fails with MEDIA_PROCESSING_TIMEOUT.
import { Configuration, PostsApi } from "@onepostly/sdk";
const postsApi = new PostsApi(
new Configuration({ apiKey: process.env.ONEPOSTLY_API_KEY })
);
const response = await postsApi.createPost({
createPostBody: {
"text": "Our story so far",
"mediaUrls": [
"https://cdn.onepostly.com/media/demo.mp4"
],
"mediaKind": "video",
"destinations": [
{
"accountId": "a1b2c3d4-e5f6-4a7b-8c9d-e0f1a2b3c4d5"
}
]
},
});
console.log(response);{
"post": {
"id": "b3e1a7c2-8f4d-4e6a-9c1b-2d3e4f5a6b7c",
"text": "Our story so far",
"mediaUrls": [
"https://cdn.onepostly.com/media/demo.mp4"
],
"mediaKind": "video",
"status": "queued",
"scheduledFor": null,
"timezone": null,
"destinations": [
{
"id": "d4e5f6a7-8b9c-4d0e-8f1a-2b3c4d5e6f7a",
"accountId": "a1b2c3d4-e5f6-4a7b-8c9d-e0f1a2b3c4d5",
"platform": "linkedin",
"status": "draft",
"externalPostId": "17895695668004550",
"externalUrl": "https://www.linkedin.com/feed/update/17895695668004550",
"errorCode": null,
"errorMessage": null,
"publishedAt": "2026-08-25T10:00:00.000Z",
"metrics": null
}
],
"createdAt": "2026-08-25T10:00:00.000Z",
"updatedAt": "2026-08-25T10:00:00.000Z"
}
}Document
One document per post, displayed as a swipeable carousel. Cannot be combined with images or video. The post text (up to 200 characters) becomes the document title:
import { Configuration, PostsApi } from "@onepostly/sdk";
const postsApi = new PostsApi(
new Configuration({ apiKey: process.env.ONEPOSTLY_API_KEY })
);
const response = await postsApi.createPost({
createPostBody: {
"text": "Download our 2024 Industry Report",
"mediaUrls": [
"https://cdn.example.com/report.pdf"
],
"mediaKind": "document",
"destinations": [
{
"accountId": "a1b2c3d4-e5f6-4a7b-8c9d-e0f1a2b3c4d5"
}
]
},
});
console.log(response);{
"post": {
"id": "b3e1a7c2-8f4d-4e6a-9c1b-2d3e4f5a6b7c",
"text": "Download our 2024 Industry Report",
"mediaUrls": [
"https://cdn.example.com/report.pdf"
],
"mediaKind": "document",
"status": "queued",
"scheduledFor": null,
"timezone": null,
"destinations": [
{
"id": "d4e5f6a7-8b9c-4d0e-8f1a-2b3c4d5e6f7a",
"accountId": "a1b2c3d4-e5f6-4a7b-8c9d-e0f1a2b3c4d5",
"platform": "linkedin",
"status": "draft",
"externalPostId": "17895695668004550",
"externalUrl": "https://www.linkedin.com/feed/update/17895695668004550",
"errorCode": null,
"errorMessage": null,
"publishedAt": "2026-08-25T10:00:00.000Z",
"metrics": null
}
],
"createdAt": "2026-08-25T10:00:00.000Z",
"updatedAt": "2026-08-25T10:00:00.000Z"
}
}Destination Parameters
Every LinkedIn destination accepts the following parameters.
Prop
Type
Media Requirements
Images
| Property | Requirement |
|---|---|
| Max images | 20 per post |
| Max file size | 30 MB |
| Recommended | 1200 × 627 px (1.91:1 landscape) |
Aspect ratios
| Type | Ratio | Dimensions | Use Case |
|---|---|---|---|
| Landscape | 1.91:1 | 1200 × 627 px | Standard feed |
| Square | 1:1 | 1080 × 1080 px | Engagement |
| Portrait | 1:1.25 | 1080 × 1350 px | Mobile feed |
Videos
| Property | Requirement |
|---|---|
| Max videos | 1 per post |
| Formats | MP4 |
| Max file size | 500 MB |
| Min file size | 75 KB |
| Min duration | 3 seconds |
| Max duration | 30 minutes |
| Recommended | 1080p, H.264, AAC audio |
Documents
| Property | Requirement |
|---|---|
| Max documents | 1 per post |
| Formats | PDF, PPT, PPTX, DOC, DOCX |
| Max file size | 100 MB |
| Title | Post text, first 200 characters |
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, the URL will not work.
Media URLs must be publicly accessible, return actual media bytes with the correct Content-Type, and use HTTPS.
Schedule
Add scheduledFor (local wall time) and timezone to the same call. The request shape is otherwise identical to an immediate post.
timezonedefaults toUTC. Use an IANA name likeEurope/Istanbul- Do not append
Zor an offset toscheduledFor. It is local wall time - Scheduled posts return
201 Createdand publish automatically at the scheduled moment - Max 1 year ahead
- Cancel with
posts.cancelPost({ id })/posts.cancel_post(id=…), orDELETE /v1/posts/:id, any time before then
Remote Delete
Removes the post from the profile and emits the post.platform.deleted webhook:
import { Configuration, PostsApi } from "@onepostly/sdk";
const postsApi = new PostsApi(
new Configuration({ apiKey: process.env.ONEPOSTLY_API_KEY })
);
const response = await postsApi.deletePostDestination({
destinationId: "d4e5f6a7-8b9c-4d0e-8f1a-2b3c4d5e6f7a",
});
console.log(response);{
"post": {
"id": "b3e1a7c2-8f4d-4e6a-9c1b-2d3e4f5a6b7c",
"text": "Ship day",
"mediaUrls": [
"https://cdn.onepostly.com/media/product.jpg"
],
"mediaKind": "image",
"status": "queued",
"scheduledFor": null,
"timezone": null,
"destinations": [
{
"id": "d4e5f6a7-8b9c-4d0e-8f1a-2b3c4d5e6f7a",
"accountId": "a1b2c3d4-e5f6-4a7b-8c9d-e0f1a2b3c4d5",
"platform": "linkedin",
"status": "draft",
"externalPostId": "17895695668004550",
"externalUrl": "https://www.linkedin.com/feed/update/17895695668004550",
"errorCode": null,
"errorMessage": null,
"publishedAt": "2026-08-25T10:00:00.000Z",
"metrics": null
}
],
"createdAt": "2026-08-25T10:00:00.000Z",
"updatedAt": "2026-08-25T10:00:00.000Z"
}
}Publish Errors
| Code | Cause | Fix |
|---|---|---|
TEXT_REQUIRED | Text-only post with empty message | Add text |
INVALID_MEDIA | Wrong media count, video in a multi-image post, or a file outside the video bounds | Match the content types table |
MEDIA_UNFETCHABLE | LinkedIn could not download the URL | Use a public HTTPS URL |
MEDIA_UPLOAD_FAILED | Upload or processing failed on LinkedIn's side | Retry. Check the file plays correctly |
UNSUPPORTED_FEATURE | quoteTweetId sent to LinkedIn | Quotes are not supported. Remove it |
UNSUPPORTED_MEDIA_KIND | mediaKind: "stories" | Use text, image, multi-image, or video |
TOKEN_INVALID | Connection token expired or revoked | Reconnect the account |
PERMISSION_DENIED | Connection lacks w_member_social | Reconnect the account |
RATE_LIMITED | LinkedIn throttled the request | Retry later |
LinkedIn's duplicate detection is strict. Identical or near-identical text returns a 422 from LinkedIn even across different time periods. Modify the text meaningfully before retrying.