---
title: "Sync an external post"
method: POST
path: "/v1/posts/sync-external"
tags: ["Analytics"]
---

# Sync an external post

`POST /v1/posts/sync-external`

Fetch an account's latest external posts (published directly on the platform, not through Zernio) on demand, so a newly published post is retrievable within seconds instead of waiting for the background sync (which refreshes each account at most every ~90 minutes).

Primary use case: verifying a submitted post. When a user publishes on the platform and immediately pastes the post URL into your app, call this with `accountId` plus `url` (or `postId`) to confirm the post exists and return its metadata.

Behavior:
- Account access and connection state are checked before any platform call, including requests inside the debounce window.
- Inactive accounts or accounts marked `needsReconnection` return `409` with code `ads_connection_required`. Stop scheduled retries for that account until it is reconnected, then read `GET /v1/accounts` for its current account ID.
- For connected accounts, we fetch the latest posts live from the platform, then match and return the submitted post.
- Requests are debounced per account (~15s): if the account was synced inside that window, the live fetch is skipped.

`accountId` is required, because a post URL or id alone cannot be resolved to an account, and the account must be connected to Zernio (we use its token to read the platform). Supported for every platform with a listing API (Instagram, Facebook, TikTok, YouTube, X, Threads, Pinterest, Reddit, Bluesky, Google Business Profile, and LinkedIn organization accounts).

LinkedIn personal profiles: LinkedIn has no listing API for personal profiles, so a `url` is REQUIRED and imports that single post. Pass any LinkedIn post URL (`linkedin.com/posts/…`, `linkedin.com/feed/update/urn:li:activity:…`) or a `urn:li:share:…` / `urn:li:ugcPost:…` URN. Works for posts published outside Zernio and before the account was connected, any age; the post must be authored by the connected member. Imported posts return full analytics (impressions, reach, reactions, comments, reshares, saves) and keep refreshing on the background analytics cycle, but carry no content/media (LinkedIn does not expose them for personal profiles).

`url` accepts any format the platform uses (e.g. `instagram.com/p/…`, `instagram.com/reel/…`, `youtu.be/…`, `youtube.com/shorts/…`, `tiktok.com/@user/video/…`, `vm.tiktok.com` short links, `pinterest.com/pin/…` on any regional domain, and `pin.it` short links). Pass `postId` (the platform media/video/pin id) as an alternative locator.

Note: post-level analytics (reach, impressions) still carry the platform's own delay (e.g. ~24h on Instagram). This endpoint confirms the post exists and returns its metadata plus basic engagement (likes, comments), not delayed insights.

## Request body

- object
  - `accountId` string, required — SocialAccount ID whose posts to sync. Must be connected to Zernio.
  - `url` string — The post URL to locate. Optional. Provide `url` or `postId` to return a specific post; omit both to refresh and return the account's recent posts.
  - `postId` string — The platform post/media/video id to locate, as an alternative to `url`. Optional.

## Response `200`

Sync result. When `url` or `postId` is provided, returns the matched post (or `found: false`). When neither is provided, returns the account's freshly-synced recent posts.

