---
title: "List publications with their metrics"
method: GET
path: "/organizations/{id_organization}/publications/stats"
tags: ["publications"]
---

# List publications with their metrics

`GET /organizations/{id_organization}/publications/stats`

## Request

- Base URL: `https://api.planvortex.com/v1.0.0`
- URL: `https://api.planvortex.com/v1.0.0/organizations/{id_organization}/publications/stats`
- Auth: one of:
  - HTTP bearer
  - OAuth 2 (client credentials, token URL `https://api.planvortex.com/v1.0.0/oauth/token`)

Publications sent in the range, each with its last known normalized metrics, ordered by the requested metric. Covers the organization **and its children**.

Two things to keep in mind when reading the response:

- `summary` always covers the **whole organization**; `social_network` and `accounts` filter only the listing. Pass `summary=false` when paging so the aggregates are not recomputed on every page — they only depend on the date range.
- Publications that have **not been measured yet** are included, with `metrics` absent. That is the difference with `/publications/top`, which can only rank what has already been measured. An absent metric is not a zero.

Only `sended` publications are listed: a draft or a scheduled publication has nothing to measure.

## Path parameters

- `id_organization` string, required

## Query parameters

- `from_date` string, date-time
- `to_date` string, date-time
- `metric` 'impressions' | 'reach' | 'engagement' | 'likes' | 'comments' | 'shares' | 'saves' | 'clicks' | 'video_views' | 'profile_views'
- `social_network` string[]
- `accounts` string[]
- `summary` boolean
- `limit` integer
- `offset` integer

## Response `200`

Successful operation

