Posts
List posts
Lists ALL workspace posts, LinkedIn and X together, one cursor-paged list: published posts (including posts imported from LinkedIn that were never created in Postiv, with their metrics), scheduled posts, drafts and failed posts. Filter by status/channel/account/text search/date range and sort by date or, for published posts, by impressions, engagements or outlier-vs-baseline. Read everything by passing nextCursor back until it is null; total is the number of matching posts. 'scheduled' only returns posts that actually have a live publish_queue entry (guards against stale status left by legacy bugs). A failed delivery leaves a post's status at scheduled with no live entry: 'failed' returns those posts too, and their delivery field says what happened. Metrics need the analytics:read scope: without it they are left out (see notes) and posts imported from LinkedIn are not listed.
/api/public/postscurl "https://postiv.ai/api/public/posts?status=...&search=launch&sort=date&limit=10" \
-H "Authorization: Bearer pk_postiv_..."{
"success": true,
"posts": [
{
"id": "post1...",
"postivPostId": "post1...",
"analyticsPostId": "ap1...",
"status": "published",
"channel": "linkedin",
"integrationId": 1234,
"profile": {
"id": 1234,
"name": "Jan",
"headline": "Founder",
"picture": null
},
"contentPreview": "Most founders think content strategy...",
"postUrl": "https://www.linkedin.com/feed/update/urn:li:share:1/",
"publishedAt": "2026-09-30T08:00:00.000Z",
"delivery": null,
"metrics": {
"impressions": 8400,
"engagements": 336,
"engagementRate": 4,
"reactions": 300,
"comments": 20,
"reshares": 10
},
"outliers": {
"impressionsMultiplier": 1.8,
"engagementMultiplier": 2.1
}
}
],
"nextCursor": "eyJzIjoiZGF0ZSIs...",
"total": 458
}Authorization
Requires an organization API key with the posts:read scope, sent as Authorization: Bearer or x-api-key.
Query parameters
statusstring|string[]draft, scheduled, published, failed, cancelled, pending_approval, approval_denied, changes_requested. Comma-separated or repeated param. Alias: statuses (both are merged). failed also matches scheduled posts whose latest delivery failed (including an X thread that stopped partway); scheduled leaves those out. Omit for every post.
channelstringNarrow to one channel. Omit for LinkedIn and X together.
linkedinxintegrationIdintAlias: integration_id. A LinkedIn profile, company page or X account. An account decides the channel; a contradicting channel returns 400 channel_mismatch.
searchstringCase-insensitive search across titles and post text, max 200 chars.
periodintOnly posts from the trailing N days (1-365). Ignored when start/end are given. Posts are dated by publish date when published, scheduled time when scheduled, last edit otherwise.
startdateYYYY-MM-DD. Must be paired with end (400 invalid_date_range).
enddateYYYY-MM-DD, inclusive. Must be paired with start.
sortstringdefault: "date"date is newest first. The others rank published posts and need analytics:read: with status omitted they list published posts only, with another status they return 400 invalid_sort_for_status. The outlier sorts are LinkedIn only (400 unsupported_sort when only X is in scope; in a mixed list posts without a value come last).
dateimpressionsengagementsengagement_outlierreach_outliertype_outliertype_reach_outlierlimitintdefault: 50Posts per page, 1-100.
cursorstringnextCursor of the previous page, unchanged, with the same filters and sort. A cursor made under other filters returns 400 invalid_cursor.
offsetintDeprecated, kept for integrations written before cursors: posts to skip, 0-5000. Use cursor instead. Combined with cursor it returns 400 invalid_pagination. The response's pagination block ({ limit, offset, count }) is kept for the same integrations.
Response
One page of posts with content previews (not full content), the cursor of the next page and the total.
posts[].iduuidThe postivPostId, or the analyticsPostId for a post only imported from LinkedIn. Accepted by GET /api/public/posts/:id and the MCP get_post tool.
posts[].postivPostIduuid|nullThe Postiv post; null for a post only imported from LinkedIn.
posts[].analyticsPostIduuid|nullThe analytics record of a published post; null when there is none.
posts[].statusstringposts[].channelstringChannel of the post.
posts[].title / type / parentPostIdmixedPostiv posts only. parentPostId links the versions of one post across accounts.
posts[].contentPreviewstringFirst 300 chars of the post text; contentLength is the full length.
posts[].formatstringX posts only: single, thread or article.
posts[].integrationIdint|nullposts[].profileobject|null{ id, name, headline, picture } (X: also username).
posts[].postUrlstring|nullposts[].publishedAttimestampPublished posts only.
posts[].scheduledTime / updatedAttimestamp|nullPostiv posts only.
posts[].deliveryobject|nullLatest publish attempt of a Postiv post, or null if it was never scheduled: { status, scheduledTime, lastError, connectionIssue, canReschedule } and, for X, { items: [{ index, status, url }], confirmedCount, totalCount }.
posts[].metricsobject|nullPublished posts, with analytics:read: { impressions, engagements, engagementRate, reactions, comments, reshares }. null when the post is not measured yet.
posts[].outliersobject|nullPublished LinkedIn posts, with analytics:read: { impressionsMultiplier, engagementMultiplier }.
nextCursorstring|nullPass as cursor for the next page; null on the last page.
totalintNumber of posts matching the filters across all pages.
notesstring[]Present when something was left out or implied, for example a key without analytics:read.
Errors
| 400 | invalid_integration_id |
| 400 | invalid_channel: channel is not linkedin or x / channel_mismatch: channel contradicts the channel of integrationId |
| 400 | invalid_date_range: start and end must be given together as YYYY-MM-DD |
| 400 | invalid_cursor: the cursor is malformed or was made under other filters or sort |
| 400 | invalid_sort_for_status: a metric sort with a status other than published / unsupported_sort: an outlier sort when only X accounts are in scope |
| 403 | insufficient_scope: a metric or outlier sort needs analytics:read |
| 404 | integration_not_found: integrationId is not an account in this workspace |
Good to know
- Cursor paging replaces offset: a cursor marks the last post served, so no post is skipped or repeated while the list changes. Keep filters, sort and limit identical between pages.
- Without analytics:read, metrics and outliers are left out and posts that exist only as LinkedIn imports are not listed (notes says so).