- object
  - `synced` object
    - `postsFound` integer — Posts returned by the platform listing during the on-demand sync
    - `postsSynced` integer — Posts inserted or updated in Zernio
    - `skipped` boolean — True when the account was synced within the debounce window and no live fetch ran.
  - `found` boolean — Present only when a locator (`url`/`postId`) was provided: whether the post was found.
  - `post` ExternalPostSummary — A post synced from a platform (published directly on the platform, not through Zernio). Returned by GET /v1/posts?source=external and POST /v1/posts/sync-external. Analytics are exposed separately via GET /v1/analytics?source=external.
    - `platform` string — Platform the post belongs to (e.g. instagram, youtube, tiktok)
    - `platformPostId` string — The platform's own post/media/video id
    - `platformPostUrl` string — Canonical URL (permalink) of the post on the platform
    - `content` string — Post caption / text
    - `publishedAt` string, date-time — When the post was published on the platform
    - `mediaType` string — Media type (e.g. image, video, carousel)
    - `thumbnailUrl` string — Thumbnail URL
    - `mediaItems` object[] — Per-item media (for carousels / multi-media posts)
    - `mediaProductType` string — Instagram only: the platform media product type (e.g. FEED, REELS, STORY, AD). Absent when the platform did not report it.
    - `isAiGenerated` boolean — Instagram only: whether Instagram labeled the media as AI-generated. Absent when the platform did not report it.
    - `isSharedToFeed` boolean — Instagram reels only: whether the reel is also shared to the main feed. Absent when the platform did not report it.
    - `mediaAudioType` string — Instagram only: audio type of the media (MUSIC or ORIGINAL_SOUND). Absent when the platform did not report it.
    - `analytics` object — Engagement + insights for the post. `likes` and `comments` are available immediately after an on-demand sync (they come from the platform listing). `reach`, `impressions`, `views` depend on the platform's insights, which carry their own delay (e.g. ~24h on Instagram) and read 0 until the platform makes them available.
      - `likes` integer
      - `comments` integer
      - `shares` integer
      - `saves` integer
      - `sends` integer
      - `clicks` integer
      - `views` integer
      - `reach` integer
      - `impressions` integer
      - `engagementRate` number — Percentage, rounded to 2 decimals. Same definition as PostAnalytics.engagementRate: (likes + comments + shares + saves) / (impressions or reach or views) * 100, where the denominator is the first of the three that is non-zero. Clicks and follows are never counted.
      - `lastUpdated` string, date-time — When these metrics were last refreshed
  - `posts` ExternalPostSummary[] — The account's recent external posts. Present only when no locator was provided.
    - `platform` string — Platform the post belongs to (e.g. instagram, youtube, tiktok)
    - `platformPostId` string — The platform's own post/media/video id
    - `platformPostUrl` string — Canonical URL (permalink) of the post on the platform
    - `content` string — Post caption / text
    - `publishedAt` string, date-time — When the post was published on the platform
    - `mediaType` string — Media type (e.g. image, video, carousel)
    - `thumbnailUrl` string — Thumbnail URL
    - `mediaItems` object[] — Per-item media (for carousels / multi-media posts)
    - `mediaProductType` string — Instagram only: the platform media product type (e.g. FEED, REELS, STORY, AD). Absent when the platform did not report it.
    - `isAiGenerated` boolean — Instagram only: whether Instagram labeled the media as AI-generated. Absent when the platform did not report it.
    - `isSharedToFeed` boolean — Instagram reels only: whether the reel is also shared to the main feed. Absent when the platform did not report it.
    - `mediaAudioType` string — Instagram only: audio type of the media (MUSIC or ORIGINAL_SOUND). Absent when the platform did not report it.
    - `analytics` object — Engagement + insights for the post. `likes` and `comments` are available immediately after an on-demand sync (they come from the platform listing). `reach`, `impressions`, `views` depend on the platform's insights, which carry their own delay (e.g. ~24h on Instagram) and read 0 until the platform makes them available.
      - `likes` integer
      - `comments` integer
      - `shares` integer
      - `saves` integer
      - `sends` integer
      - `clicks` integer
      - `views` integer
      - `reach` integer
      - `impressions` integer
      - `engagementRate` number — Percentage, rounded to 2 decimals. Same definition as PostAnalytics.engagementRate: (likes + comments + shares + saves) / (impressions or reach or views) * 100, where the denominator is the first of the three that is non-zero. Clicks and follows are never counted.
      - `lastUpdated` string, date-time — When these metrics were last refreshed

## Other responses

- `400` — Invalid request (e.g. `accountId` missing or malformed)
- `401` — The platform authorization has expired. Reconnect the account.
- `403` — The API key lacks access to this account or the platform denied access.
- `404` — Account not found (or not owned by the authenticated user)
- `409` — The account is inactive or needs reconnection. Stop scheduled retries until it is reconnected.
- `502` — The platform returned a server error.
- `503` — An upstream service or database is temporarily unavailable. Retry after the indicated delay. A timed-out write may have completed upstream; check its outcome before resubmitting.

## Changes

- **2026-09-16** `3e6ddf2a99ea` — 5 info
  - added the optional property `details/budgetScope` to the response with the `400` status
  - added the optional property `details/budgetScope` to the response with the `404` status
  - added the optional property `details/budgetScope` to the response with the `409` status
  - added the optional property `details/budgetScope` to the response with the `502` status
  - …1 more
- **2026-09-15** `0dba7d004d75` — 10 info
  - added the optional property `details/quotaExhausted` to the response with the `400` status
  - added the optional property `details/quotaExhausted` to the response with the `404` status
  - added the optional property `details/quotaExhausted` to the response with the `409` status
  - added the optional property `details/quotaExhausted` to the response with the `502` status
  - …6 more
- **2026-09-10** `e70ed06e7150` — 5 info
  - added the non-success response with the status `401`
  - added the non-success response with the status `403`
  - added the non-success response with the status `409`
  - added the non-success response with the status `502`
  - …1 more
- …earlier changes not shown

[Full history](https://skmtc.dev/zernio/apis/zernio-api/changes/v1/posts/sync-external/post.md)

---

[API](https://skmtc.dev/zernio/apis/zernio-api.md) · [All operations](https://skmtc.dev/zernio/apis/zernio-api/llms.txt) · [OpenAPI document](https://skmtc.dev/zernio/apis/zernio-api/revisions/be448f13ecdc?raw)
