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.

GET/api/public/posts
cURLGET /api/public/posts
curl "https://postiv.ai/api/public/posts?status=...&search=launch&sort=date&limit=10" \
  -H "Authorization: Bearer pk_postiv_..."
200Response
{
  "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.

channelstring

Narrow to one channel. Omit for LinkedIn and X together.

One of:linkedinx
integrationIdint

Alias: integration_id. A LinkedIn profile, company page or X account. An account decides the channel; a contradicting channel returns 400 channel_mismatch.

searchstring

Case-insensitive search across titles and post text, max 200 chars.

periodint

Only 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.

startdate

YYYY-MM-DD. Must be paired with end (400 invalid_date_range).

enddate

YYYY-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).

One of:dateimpressionsengagementsengagement_outlierreach_outliertype_outliertype_reach_outlier
limitintdefault: 50

Posts per page, 1-100.

cursorstring

nextCursor of the previous page, unchanged, with the same filters and sort. A cursor made under other filters returns 400 invalid_cursor.

offsetint

Deprecated, 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[].iduuid

The 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|null

The Postiv post; null for a post only imported from LinkedIn.

posts[].analyticsPostIduuid|null

The analytics record of a published post; null when there is none.

posts[].statusstring
posts[].channelstring

Channel of the post.

posts[].title / type / parentPostIdmixed

Postiv posts only. parentPostId links the versions of one post across accounts.

posts[].contentPreviewstring

First 300 chars of the post text; contentLength is the full length.

posts[].formatstring

X posts only: single, thread or article.

posts[].integrationIdint|null
posts[].profileobject|null

{ id, name, headline, picture } (X: also username).

posts[].postUrlstring|null
posts[].publishedAttimestamp

Published posts only.

posts[].scheduledTime / updatedAttimestamp|null

Postiv posts only.

posts[].deliveryobject|null

Latest 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|null

Published posts, with analytics:read: { impressions, engagements, engagementRate, reactions, comments, reshares }. null when the post is not measured yet.

posts[].outliersobject|null

Published LinkedIn posts, with analytics:read: { impressionsMultiplier, engagementMultiplier }.

nextCursorstring|null

Pass as cursor for the next page; null on the last page.

totalint

Number 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

400invalid_integration_id
400invalid_channel: channel is not linkedin or x / channel_mismatch: channel contradicts the channel of integrationId
400invalid_date_range: start and end must be given together as YYYY-MM-DD
400invalid_cursor: the cursor is malformed or was made under other filters or sort
400invalid_sort_for_status: a metric sort with a status other than published / unsupported_sort: an outlier sort when only X accounts are in scope
403insufficient_scope: a metric or outlier sort needs analytics:read
404integration_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).