{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://socialcrawl.dev/schemas/post.json",
  "title": "SocialCrawl Post",
  "description": "Canonical unified post object returned by every SocialCrawl endpoint with the Post/PostList archetype. Nullability is expressed via JSON Schema 2020-12 type arrays. Generated from the Zod schema in packages/social-api/src/schemas/canonical.ts.",
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "description": "Platform-specific post ID (always a string; numeric upstream IDs are stringified)"
    },
    "url": {
      "type": [
        "string",
        "null"
      ],
      "description": "Direct URL to the post on the source platform"
    },
    "content": {
      "type": "object",
      "description": "Post content fields (text, media, thumbnail, duration)",
      "properties": {
        "text": {
          "type": [
            "string",
            "null"
          ],
          "description": "Post caption, description, or text content"
        },
        "media_urls": {
          "type": [
            "string",
            "array",
            "null"
          ],
          "description": "URL(s) of the primary media. Single string for video/photo posts; array of strings for carousels.",
          "items": {
            "type": "string",
            "description": "URL(s) of the primary media. Single string for video/photo posts; array of strings for carousels."
          }
        },
        "thumbnail_url": {
          "type": [
            "string",
            "null"
          ],
          "description": "URL to thumbnail image"
        },
        "duration_seconds": {
          "type": [
            "number",
            "null"
          ],
          "description": "Video/clip duration in seconds (null for non-video posts)"
        }
      }
    },
    "author": {
      "type": "object",
      "description": "Subset of Author fields for the post creator",
      "properties": {
        "username": {
          "type": [
            "string",
            "null"
          ],
          "description": "Author username"
        },
        "display_name": {
          "type": [
            "string",
            "null"
          ],
          "description": "Author display name"
        },
        "avatar_url": {
          "type": [
            "string",
            "null"
          ],
          "description": "URL to author profile picture"
        },
        "verified": {
          "type": [
            "boolean",
            "null"
          ],
          "description": "Whether the author account is verified"
        }
      }
    },
    "engagement": {
      "type": "object",
      "description": "Engagement counts",
      "properties": {
        "views": {
          "type": [
            "number",
            "null"
          ],
          "description": "View count (if available). For Instagram video/reels this is the play count: the headline 'Views' the IG app shows. Instagram removed per-post play counts from its public web pages in August 2026, so `GET /v1/instagram/post` now fills the count automatically from a second source within the same call: videos and reels keep a numeric `views` with no extra endpoint or workaround. Note: since mid-July 2026 Instagram's play count is Instagram-only. It no longer includes Facebook crosspost views (Meta-side change; the app UI changed identically). For combined reach, also fetch the Facebook crosspost via `/v1/facebook/post`."
        },
        "likes": {
          "type": [
            "number",
            "null"
          ],
          "description": "Like / reaction count"
        },
        "comments": {
          "type": [
            "number",
            "null"
          ],
          "description": "Comment count. Note: on `GET /v1/instagram/post` this is Instagram's `edge_media_to_parent_comment.count` (top-level comments); the IG list endpoints report the mobile `comment_count` total (includes replies). Both land on this same field."
        },
        "shares": {
          "type": [
            "number",
            "null"
          ],
          "description": "Share / repost / retweet count. On Instagram this is `reshare_count` (the paper-plane Share / Send count) and it exists only on Instagram's mobile surface, so it is returned by `/v1/instagram/post/stats`, `/v1/instagram/profile/reels/full`, and `/v1/instagram/profile/posts/full`. The web-sourced Instagram list and search endpoints return `null` (never fabricated)."
        },
        "saves": {
          "type": [
            "number",
            "null"
          ],
          "description": "Save / bookmark count. `null` on Instagram: the save count is platform-private and Instagram exposes no numeric save metric on any surface (never fabricated)."
        }
      }
    },
    "flags": {
      "type": "object",
      "description": "Boolean flags on the post",
      "properties": {
        "nsfw": {
          "type": [
            "boolean",
            "null"
          ],
          "description": "NSFW flag (null when platform does not surface)"
        },
        "spoiler": {
          "type": [
            "boolean",
            "null"
          ],
          "description": "Spoiler flag (null when platform does not surface)"
        },
        "pinned": {
          "type": [
            "boolean",
            "null"
          ],
          "description": "Pinned-to-profile flag (null when platform does not surface)"
        },
        "deleted": {
          "type": "boolean",
          "description": "Whether the post is tombstoned (always present)"
        },
        "likes_hidden": {
          "type": [
            "boolean",
            "null"
          ],
          "description": "Present and `true` when the creator has hidden the like count. `engagement.likes` is `null` in that case (never the platform's decoy preview number). Absent on normal posts."
        },
        "comments_hidden": {
          "type": [
            "boolean",
            "null"
          ],
          "description": "Present and `true` when the upstream reports the comment count as hidden. `engagement.comments` is `null` in that case. Absent on normal posts."
        },
        "shares_hidden": {
          "type": [
            "boolean",
            "null"
          ],
          "description": "Boolean at post.flags.shares_hidden"
        },
        "views_hidden": {
          "type": [
            "boolean",
            "null"
          ],
          "description": "Boolean at post.flags.views_hidden"
        },
        "saves_hidden": {
          "type": [
            "boolean",
            "null"
          ],
          "description": "Boolean at post.flags.saves_hidden"
        }
      }
    },
    "published_at": {
      "type": [
        "string",
        "number",
        "null"
      ],
      "description": "Post creation timestamp as an ISO 8601 UTC string. When the upstream sent a Unix epoch it is converted here and the raw epoch is preserved under `post.ext.published_at_epoch` for one deprecation cycle."
    },
    "ext": {
      "type": [
        "object",
        "null"
      ],
      "description": "Platform-specific passthrough fields for cross-endpoint chains (present only on platforms that surface them)",
      "properties": {
        "music_id": {
          "type": [
            "string",
            "null"
          ],
          "description": "TikTok music/clip id (exact string): pass to `/v1/tiktok/song/videos?clipId=` to find videos using the same sound"
        },
        "matched_song_id": {
          "type": [
            "string",
            "null"
          ],
          "description": "TikTok only. The catalogue song TikTok matched the video's sound to, as an exact digit string. An official clip and the original sounds creators upload with it have different `music_id` values and the same `matched_song_id`, so group rows on it to count every use of one song. It is not a clipId: `/v1/tiktok/song` and `/v1/tiktok/song/videos` do not accept it. `/v1/tiktok/search/music` carries it on each sound, so a search on the song's title and artist, kept to the sounds with the same `matched_song_id`, gives the song's official sounds and their `video_count`. Absent when TikTok matched the sound to no song, and on rows served from a source that does not report the match."
        },
        "matched_song_title": {
          "type": [
            "string",
            "null"
          ],
          "description": "TikTok only. The title of the song in `matched_song_id`. Absent when there is no matched song."
        },
        "matched_song_artist": {
          "type": [
            "string",
            "null"
          ],
          "description": "TikTok only. The artist of the song in `matched_song_id`, as TikTok's catalogue names it. Absent when there is no matched song."
        },
        "music_title": {
          "type": [
            "string",
            "null"
          ],
          "description": "TikTok only. The title of the video's own sound (`music_id`). An uploaded sound is titled like `original sound - <handle>`; the song it plays, when TikTok recognised one, is `matched_song_title`."
        },
        "music_artist": {
          "type": [
            "string",
            "null"
          ],
          "description": "TikTok only. The artist TikTok shows for the video's own sound. On an original sound this is the uploader's display name, not the song's artist; use `matched_song_artist` for that."
        },
        "music_is_original": {
          "type": [
            "boolean",
            "null"
          ],
          "description": "TikTok only. `true` when the video's sound is an original sound a creator uploaded, `false` when it is a catalogue track. Absent when the source does not say."
        },
        "author_id": {
          "type": [
            "string",
            "null"
          ],
          "description": "The creator's platform-native numeric user id. On TikTok search and list items, pass to `/v1/tiktok/profile?user_id=` for the creator's current follower count (survives username changes). On Instagram `/v1/instagram/search/reels`, `/v1/instagram/search/hashtag` and `/v1/instagram/location/posts` items it is present on every row, accepted by `/v1/instagram/profile/country?user_id=` and `/v1/instagram/basic-profile?userId=`. On Facebook it appears when the upstream exposed a numeric actor id and no real handle."
        },
        "author_url": {
          "type": [
            "string",
            "null"
          ],
          "description": "The posting account's profile URL, so a matching post can be followed to its account. Present on `/v1/facebook/search/posts` rows: the vanity or `/people/<name>/<id>/` URL when Facebook sends one, otherwise `https://www.facebook.com/profile.php?id=<numeric id>` built from the account id. Pass it as `url` to `/v1/facebook/profile` (or to `/v1/prism/profiles` with `platform: facebook`). Null when the row names neither an account URL nor a numeric account id. Absent on other endpoints."
        },
        "author_followers": {
          "type": [
            "number",
            "null"
          ],
          "description": "The creator's follower count as embedded in the search payload itself, when the search source happens to carry one. On Instagram `search/reels` it is null on a plain call, because Instagram stopped sending follower counts in its search payload in August 2026; send `include=creator` (or `country`) and it is filled from the creator's profile, 2 credits per creator looked up. It stays null when that lookup does not resolve."
        },
        "author_following": {
          "type": [
            "number",
            "null"
          ],
          "description": "Numeric at post.ext.author_following"
        },
        "author_posts_count": {
          "type": [
            "number",
            "null"
          ],
          "description": "Numeric at post.ext.author_posts_count"
        },
        "author_location": {
          "type": [
            "string",
            "null"
          ],
          "description": "The free text the author wrote in their profile's location field, exactly as written (for example `Johannesburg, South Africa`). It is not a country code and is not checked; discover reads a country from it into `geo.country` (`source: author_location`). Present on X tweet and reply rows; null or absent when the author left it blank."
        },
        "in_reply_to_id": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at post.ext.in_reply_to_id"
        },
        "in_reply_to_username": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at post.ext.in_reply_to_username"
        },
        "conversation_id": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at post.ext.conversation_id"
        },
        "author_country": {
          "type": [
            "string",
            "null"
          ],
          "description": "The creator's country. On Instagram it is the country the creator declares on the About this account panel, as a country name (for example `Spain`), and it is not where the post was filmed. It is present on `/v1/instagram/search/reels` when the request sends `include=creator`, `include=country` or `country`, and on `/v1/instagram/search/hashtag` and `/v1/instagram/location/posts` when it sends `include=country`, filled by looking the creator up. On YouTube rows joined with `include=channel` it is the channel's own country as an ISO 3166-1 alpha-2 code (for example `ZA`). Null when the platform publishes no country for the creator or the lookup did not resolve, and nothing is guessed; absent on a plain call."
        },
        "author_public_email": {
          "type": [
            "string",
            "null"
          ],
          "description": "The public contact email on the creator's profile. Present only on `/v1/instagram/search/reels` when the request sends `include=creator`, filled by looking the creator up. Null when the creator lists no public email or the lookup did not resolve; absent on a plain call."
        },
        "author_public_phone": {
          "type": [
            "string",
            "null"
          ],
          "description": "The public contact phone number on the creator's profile. Present only on `/v1/instagram/search/reels` when the request sends `include=creator`, filled by looking the creator up. Null when the creator lists no public phone or the lookup did not resolve; absent on a plain call."
        },
        "download_count": {
          "type": [
            "number",
            "null"
          ],
          "description": "How many times the video has been saved to a device. TikTok only, and distinct from `engagement.saves`: a save keeps the video in a private collection on the platform, a download takes a copy off it, and TikTok counts the two separately. Present on TikTok post, search and post-list responses; absent everywhere else."
        },
        "region": {
          "type": [
            "string",
            "null"
          ],
          "description": "TikTok only: the country TikTok registers the video to, as an ISO 3166-1 alpha-2 code (normally the creator's account country when they posted). It is not the viewer's country, not the `region` you requested, and not a language. On `/v1/tiktok/trending`, `/v1/tiktok/search`, `/v1/tiktok/search/top`, `/v1/tiktok/profile/videos`, `/v1/tiktok/search/hashtag` and `/v1/tiktok/song/videos` it is how you tell which rows are from a given country: filter on it when you need only that country. A row whose source omits the country stays null. The `region` request parameter still only sets the proxy."
        },
        "subreddit": {
          "type": [
            "string",
            "null"
          ],
          "description": "Reddit subreddit name (search/list items): pass to `/v1/reddit/subreddit/details?subreddit=`"
        },
        "title": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at post.ext.title"
        },
        "selftext": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at post.ext.selftext"
        },
        "link_domain": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at post.ext.link_domain"
        },
        "upvote_ratio": {
          "type": [
            "number",
            "null"
          ],
          "description": "Reddit only: the share of a post's votes that are upvotes, as a fraction from 0 to 1 and usually close to 1 (six live rows measured 1, 1, 1, 0.86, 0.75, 1). Fractional despite the integer type this schema emits for every numeric leaf, the same caveat as `author.ext.average_rating`. Populated on `/v1/reddit/search` rows whose source carries it, and null everywhere else on Reddit including `/v1/reddit/post` and `/v1/reddit/subreddit`."
        },
        "flair": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at post.ext.flair"
        },
        "content_language": {
          "type": [
            "string",
            "null"
          ],
          "description": "The language the platform itself tags the post with, when it sends one: Reddit's own language tag on Reddit lanes, and TikTok's caption-language tag (for example `de`) on `/v1/tiktok/trending?feed=local`. Null when the platform could not tell (Reddit `und`, TikTok `un`, common on short or emoji-only captions); absent where the source sends no tag. Never inferred from the text by us: that is `post.computed.language`."
        },
        "type": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at post.ext.type"
        },
        "content_type": {
          "type": [
            "string",
            "null"
          ],
          "enum": [
            "video",
            "short",
            "live",
            "channel",
            "playlist",
            null
          ],
          "description": "Leaf at post.ext.content_type"
        },
        "ticker_symbols": {
          "type": [
            "array",
            "null"
          ],
          "description": "Array at post.ext.ticker_symbols",
          "items": {
            "type": "string",
            "description": "String at post.ext.ticker_symbols"
          }
        },
        "video_view_count": {
          "type": [
            "number",
            "null"
          ],
          "description": "Instagram's legacy 3-second-view count. Historical: Instagram stopped serving it in August 2026 (removed from its web post surface along with the web play count), so fresh `/v1/instagram/post` lookups no longer carry it and no source can restore it. Use `engagement.views` (the play count) instead."
        },
        "ig_play_count": {
          "type": [
            "number",
            "null"
          ],
          "description": "Instagram-only play count (`ig_play_count`). Since mid-July 2026 Instagram's headline play count (`engagement.views`) no longer includes Facebook crosspost views and equals this value; it is surfaced explicitly so you can tell the Instagram-only figure apart and detect any future re-divergence. For combined Instagram + Facebook reach, also fetch the Facebook crosspost via `/v1/facebook/post`. Present on `/v1/instagram/post/stats` and, since the August 2026 views fix, on `/v1/instagram/post` for video posts."
        },
        "repost_count": {
          "type": [
            "number",
            "null"
          ],
          "description": "Numeric at post.ext.repost_count"
        },
        "media_type": {
          "type": [
            "string",
            "null"
          ],
          "enum": [
            "image",
            "video",
            "carousel",
            null
          ],
          "description": "Leaf at post.ext.media_type"
        },
        "text_truncated": {
          "type": [
            "boolean",
            "null"
          ],
          "description": "Boolean at post.ext.text_truncated"
        },
        "ip_location": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at post.ext.ip_location"
        },
        "carousel_count": {
          "type": [
            "number",
            "null"
          ],
          "description": "Numeric at post.ext.carousel_count"
        },
        "remix_count": {
          "type": [
            "number",
            "null"
          ],
          "description": "Numeric at post.ext.remix_count"
        },
        "audio_cluster_id": {
          "type": [
            "string",
            "null"
          ],
          "description": "Instagram's audio cluster for a reel, as an exact digit string: the id Instagram uses to group different uploads of the same sound. Present on `/v1/instagram/audio/reels` rows and on `/v1/instagram/profile/reels` with `include=stats`; absent on a row when it is not available and on every other surface."
        },
        "facebook_likes": {
          "type": [
            "number",
            "null"
          ],
          "description": "Instagram only: the Facebook cross-post share that was EXCLUDED from `engagement.likes` on this row. A post shared to Facebook arrives from some sources with its Facebook likes already folded into the Instagram count, so the share is subtracted and published here, which keeps `engagement.likes` the Instagram-only figure instagram.com shows on the post. Add the two together for the combined number. Absent when the read carried no Facebook share, and absent on a post whose owner hides the like count, because nothing can be subtracted from a null. A response that excluded a share also carries a note in `data._warnings`."
        },
        "facebook_comments": {
          "type": [
            "number",
            "null"
          ],
          "description": "Instagram only: the Facebook cross-post share that was EXCLUDED from `engagement.comments` on this row, the comment counterpart of `post.ext.facebook_likes`. Each leaf appears only for the count it actually changed, so a post whose likes are hidden but whose comments were reconciled carries this one alone."
        },
        "instagram_views": {
          "type": [
            "number",
            "null"
          ],
          "description": "Instagram only: the Instagram-only play count of a video, the same figure on every Instagram endpoint. `engagement.views` keeps each endpoint's established meaning: on `/v1/instagram/profile/reels`, `/v1/instagram/audio/reels`, `/v1/instagram/search/reels` and `/v1/instagram/search/hashtag` it can include the post's Facebook cross-post views, while on `/v1/instagram/post` and `/v1/instagram/post/stats` it is Instagram-only. Read this leaf when you compare one post across endpoints. Absent on photos and when the source did not report the Instagram-only figure."
        },
        "facebook_views": {
          "type": [
            "number",
            "null"
          ],
          "description": "Instagram only: the Facebook cross-post part of a video's play count, the counterpart of `post.ext.instagram_views`. Present only when the source reported the Facebook part, or a combined total above the Instagram figure. Absent does not mean zero: some sources report the Instagram figure alone even for a cross-posted video."
        },
        "published_at_epoch": {
          "type": [
            "number",
            "null"
          ],
          "description": "Raw Unix epoch for `published_at` (seconds, or milliseconds when the upstream sent millis). Present only when the upstream sent a numeric epoch that was normalised to the ISO 8601 `published_at` string. Kept for one deprecation cycle for integrations pinned to the numeric form."
        },
        "usertags": {
          "type": [
            "array",
            "null"
          ],
          "description": "Array at post.ext.usertags",
          "items": {
            "description": "Leaf at post.ext.usertags"
          }
        },
        "coauthors": {
          "type": [
            "array",
            "null"
          ],
          "description": "Instagram collaborative posts (the native \"Collab\" feature). The full list of co-author accounts on the post, as `{ id, username, full_name, is_verified, profile_pic_url }`. A collab post has ONE producer and appears in every co-author's grid, so `post.author` is whichever account created it, which is not necessarily the profile you queried. The complete set of accounts on a post is `post.author.username` plus every `username` in this array. An empty array means Instagram reports the post as NOT a collab; the field is absent on surfaces that carry no co-author signal, including `/v1/instagram/post` (its web source ships the field permanently empty, so use `/v1/instagram/post/stats` for a single post).",
          "items": {
            "description": "Instagram collaborative posts (the native \"Collab\" feature). The full list of co-author accounts on the post, as `{ id, username, full_name, is_verified, profile_pic_url }`. A collab post has ONE producer and appears in every co-author's grid, so `post.author` is whichever account created it, which is not necessarily the profile you queried. The complete set of accounts on a post is `post.author.username` plus every `username` in this array. An empty array means Instagram reports the post as NOT a collab; the field is absent on surfaces that carry no co-author signal, including `/v1/instagram/post` (its web source ships the field permanently empty, so use `/v1/instagram/post/stats` for a single post)."
          }
        },
        "music": {
          "type": [
            "object",
            "null"
          ],
          "description": "Object at post.ext.music"
        },
        "sponsor_tags": {
          "type": [
            "array",
            "null"
          ],
          "description": "Instagram only: the brand accounts the post tags as its paid partner, as usernames (`[\"rolex\"]`). `[]` on a post with no partner tagged. Present on `/v1/instagram/post/stats`; absent on every other endpoint.",
          "items": {
            "description": "Instagram only: the brand accounts the post tags as its paid partner, as usernames (`[\"rolex\"]`). `[]` on a post with no partner tagged. Present on `/v1/instagram/post/stats`; absent on every other endpoint."
          }
        },
        "sponsored_disclosed": {
          "type": [
            "boolean",
            "null"
          ],
          "description": "The platform's own disclosure that the post is paid or promotional: on Instagram the paid-partnership label (`is_paid_partnership`) or a tagged brand partner, on TikTok the label TikTok shows viewers (\"Paid partnership\", \"Commission paid\", \"Promotional content\"). `false` when the post carries the flag unset, null when it carries none. Present on `/v1/instagram/post/stats`, `/v1/instagram/profile/posts`, `/v1/instagram/profile/reels`, `/v1/instagram/search/reels`, `/v1/tiktok/profile/videos` and `/v1/tiktok/search`; absent on every other endpoint."
        },
        "ai_label": {
          "type": [
            "string",
            "null"
          ],
          "description": "Instagram only: Instagram's AI-content label on the post, as the method Instagram reports: `classifier_score_high` (Instagram detected it, shown as \"Likely made with AI\"), `ai_created` or `ai_edited` (declared or detected as made or edited with AI). Null when the post carries no AI label. Present on `/v1/instagram/post/stats`; absent on every other endpoint."
        },
        "story_mentions": {
          "type": [
            "array",
            "null"
          ],
          "description": "Instagram stories only: the accounts the story mentions with a mention sticker, as usernames in alphabetical order. `[]` when it mentions nobody. Present on `/v1/instagram/stories`; absent on every other endpoint.",
          "items": {
            "type": "string",
            "description": "Instagram stories only: the accounts the story mentions with a mention sticker, as usernames in alphabetical order. `[]` when it mentions nobody. Present on `/v1/instagram/stories`; absent on every other endpoint."
          }
        },
        "story_links": {
          "type": [
            "array",
            "null"
          ],
          "description": "Instagram stories only: where the story's link stickers lead, as URLs in alphabetical order. Instagram wraps each link in its own redirect with a per-view click id; both are removed, so this is the destination itself. `[]` when the story has no link sticker. Present on `/v1/instagram/stories` and `/v1/instagram/highlight/detail`; absent on every other endpoint.",
          "items": {
            "type": "string",
            "description": "Instagram stories only: where the story's link stickers lead, as URLs in alphabetical order. Instagram wraps each link in its own redirect with a per-view click id; both are removed, so this is the destination itself. `[]` when the story has no link sticker. Present on `/v1/instagram/stories` and `/v1/instagram/highlight/detail`; absent on every other endpoint."
          }
        },
        "story_reshared_posts": {
          "type": [
            "array",
            "null"
          ],
          "description": "Instagram stories only: the feed posts the story reshares, as post URLs (`https://www.instagram.com/p/{shortcode}/`, which `/v1/instagram/post` takes). `[]` when it reshares none. Present on `/v1/instagram/stories`; absent on every other endpoint.",
          "items": {
            "type": "string",
            "description": "Instagram stories only: the feed posts the story reshares, as post URLs (`https://www.instagram.com/p/{shortcode}/`, which `/v1/instagram/post` takes). `[]` when it reshares none. Present on `/v1/instagram/stories`; absent on every other endpoint."
          }
        },
        "location": {
          "type": [
            "object",
            "null"
          ],
          "description": "Object at post.ext.location"
        },
        "media": {
          "type": [
            "array",
            "null"
          ],
          "description": "Array at post.ext.media",
          "items": {
            "type": "object",
            "description": "Nested object: post.ext.media",
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "image",
                  "video"
                ],
                "description": "Leaf at post.ext.media.type"
              },
              "url": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "String at post.ext.media.url"
              },
              "width": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Numeric at post.ext.media.width"
              },
              "height": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Numeric at post.ext.media.height"
              }
            }
          }
        },
        "top_comments": {
          "type": [
            "array",
            "null"
          ],
          "description": "Array at post.ext.top_comments",
          "items": {
            "type": "object",
            "description": "Nested object: post.ext.top_comments",
            "properties": {
              "id": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "String at post.ext.top_comments.id"
              },
              "text": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "String at post.ext.top_comments.text"
              },
              "username": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "String at post.ext.top_comments.username"
              },
              "likes": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Numeric at post.ext.top_comments.likes"
              },
              "published_at": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "String at post.ext.top_comments.published_at"
              }
            }
          }
        },
        "hashtags": {
          "type": [
            "array",
            "null"
          ],
          "description": "Array at post.ext.hashtags",
          "items": {
            "type": "string",
            "description": "String at post.ext.hashtags"
          }
        },
        "mentions": {
          "type": [
            "array",
            "null"
          ],
          "description": "Array at post.ext.mentions",
          "items": {
            "type": "string",
            "description": "String at post.ext.mentions"
          }
        },
        "mentioned_user_ids": {
          "type": [
            "array",
            "null"
          ],
          "description": "Array at post.ext.mentioned_user_ids",
          "items": {
            "type": "string",
            "description": "String at post.ext.mentioned_user_ids"
          }
        },
        "quoted_post": {
          "type": [
            "object",
            "null"
          ],
          "description": "Threads quote posts: the post this one quotes, as `{ id, url, author: { id, username }, text, media_urls, thumbnail_url }`. A quote post usually carries no caption or media of its own, and the text and video you see on the post's page belong to this attachment, by a different author. It is returned here rather than merged into `post.content`, so `content.*` always stays the outer post's own content and one account's media is never attributed to another. Absent on posts that are not quote posts."
        },
        "retweeted_post": {
          "type": [
            "object",
            "null"
          ],
          "description": "Object at post.ext.retweeted_post"
        },
        "quote_count": {
          "type": [
            "number",
            "null"
          ],
          "description": "Numeric at post.ext.quote_count"
        },
        "all_media_urls": {
          "type": [
            "array",
            "null"
          ],
          "description": "Array at post.ext.all_media_urls",
          "items": {
            "type": "string",
            "description": "String at post.ext.all_media_urls"
          }
        },
        "reaction_counts": {
          "type": [
            "array",
            "null"
          ],
          "description": "Array at post.ext.reaction_counts",
          "items": {
            "description": "Leaf at post.ext.reaction_counts"
          }
        },
        "share_urn": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at post.ext.share_urn"
        },
        "post_type": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at post.ext.post_type"
        },
        "activity_id": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at post.ext.activity_id"
        },
        "published_at_precision": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at post.ext.published_at_precision"
        },
        "author_urn": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at post.ext.author_urn"
        },
        "author_headline": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at post.ext.author_headline"
        },
        "author_type": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at post.ext.author_type"
        },
        "is_repost_quote": {
          "type": [
            "boolean",
            "null"
          ],
          "description": "Boolean at post.ext.is_repost_quote"
        },
        "article": {
          "type": [
            "object",
            "null"
          ],
          "description": "Object at post.ext.article"
        },
        "reaction_type": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at post.ext.reaction_type"
        },
        "download_media_urls": {
          "type": [
            "array",
            "null"
          ],
          "description": "Array at post.ext.download_media_urls",
          "items": {
            "type": "object",
            "description": "Object at post.ext.download_media_urls"
          }
        },
        "tags": {
          "type": [
            "array",
            "null"
          ],
          "description": "Array at post.ext.tags",
          "items": {
            "type": "string",
            "description": "String at post.ext.tags"
          }
        },
        "categoryId": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at post.ext.categoryId"
        },
        "categoryTitle": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at post.ext.categoryTitle"
        },
        "topicCategories": {
          "type": [
            "array",
            "null"
          ],
          "description": "Array at post.ext.topicCategories",
          "items": {
            "type": "string",
            "description": "String at post.ext.topicCategories"
          }
        },
        "duration": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at post.ext.duration"
        },
        "license": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at post.ext.license"
        },
        "madeForKids": {
          "type": [
            "boolean",
            "null"
          ],
          "description": "Boolean at post.ext.madeForKids"
        },
        "defaultAudioLanguage": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at post.ext.defaultAudioLanguage"
        },
        "hasPaidProductPlacement": {
          "type": [
            "boolean",
            "null"
          ],
          "description": "Boolean at post.ext.hasPaidProductPlacement"
        },
        "caption": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at post.ext.caption"
        },
        "position": {
          "type": [
            "number",
            "null"
          ],
          "description": "Numeric at post.ext.position"
        },
        "playlistId": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at post.ext.playlistId"
        },
        "videoOwnerChannelId": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at post.ext.videoOwnerChannelId"
        },
        "videoPublishedAt": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at post.ext.videoPublishedAt"
        },
        "description": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at post.ext.description"
        },
        "default_language": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at post.ext.default_language"
        },
        "localizations": {
          "type": [
            "object",
            "null"
          ],
          "description": "Object at post.ext.localizations",
          "additionalProperties": {
            "type": "object",
            "description": "Nested object: post.ext.localizations.*",
            "properties": {
              "title": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "String at post.ext.localizations.*.title"
              },
              "description": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "String at post.ext.localizations.*.description"
              }
            }
          }
        },
        "playlist_item_id": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at post.ext.playlist_item_id"
        },
        "playlist_owner_channel_id": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at post.ext.playlist_owner_channel_id"
        },
        "playlist_owner_title": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at post.ext.playlist_owner_title"
        },
        "channel_id": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at post.ext.channel_id"
        },
        "published_label": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at post.ext.published_label"
        },
        "published_precision": {
          "type": [
            "string",
            "null"
          ],
          "enum": [
            "minute",
            "hour",
            "day",
            "week",
            "month",
            "year",
            null
          ],
          "description": "Leaf at post.ext.published_precision"
        },
        "title_maybe_translated": {
          "type": [
            "boolean",
            "null"
          ],
          "description": "Boolean at post.ext.title_maybe_translated"
        },
        "video_count": {
          "type": [
            "number",
            "null"
          ],
          "description": "Numeric at post.ext.video_count"
        },
        "posts_count": {
          "type": [
            "number",
            "null"
          ],
          "description": "`/v1/instagram/hashtag` only: the exact number of public posts Instagram counts under the hashtag. Live: it moves between calls minutes apart."
        },
        "commerce": {
          "type": [
            "object",
            "null"
          ],
          "description": "Object at post.ext.commerce"
        },
        "on_screen_texts": {
          "type": [
            "array",
            "null"
          ],
          "description": "Array at post.ext.on_screen_texts",
          "items": {
            "type": "string",
            "description": "String at post.ext.on_screen_texts"
          }
        },
        "topic_tag": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at post.ext.topic_tag"
        },
        "reshare_count": {
          "type": [
            "number",
            "null"
          ],
          "description": "Numeric at post.ext.reshare_count"
        },
        "dsp_ids": {
          "type": [
            "object",
            "null"
          ],
          "description": "Object at post.ext.dsp_ids",
          "additionalProperties": {
            "type": "string",
            "description": "String at post.ext.dsp_ids.*"
          }
        },
        "topic_tag_id": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at post.ext.topic_tag_id"
        },
        "amazon_shop_lists": {
          "type": [
            "array",
            "null"
          ],
          "description": "Array at post.ext.amazon_shop_lists",
          "items": {
            "type": "object",
            "description": "Object at post.ext.amazon_shop_lists"
          }
        },
        "amazon_shop_trending_picks": {
          "type": [
            "array",
            "null"
          ],
          "description": "Array at post.ext.amazon_shop_trending_picks",
          "items": {
            "type": "object",
            "description": "Object at post.ext.amazon_shop_trending_picks"
          }
        },
        "amazon_shop_curations": {
          "type": [
            "array",
            "null"
          ],
          "description": "Array at post.ext.amazon_shop_curations",
          "items": {
            "type": "object",
            "description": "Object at post.ext.amazon_shop_curations"
          }
        },
        "amazon_shop_socials": {
          "type": [
            "array",
            "null"
          ],
          "description": "Array at post.ext.amazon_shop_socials",
          "items": {
            "type": "string",
            "description": "String at post.ext.amazon_shop_socials"
          }
        },
        "ad": {
          "type": [
            "object",
            "null"
          ],
          "description": "Ad Library envelope (Facebook `adlibrary/ad` and `adlibrary/company/ads` items): page_id, currency, spend, reach_estimate, publisher_platforms, categories, targeted_or_reached_countries, cta_text, cta_type, link_url, is_active, end_date_iso, display_format, title, video_hd_url, video_sd_url. Absent on every non-ad surface."
        },
        "apple_music": {
          "type": [
            "object",
            "null"
          ],
          "description": "Object at post.ext.apple_music"
        },
        "feedback_id": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at post.ext.feedback_id"
        },
        "question_id": {
          "type": [
            "string",
            "null"
          ],
          "description": "Naver KnowledgeiN (`/v1/naver/kin/search`) only: the question's id (Naver's `docId`), read from the row's link. Kin returns one row per answer, so several rows can share it; send `group=question` for one row per question."
        },
        "answer_no": {
          "type": [
            "number",
            "null"
          ],
          "description": "Naver KnowledgeiN only: which answer of the question this row is (Naver's `answerNo`), read from the row's link."
        },
        "answer_nos": {
          "type": [
            "array",
            "null"
          ],
          "description": "Naver KnowledgeiN with `group=question` only: every answer number of the question that was on the page, best-ranked first.",
          "items": {
            "type": "number",
            "description": "Naver KnowledgeiN with `group=question` only: every answer number of the question that was on the page, best-ranked first."
          }
        },
        "published_at_est": {
          "type": [
            "string",
            "null"
          ],
          "description": "Naver KnowledgeiN only: the question's date ESTIMATED from its id (`YYYY-MM-DD`, Korea time), never a published date, so `published_at` stays null. Absent for questions older than the calibration table."
        },
        "date_basis": {
          "type": [
            "string",
            "null"
          ],
          "description": "How `post.ext.published_at_est` was derived. `kin_docid_estimate`: interpolated from the question id against question pages whose posted date was read."
        },
        "event": {
          "type": [
            "object",
            "null"
          ],
          "description": "Object at post.ext.event"
        },
        "trend": {
          "type": [
            "object",
            "null"
          ],
          "description": "Trend-board envelope: what TikTok's own trend board says about the row. On `/v1/tiktok/hashtags/popular`: `rank`, `country_code`, `period_days`, `posts` and `views` for the window (not lifetime totals), `industry` / `industry_id` / `industry_label` for the first board the hashtag was read from (null for the overall board), `boards` listing EVERY board it ranks on as `{ industry, industry_id, industry_label, rank }` in board order (so a hashtag in the top 3 overall and on an industry board shows both), `popularity_curve` as `{ date, value }` with value 0 to 100 relative to the window's peak, and `top_creators`. On `/v1/tiktok/videos/popular`: `rank`, `country_code`, `period_days`, `order_by`, `period_views`, `organic_views`, `engagement_rate`, `six_second_view_through_rate`, `content_tags`. Absent on every non-board surface."
        },
        "updated_at": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at post.ext.updated_at"
        },
        "alt_text": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at post.ext.alt_text"
        },
        "board_name": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at post.ext.board_name"
        },
        "numeric_id": {
          "type": [
            "number",
            "null"
          ],
          "description": "Numeric at post.ext.numeric_id"
        },
        "outbound_url": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at post.ext.outbound_url"
        },
        "group": {
          "type": [
            "object",
            "null"
          ],
          "description": "Object at post.ext.group"
        }
      }
    }
  },
  "additionalProperties": true
}
