Agency LinkedIn Page reshares

View as Markdown

Use this flow when employees reshare a post published by their company's LinkedIn Page from their own profiles, each after their own approval. The Page and the employee profiles are connected channels of the same Agency Workspace. Each employee gets a separate draft and a separate review link; no Publora account or Agency seat is needed to approve.

Company API paths are relative to https://api.publora.com/api/v1/company. Send your own x-publora-key and the selected X-Publora-Workspace-Id on every request. Mutations need a unique Idempotency-Key for each intended operation; retry an uncertain request with the identical body and key. MCP clients use the company_* tools with top-level workspaceId.

  1. Subscribe a Workspace webhook to approval.decided, post.published and post.failed before requesting reviews. See Agency webhooks.
  2. Find the source post on the connected Company Page.
  3. Create one draft per employee profile with the repost settings and the source snapshot.
  4. Request a review for each draft, optionally with a publication time.
  5. Distribute each review link privately and track decisions and publication results.

Find a Company Page post

POST /linkedin-page-posts lists published posts authored by one connected Company Page. It is read-only: it creates no draft, review or publication. The MCP equivalent is company_list_linkedin_page_posts.

curl -X POST 'https://api.publora.com/api/v1/company/linkedin-page-posts' \
  -H "x-publora-key: $PUBLORA_API_KEY" \
  -H "X-Publora-Workspace-Id: $WORKSPACE_ID" \
  -H 'Content-Type: application/json' \
  -d '{"connectionId":"64b000000000000000000301","start":0,"count":10,"sortBy":"LAST_MODIFIED"}'
Field Type Required Description
connectionId string Yes The Company Page connection's database ID from Company /connections (id in company_list_channels), not a channel key, provider ID or personal connection
start integer No Offset from 0 to 2147483647, default 0. Pass the previous paging.nextStart
count integer No Page size from 1 to 100, default 10
sortBy string No LAST_MODIFIED (default) or CREATED

No other body fields are accepted. The author is derived from the authorized connection: you cannot select another organization, author or URL.

{
  "success":true,
  "data":{
    "connectionId":"64b000000000000000000301",
    "authorUrn":"urn:li:organization:2414183",
    "items":[{
      "postedId":"urn:li:share:7520484550762942467",
      "permalink":"https://www.linkedin.com/feed/update/urn:li:share:7520484550762942467/",
      "commentary":"Company announcement",
      "publishedAt":"2026-10-28T10:00:00.000Z",
      "lastModifiedAt":"2026-10-28T10:00:00.000Z",
      "visibility":"PUBLIC",
      "feedDistribution":"MAIN_FEED",
      "isReshareDisabledByAuthor":false,
      "reshareParentUrn":null,
      "sourceSnapshot":{
        "postedId":"urn:li:share:7520484550762942467",
        "commentary":"Company announcement",
        "authorName":"Company Page",
        "publishedAt":"2026-10-28T10:00:00.000Z",
        "capturedAt":"2026-11-03T12:00:00.000Z"
      }
    }],
    "paging":{"start":0,"count":10,"nextStart":10}
  }
}
  • postedId is always the listed post's own canonical urn:li:share:… or urn:li:ugcPost:… ID. Use it as the repost parent. reshareParentUrn is only metadata about a post that is itself a reshare; do not use it instead.
  • Follow paging.nextStart with the same connection, count and sortBy until it is null. A short or even empty page can still have a next page. Pagination is LinkedIn's live offset, so deduplicate by postedId while posts change.
  • Only published posts with canonical IDs are returned. Activity IDs are omitted and arbitrary LinkedIn URLs are not resolved.
  • commentary is a preview of at most 30,000 characters. Timestamps, visibility, feed distribution, the reshare-disabled flag and the parent are null when LinkedIn does not provide them. The list can include sponsored posts. Check visibility, distribution and isReshareDisabledByAuthor before choosing a source; a missing flag does not guarantee that LinkedIn accepts the reshare.
  • The original can change or disappear after this read. Publication still runs the normal LinkedIn checks.

Reading requires internal content view access in the Workspace and a Company Page connection with a usable token. Organization posts require the Page's existing r_organization_social permission.

Status/code Meaning Recovery
400 LINKEDIN_INVALID_REQUEST Unknown field or invalid start, count or sortBy Correct the request.
400 LINKEDIN_COMPANY_PAGE_REQUIRED The connection is a personal profile, not a Company Page Use the Page connection's ID.
401 LINKEDIN_TOKEN_EXPIRED / LINKEDIN_RECONNECT_REQUIRED The Page connection needs to be reconnected Reconnect the Page, then retry.
403 AGENCY_CORE_ACCESS_DENIED No current access to this Workspace's content Refresh context and use an accessible Workspace.
404 AGENCY_CORE_CONNECTION_NOT_FOUND No such LinkedIn connection in the selected Workspace Refresh the Workspace's connections.
429 LINKEDIN_RATE_LIMITED LinkedIn quota or cooldown Honor Retry-After.
502 LINKEDIN_INVALID_RESPONSE LinkedIn returned an unusable page or continuation Retry later from the same start.
503 COMPANY_LINKEDIN_UNAVAILABLE Temporarily unavailable Retry later.