- PublicationsPublicationsStatsList
  - `range` object: The resolved range and the immediately preceding period of the same length, which is what `summary.previous_total` covers.
    - `from_date` string, date-time
    - `to_date` string, date-time
    - `previous_from_date` string, date-time
    - `previous_to_date` string, date-time
  - `metric` string: Metric the listing is ordered by
  - `summary` object: Aggregates for the whole organization in the range. Omitted when `summary=false`.
    - `total` NormalizedMetrics: Metrics translated to a **common vocabulary** shared by every network, which is what makes two networks comparable and summable (each network names them differently: `page_post_engagements`, `total_interactions`, `views`…). **A missing key means the network does not publish that metric** — it is never an implicit zero. A key present with value `0` means it was measured and came out zero. Never default a missing key to 0 when displaying it. `engagement` is the network's own total when it provides one, and otherwise the sum of likes, comments, shares, saves and clicks. Video views are deliberately excluded from it: a view is not an interaction.
      - `impressions` integer: Times the content was shown (not unique)
      - `reach` integer: Unique users reached
      - `engagement` integer: Total interactions
      - `likes` integer
      - `comments` integer
      - `shares` integer
      - `saves` integer
      - `clicks` integer
      - `video_views` integer
      - `profile_views` integer
      - `followers` integer: Followers accumulated at that date, not the day's gain.
      - `followers_gained` integer: Followers gained that day.
    - `previous_total` NormalizedMetrics: Metrics translated to a **common vocabulary** shared by every network, which is what makes two networks comparable and summable (each network names them differently: `page_post_engagements`, `total_interactions`, `views`…). **A missing key means the network does not publish that metric** — it is never an implicit zero. A key present with value `0` means it was measured and came out zero. Never default a missing key to 0 when displaying it. `engagement` is the network's own total when it provides one, and otherwise the sum of likes, comments, shares, saves and clicks. Video views are deliberately excluded from it: a view is not an interaction.
      - `impressions` integer: Times the content was shown (not unique)
      - `reach` integer: Unique users reached
      - `engagement` integer: Total interactions
      - `likes` integer
      - `comments` integer
      - `shares` integer
      - `saves` integer
      - `clicks` integer
      - `video_views` integer
      - `profile_views` integer
      - `followers` integer: Followers accumulated at that date, not the day's gain.
      - `followers_gained` integer: Followers gained that day.
    - `by_network` object[]
      - `social_network` string
      - `publications` integer
      - `metrics` NormalizedMetrics: Metrics translated to a **common vocabulary** shared by every network, which is what makes two networks comparable and summable (each network names them differently: `page_post_engagements`, `total_interactions`, `views`…). **A missing key means the network does not publish that metric** — it is never an implicit zero. A key present with value `0` means it was measured and came out zero. Never default a missing key to 0 when displaying it. `engagement` is the network's own total when it provides one, and otherwise the sum of likes, comments, shares, saves and clicks. Video views are deliberately excluded from it: a view is not an interaction.
        - `impressions` integer: Times the content was shown (not unique)
        - `reach` integer: Unique users reached
        - `engagement` integer: Total interactions
        - `likes` integer
        - `comments` integer
        - `shares` integer
        - `saves` integer
        - `clicks` integer
        - `video_views` integer
        - `profile_views` integer
        - `followers` integer: Followers accumulated at that date, not the day's gain.
        - `followers_gained` integer: Followers gained that day.
  - `publications` Publication[]: The requested page. Each publication carries its last known `metrics`, `engagement_base` and `stats_updated_date`; a publication that has not been measured yet has none of them.
    - `_id` string, required
    - `id_organization` string, required
    - `id_account` union, required: The account the publication goes out through. **It is not always the same shape.** The single-publication operations (create, read, retry) return the account already resolved; the listing and the update return its identifier as a string. Check before using it.
      - string
      - Account: A social account connected to an organization. On `discord`, on `telegram` and on `slack` an account is a **channel**, not a profile: publishing to two Discord channels of the same server — or to two Telegram channels of the same brand, or to `#anuncios` and `#general` of the same Slack workspace — costs two accounts of the plan. On `linkedin` one authorization brings two kinds of account: the **personal profile** of whoever authorizes and each **page** they manage. Both publish and both have statistics, but only pages have a comment inbox: LinkedIn does not let any app read the comments on a member's posts, so on a profile every comment endpoint returns error `2600`. `extra_data.is_personal_profile` tells them apart. `error_code` other than `0` means the connection is broken — an expired token, a permission taken away — and the account has to be connected again. On `telegram` nothing expires, because there is no account token: what breaks the connection is the bot being removed from the channel or losing its permission to post there (error 968). On `slack` the bot token does not expire either: what breaks it is the app being removed from the channel (error 980) or the channel being archived or deleted (error 985).
        - `_id` string, required
        - `id_organization` string, required
        - `id_client` string, required: The client the organization hangs from. Denormalized here for the plan checks.
        - `name` string, required: Display name of the profile, page or channel.
        - `username` string: The handle, when the network has one. Absent on the networks that do not (a Discord channel, a WhatsApp number, a Google Business listing) and on a **private** Telegram channel, which has no `@name` at all — only public ones do. That is also why a private channel's publications come back with no `url`.
        - `social_network` 'facebook' | 'instagram' | 'linkedin' | 'tiktok' | 'twitter' | 'whatsapp' | 'youtube' | 'google_business' | 'bluesky' | 'discord' | 'telegram' | 'threads' | 'slack' | 'pinterest', required: A social network supported by PlanVortex. **This list grows.** Treat it as an open enumeration: a client that rejects an unknown value breaks the day a network is added, which happens several times a year. Not every network does everything — ask `GET /social_capabilities`.
        - `creation_date` string, date-time, required
        - `error_code` integer, required: `0` is a healthy account. Anything else is a PlanVortex error code explaining why the connection stopped working; the account keeps its data but cannot be used until it is reconnected.
        - `image` string: Avatar URL as the network publishes it. Empty string when there is none.
        - `followers_count` integer: Followers the network reports. Absent on an account that has never been measured. On `telegram` and on `slack` it is the channel's member count, and it is the **only** audience figure either network publishes: there are no views, no impressions and no reach anywhere in the Bot API nor in the Slack Web API.
        - `next_stats_update` string, date-time: When the collector will ask the network for this account's stats again.
        - `next_comments_update` string, date-time: When the collector will read this account's comments again. Only on the networks whose comments hang off the account and not off a publication — today, `google_business`, whose reviews belong to the listing.
        - `private_message_link` string: A link that opens a private chat with this account (`m.me`, `ig.me`, `wa.me`). Absent on every other network.
        - `extra_data` object: What only makes sense on this account's network. Only the keys documented here are part of the contract: anything else in the object can change without notice.
          - `is_personal_profile` boolean: `linkedin` only. `true` on the personal profile of whoever authorized, `false` on a page. A profile publishes and has statistics but no comment inbox (error `2600`). Pages connected before personal profiles existed do not carry it, so treat absent as a page.
    - `social_network` 'facebook' | 'instagram' | 'linkedin' | 'tiktok' | 'twitter' | 'whatsapp' | 'youtube' | 'google_business' | 'bluesky' | 'discord' | 'telegram' | 'threads' | 'slack' | 'pinterest', required: A social network supported by PlanVortex. **This list grows.** Treat it as an open enumeration: a client that rejects an unknown value breaks the day a network is added, which happens several times a year. Not every network does everything — ask `GET /social_capabilities`.
    - `name` string: Internal name. Never shown on the social network.
    - `text` string
    - `title` string: Only the networks that have a title field use it.
    - `destination` PublicationDestination: **Where inside the account** a publication goes, on the networks where choosing the account is not yet choosing the destination. Today that is `pinterest` alone, where it is the board the pin is saved to. Ask `destinations` in `GET /social_capabilities` instead of keeping a list of your own, and read the account's boards with `GET /organizations/{id_organization}/accounts/{id_account}/destinations`. **On Pinterest it is required.** A publication created without it is created in state `withErrors` with `publication_errors[].code = 987` and the background job will not attempt it — the same treatment as a YouTube video with no title, and for the same reason: a scheduled publication that dies at 3 a.m. over a board that was missing from the start is a failure that could have been told to the person while they were still there. **On every other network the field is deleted, not rejected.** A `destination` on a Facebook publication would be a field that does nothing, and giving it back in the response would be worse than dropping it. `name` and `section_name` are labels for display ("Recipes > Desserts"). The server never checks them and nothing is decided by them: what publishes is `id`.
      - `id` string: The board id. **Always a string**, never a number: Pinterest's ids are long integers, so a client that sends one as a JSON number has already lost digits to rounding before the request arrives. That is refused — `publication_errors[].code = 987` with `data.reason = "invalid_id"` — rather than published to some other board. Sending the board's *name* instead of its id fails the same way, which is the likeliest first mistake of an integration.
      - `name` string: The board's label, for display. Decides nothing.
      - `section_id` string: Pinterest only: the section of the board, optional. Absent or empty means the board itself. Same rule as `id` — anything that is not a string of digits is `data.reason = "invalid_section_id"`. A board's sections arrive in the detail of one destination, not in the list of them.
      - `section_name` string: The section's label, for display. Decides nothing.
    - `link` string: **The destination link**: where the publication takes whoever clicks it. Not to be confused with `url`, which is the link to the publication *on the network* and only exists once it is published. Only the networks that answer `link: true` in `GET /social_capabilities` carry it — today `pinterest` alone, where a pin has a `link` field of its own and sending traffic somewhere is the whole point of publishing there. Putting the URL inside `text` instead leaves it visible and unclickable. **On every other network the field is deleted on save**, like `destination`.
    - `reply_control` 'everyone' | 'accounts_you_follow' | 'mentioned_only' | 'followers_only': **Who can reply** to the publication, on the networks that answer `reply_control: true` in `GET /social_capabilities` (today `threads`). Absent means the network's default, which is anyone. It is set when the publication goes out and cannot be changed afterwards. **On every other network the field is deleted on save**, like `link`.
    - `visibility` 'public' | 'unlisted' | 'friends' | 'followers' | 'private': **Who can see the publication** when it goes out, on the networks that answer `visibility: true` in `GET /social_capabilities` (today `youtube` and `tiktok`). Absent means `public`. It is set when the publication goes out. `unlisted` exists only on YouTube; `friends` and `followers` only on TikTok. **On every other network the field is deleted on save**, like `reply_control`.
    - `made_for_kids` boolean: Whether the video is **made for kids** (COPPA), on the networks that answer `made_for_kids: true` in `GET /social_capabilities` (today `youtube`). Absent means `false`. **On every other network the field is deleted on save**, like `reply_control`.
    - `interactions` PublicationInteractions: What the audience can do with a publication. `true` allows it; a missing key is the network's default.
      - `comments` boolean: The audience can comment.
      - `duet` boolean: The audience can make a Duet with the video. Ignored on a photo post.
      - `stitch` boolean: The audience can make a Stitch with the video. Ignored on a photo post.
    - `commercial_content` PublicationCommercialContent: Whether a publication is commercial content. Both `false` is the same as omitting it.
      - `your_brand` boolean: It promotes the creator or their own business. TikTok labels it "Promotional content".
      - `branded_content` boolean: It is a paid partnership promoting a third party. TikTok labels it "Paid partnership", and it cannot be private.
    - `publication_type` 'profile' | 'page' | 'group' | 'reels' | 'stories' | 'message', required
    - `state` 'ready' | 'withErrors' | 'sended' | 'draft' | 'publishing', required: `draft` is never sent; `ready` is scheduled; `publishing` is in the network's hands right now; `sended` went out; `withErrors` failed and carries the reason in `publication_errors`. `publishing` usually lasts a few seconds, but it can last **up to 10 minutes** when the network is still processing a video (today, Instagram). Then `pending_publish` is present: wait and read the publication again, do not retry it.
    - `pending_publish` PublicationPending: Present only while the network is still **processing** what was sent, and PlanVortex is waiting to publish it. Today that happens on Instagram alone, when Meta takes longer than the ~30 seconds the request waits to process the media. In practice, that means videos. The publication stays in state `publishing`, and the background job asks Instagram again about once a minute until it goes out (`sended`) or fails (`withErrors`). It gives up 10 minutes after the media was sent to Instagram, and then the publication ends in `withErrors` with code 999. The object disappears as soon as the publication is resolved. **Wait for it. Do not retry it and do not create it again**: it has not failed, and a second publication would put the same video out twice. There is no webhook for the outcome: read the publication again after `next_check`. While this object is present the publication **cannot be updated** (error 921), for the same reason.
      - `next_check` string, date-time: When PlanVortex will ask the network again. A good moment to read the publication again: asking before it changes nothing.
      - `deadline` string, date-time: When PlanVortex stops waiting. If the network has not finished by then, the publication ends in `withErrors` (999 on Instagram).
      - `temp_keys` string[]: PlanVortex's own bookkeeping: the temporary cropped files the network may still be downloading. Do not rely on it.
      - `data` object: PlanVortex's own bookkeeping to resume the publication (on Instagram, the ids of Meta's containers). Its content may change without notice.
    - `files` Upload[], required: The attached files, **already resolved**: every read and write path returns full uploads, not identifiers. Identifiers are what you SEND (see `PublicationInput.files`).
      - `_id` string, required
      - `id_organization` string, required
      - `name` string, required
      - `file_type` 'video' | 'image', required
      - `file_format` 'mp4' | 'jpeg' | 'gif' | 'png' | 'jpg', required: The format of the stored bytes. `heic`/`heif` are accepted at the door — that is what an iPhone produces — but never stored: they are converted to JPEG while being ingested, so an upload never comes back with one.
      - `file_properties` FileProperties, required: Measurements taken when the file was ingested. Always present on an upload.
        - `width` integer, required: `0` on a file whose dimensions could not be read.
        - `height` integer, required
        - `duration` integer, required: Seconds. `0` on an image.
        - `size_in_bytes` integer, required
        - `aspect_ratio` object, required: Width divided by height, as a number and as a person writes it.
          - `value` number, required
          - `text` string, required
        - `allowed_social_networks` string[], required: The networks whose accepted crops this file's ratio matches, with a 0.1 margin. It is about the RATIO only: a network listed here can still reject the file for its duration or its weight (see `GET /social_limits`).
      - `file_externals` object[], required: Where this file ended up on each network it has been published to. Some networks keep their own copy and give it an identifier that is reused instead of uploading the bytes again.
        - `social_network` 'facebook' | 'instagram' | 'linkedin' | 'tiktok' | 'twitter' | 'whatsapp' | 'youtube' | 'google_business' | 'bluesky' | 'discord' | 'telegram' | 'threads' | 'slack' | 'pinterest', required: A social network supported by PlanVortex. **This list grows.** Treat it as an open enumeration: a client that rejects an unknown value breaks the day a network is added, which happens several times a year. Not every network does everything — ask `GET /social_capabilities`.
        - `external_identifier` string, required
        - `external_url` string, required
      - `cover_image` Upload (recursive)
      - `cover_offset` integer: Point of the video, in milliseconds, used as the cover frame.
      - `ai_generated` AiImageOrigin: Where an AI-generated image came from. **Only images a model generated carry it**: a photo the organization uploaded, a product photo pulled from a connected shop or an image imported from an integration never does. It is set when the image is generated and cannot be edited. Use it to show your own AI label: under article 50(4) of the EU AI Act, telling the audience that an image is AI-generated is the job of whoever publishes it. Images made with the default model also carry a signed C2PA manifest, IPTC metadata and a SynthID watermark in the file itself. PlanVortex keeps the IPTC marker when it crops or recompresses an image for a network; the C2PA signature does not survive a change of pixels.
        - `provider` string, required: The AI provider that answered: `openrouter` unless the client brought its own key.
        - `model` string, required: The model, as it was requested, for instance `google/gemini-3.1-flash-image`.
        - `generated_at` string, date-time, required
        - `id_ai_plan` string: The AI plan that generated it.
      - `is_temporal` boolean, required: `true` on a file the platform created for itself — a crop made to fit a network's aspect ratio. Temporary files are not part of the library listing.
      - `public_path` string, uri, required: Download URL. IMPORTANT: it is a TEMPORARY, signed URL, not a permanent link. It expires (24 hours by default) and must not be stored or shared: ask for the upload again to get a valid one. The URL stays byte-identical within the same hour, so it can be cached for that long.
      - `creation_date` string, date-time, required
    - `publish_date` string, date-time
    - `creation_date` string, date-time, required
    - `publication_errors` object[], required: Why the publication failed, one entry per problem. **It is an array**, and it is empty on a publication that has not failed. For a scheduled X (Twitter) publication that runs out of credits at publish time, `code` is 940 and `data` is `{ used, limit }`; the publication stays in state `withErrors`. No webhook announces it: read the publication to find out. On Instagram, two codes tell you whether a retry makes sense. **998**: Meta rejected the media while processing it (a codec, a duration, a URL it could not download). `data` carries `container_id`, `status_code` and, when Meta gives one, its reason in `status`. Retrying the same file fails the same way: change it first. **999**: Meta had not finished processing the media 10 minutes after it was sent. `data` carries `container_ids` and `minutes`. The file is not the problem, and a retry usually works.
      - `code` integer, required
      - `message` string, required
      - `data` object
    - `publication_warnings` object[]: What still has to be done **by hand on the network** for a publication that DID go out. Same shape as `publication_errors`, and an empty array when there is nothing to do. It is a separate field because these are not failures: the publication stays in state `sended` and must **not** be retried, because retrying would publish it twice. Like `publication_errors`, it belongs to the last attempt. Today there is one code. **2400** (YouTube): YouTube accepted the upload but did not make the video public, so YouTube Studio shows it as a draft or private until someone sets its audience and visibility there. The upload does not fail when this happens; PlanVortex reads the video back right after uploading it to find out. `data` carries `requested_privacy_status`, `privacy_status` (what YouTube actually left), `upload_status`, `made_for_kids` and `studio_url`, the YouTube Studio page where it is finished.
      - `code` integer, required
      - `message` string, required
      - `data` object
    - `retries` integer, required: Manual retries already spent on a failed publication, against the `max_retries` published by `GET /publication_limits`. Only `POST .../retry` increases it; updating the publication resets it to 0.
    - `extra_data` object: What one network needs to remember about **this** publication and that has no common field. Absent on a publication whose network needs nothing, which is almost all of them. Today only `telegram` writes here, and only `telegram_message_ids`: the ids of every message an album turned into, because deleting the album means deleting all of them.
    - `external_identifier` string: The network's own identifier, once published. On `telegram` an album is one publication that is **several messages**, and this holds the first one; the rest travel in `extra_data.telegram_message_ids`.
    - `url` string: Link to the publication on the network, when there is one. A **private** Telegram channel has no public URL, so it comes back empty even though the post went out.
    - `statistics` PublicationStats: Raw, per-network metrics for a publication. Only the fields that belong to the publication's own social network are returned. **An absent field is not a zero.** A field is present only when the network actually reported it; `0` means the network measured zero. This matters most on X (Twitter), where metrics are split into groups with different access levels: `public_metrics` (likes, replys, retwets, quotes, bookmarks, impressions) is always available, while clicks, the pre-computed `engagement` and the video playback quartiles come from X's non-public metrics — only for your own posts, within 30 days of publishing, and only if the app is entitled to them. When they are unavailable they are omitted rather than returned as `0`. Do not default missing fields to zero when displaying them. On `discord` there are only two: `likes` (the reactions on the message) and `comments` (the messages in its thread). There is no impressions figure anywhere in Discord's API, so engagement is computed over the server's member count. On `bluesky` there are no impressions and no reach either — only the public counters — so engagement is computed over followers. On `threads` there are six, and the one that matters is `views`: it is the only one of the four newest networks with something like impressions, so its engagement rate is computed over a real base and not over followers. Sharing arrives split in three — `reposts`, `quotes` and `shares` (outside Threads) — and the three are added into one figure, the same criterion as on X; the breakdown stays in `raw`. A reply is what every other network calls a comment. On `telegram` there are two as well, and **neither of them is asked for**: the Bot API has no method that returns a message's metrics, so `reactions` arrives on its own through the bot and `comments` is counted in PlanVortex's own inbox. There are no impressions, no reach, no views and no forwards to be had anywhere in it, so engagement is computed over followers. On `slack` there is exactly **one**, `reactions`, and the other absences are the informative part: the Web API publishes no impressions, no reach, no views and no clicks for a message, so those keys are missing rather than zero. There is no `comments` either — Slack threads are not read at all (`comments` is `false` in `GET /social_capabilities`). Engagement is computed over the channel's members. On `pinterest` there are seven, and three of them exist nowhere else: `saves` — the gesture the whole network is built on —, `outbound_clicks` (clicks that left the pin towards its `link`, which is the one normalised as the common `clicks`) and `pin_clicks` (clicks that opened the pin inside Pinterest, with no common equivalent). `impressions` is real here, so the engagement rate is computed over it and not over followers. `video_views` only exists on a video pin. And `comments` is the odd one: Pinterest **counts** them and offers no way to read them, so the number is reported while `comments` is `false` in `GET /social_capabilities` — this network has no comment inbox, and it is not a gap waiting to be filled.
      - `likes` integer
      - `loves` integer
      - `wows` integer
      - `hahas` integer
      - `sorrys` integer
      - `angers` integer
      - `impressions` integer
      - `negative_feedback` integer
      - `clicks` integer
      - `page_likes` integer
      - `video_views` integer
      - `comments` integer
      - `follows` integer
      - `profile_activity` integer
      - `profile_visits` integer
      - `shares` integer
      - `reach` integer
      - `saved` integer
      - `saves` integer: Pinterest. Times the pin was saved to a board — the gesture the network is built on. Normalised as `saves` and counted towards engagement. Not the same key as `saved`, which is Instagram's.
      - `outbound_clicks` integer: Pinterest. Clicks that left the pin towards its `link`. This is the one normalised as the common `clicks`.
      - `pin_clicks` integer: Pinterest. Clicks that opened the pin inside Pinterest without leaving it. No common equivalent, so it lives only here.
      - `retwets` integer
      - `replys` integer
      - `quotes` integer
      - `url_link_clicks` integer: X (Twitter). Clicks on links in the post. Comes from X's non-public metrics: omitted when unavailable.
      - `user_profile_clicks` integer: X (Twitter). Clicks on the author's profile from the post. Comes from X's non-public metrics: omitted when unavailable.
      - `bookmarks` integer: X (Twitter). Times the post was saved to bookmarks. Always available (`public_metrics`). Normalised as `saves` and counted towards engagement.
      - `engagement` integer: Total interactions. LinkedIn reports it directly. On X it comes from the non-public metrics and, when present, takes precedence over the sum of the individual interactions — it includes interactions the API does not break down.
      - `playback_0_count` integer: X (Twitter). Video playbacks that reached 0% — i.e. started. Comes from X's non-public metrics: omitted when unavailable.
      - `playback_25_count` integer: X (Twitter). Video playbacks that reached 25%. Comes from X's non-public metrics: omitted when unavailable.
      - `playback_50_count` integer: X (Twitter). Video playbacks that reached 50%. Comes from X's non-public metrics: omitted when unavailable.
      - `playback_75_count` integer: X (Twitter). Video playbacks that reached 75%. Comes from X's non-public metrics: omitted when unavailable.
      - `playback_100_count` integer: X (Twitter). Video playbacks that reached 100%. Comes from X's non-public metrics: omitted when unavailable.
      - `share` integer
      - `shareMentions` integer
      - `views` integer
      - `reactions` integer: Telegram, Slack and Pinterest. Every reaction on the post, all emoji together, normalised as `likes`. On `telegram` and `slack` it is the **complete state and not an increment**: it goes down when somebody takes theirs back. On `slack` it is the only metric the network gives.
      - `reactions_by_emoji` object: Telegram. The same total broken down by emoji. Reactions with a custom emoji are grouped under a single key: their identifier means nothing outside the server that created it.
    - `metrics` NormalizedMetrics: Metrics translated to a **common vocabulary** shared by every network, which is what makes two networks comparable and summable (each network names them differently: `page_post_engagements`, `total_interactions`, `views`…). **A missing key means the network does not publish that metric** — it is never an implicit zero. A key present with value `0` means it was measured and came out zero. Never default a missing key to 0 when displaying it. `engagement` is the network's own total when it provides one, and otherwise the sum of likes, comments, shares, saves and clicks. Video views are deliberately excluded from it: a view is not an interaction.
      - `impressions` integer: Times the content was shown (not unique)
      - `reach` integer: Unique users reached
      - `engagement` integer: Total interactions
      - `likes` integer
      - `comments` integer
      - `shares` integer
      - `saves` integer
      - `clicks` integer
      - `video_views` integer
      - `profile_views` integer
      - `followers` integer: Followers accumulated at that date, not the day's gain.
      - `followers_gained` integer: Followers gained that day.
    - `engagement_base` 'reach' | 'impressions' | 'followers': What this publication's engagement rate is divided by.
    - `stats_updated_date` string, date-time
    - `next_stats_update` string, date-time
    - `id_integration` string: The integration this publication came from — an RSS feed, for instance. Set when it is created and never changed afterwards.
    - `ai_generated` PublicationAiGenerated: Which part of a publication was generated by AI. **Absent means nothing was.** Use it to show your own AI label: under article 50(4) of the EU AI Act, telling the audience is the job of whoever publishes.
      - `text` boolean, required: The text was written by the AI planner. Set when the draft is created and never changed afterwards, not even if the text is rewritten: it says where the text came from, not who reviewed it.
      - `image` boolean, required: At least one of the files carries `ai_generated`. Recomputed every time the publication is saved, so replacing an AI image with a photo of your own turns it off.
  - `total` integer: Publications matching the filters, for paging

## Other responses

- `400`: The request failed. The body carries the PlanVortex error code in `code`: | Code | Meaning | | --- | --- | | `1101` | Invalid organization | | `1000` | Invalid `from_date` or `metric` | | `1001` | Invalid `to_date` | | `1003` | Range longer than 366 days. Response data: { max_days } | | `523` | Invalid application |
- `500`: Unhandled error by the server

---

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