Create one draft per employee

Create a separate draft for each employee with exactly one target: that employee's personal LinkedIn channel key (linkedin-<platformId> from the Workspace's connections). Keep the source Page out of the targets. Commentary is optional and media is forbidden on a repost. See Agency platform settings for the repost fields.

{
  "platforms":["linkedin-EMPLOYEE_PLATFORM_ID"],
  "content":"",
  "platformSettings":{
    "linkedin":{
      "repostEnabled":true,
      "repostParentUrn":"urn:li:share:7520484550762942467",
      "repostVisibility":"PUBLIC"
    }
  },
  "repostSourceSnapshot":{
    "postedId":"urn:li:share:7520484550762942467",
    "commentary":"Company announcement",
    "authorName":"Company Page",
    "publishedAt":"2026-10-28T10:00:00.000Z",
    "capturedAt":"2026-11-03T12:00:00.000Z"
  }
}

Send it with POST /post-groups or update an existing draft with PATCH /post-groups/:postGroupId. MCP clients put the same fields inside draft of company_create_post / company_update_post.

Optional top-level repostSourceSnapshot shows the original post's text, author and capture time inside the client review. Copy the finder item's sourceSnapshot verbatim:

Field Type Rules
postedId string Required. Must equal platformSettings.linkedin.repostParentUrn
commentary string Required, at most 30,000 characters (may be empty)
authorName string or null Optional, at most 300 characters
publishedAt string or null Optional exact UTC ISO timestamp from the finder
capturedAt string Required exact UTC ISO timestamp from the finder

Unknown fields and avatar URLs are rejected. null clears the snapshot. It is a descriptive preview supplied by the draft author, not live LinkedIn data or a publishing permission, and it does not change what is published. Changing or clearing it invalidates an existing client approval. Changing the repost parent or removing the LinkedIn target clears an older snapshot automatically. Save the snapshot in your stored request plan and resend it unchanged on retries instead of calling the finder again. Without a snapshot, the review shows the link to the original on LinkedIn.

GET /post-groups/:postGroupId?includeRepostSource=true (MCP: company_get_post with includeRepostSource: true) adds group.repostSourceSnapshot when a matching snapshot is saved. Without the parameter the response is unchanged.

Request reviews

POST /post-groups/:postGroupId/approval starts a new review round for the saved draft and returns its review link (reviewUrl). MCP: company_request_post_approval. Finish edits and media uploads first.

curl -X POST "https://api.publora.com/api/v1/company/post-groups/$POST_GROUP_ID/approval" \
  -H "x-publora-key: $PUBLORA_API_KEY" \
  -H "X-Publora-Workspace-Id: $WORKSPACE_ID" \
  -H "Idempotency-Key: $REVIEW_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"managerComment":"Please review before Tuesday","scheduledTime":"2030-12-10T09:00:00.000Z"}'
Field Type Description
managerComment string Optional note for the client, at most 5,000 characters
scheduledTime string or null Optional future UTC instant. When set, approval schedules this exact version through the normal scheduler. Omit or null keeps manual publication
  • With a future scheduledTime, the client's approval and the scheduling commit together, with the same checks as a manual schedule. If scheduling validation fails, the approval is not saved (409 COMPANY_APPROVAL_SCHEDULING_FAILED, or 503 COMPANY_APPROVAL_SCHEDULING_UNAVAILABLE for a temporary failure); fix the post or channel, then the client approves again.
  • If the time passes before the client approves, the approval is saved and the post stays a draft. It is never published immediately instead. Schedule it explicitly or request a new round with a new time.
  • To change the time of a pending review, request a new round. After approval, use the normal reschedule and unschedule operations.
  • Clients can approve, decline with an optional comment, or request changes with a required comment. Declines and change requests never schedule. Editing text, channels, media, publishing settings or the source snapshot makes the round outdated; request a new one.

Treat each reviewUrl as a bearer credential: deliver it only to its employee and keep it out of logs and public exports. Store the mapping of employee, channel, draft and review ID in your application.

Track decisions and results

Approval and publication are separate states. An approval.decided event reports approved, declined or changes_requested for one review round and carries a stable id for deduplication; for a timed round it includes the publication state at decision time. post.published and post.failed report the actual per-channel outcome later. Deduplicate decisions by envelope id and publication events by event + postId. A late event for an older round must not replace the current round. See Agency webhooks.

To recover state after a missed delivery, read:

  • GET /post-groups/:postGroupId/approval for the current round, decision and feedback (MCP: company_get_post_approval).
  • GET /post-groups/:postGroupId for scheduling and per-channel publication results (MCP: company_get_post).

Clients that understand declined reviews send Accept: application/vnd.publora.company-approval.v2+json, application/json on approval reads and on list, calendar and detail reads with includeApproval=true. Without it, list and detail responses omit the approval details of a declined post, and a dedicated approval read of a declined or timed round returns 409 COMPANY_APPROVAL_CLIENT_UPDATE_REQUIRED. The MCP tools send the v2 header.

See also Company MCP tools and LinkedIn reshare for personal accounts.