{
  "$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": [
            "integer",
            "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": [
            "integer",
            "null"
          ],
          "description": "View count (if available). For Instagram video/reels this is the play count (`video_play_count`) — the headline 'Views' the IG app shows; the legacy 3-second-view count is preserved separately under `post.ext.video_view_count` on `/v1/instagram/post`."
        },
        "likes": {
          "type": [
            "integer",
            "null"
          ],
          "description": "Like / reaction count"
        },
        "comments": {
          "type": [
            "integer",
            "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": [
            "integer",
            "null"
          ],
          "description": "Share / repost / retweet count"
        },
        "saves": {
          "type": [
            "integer",
            "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",
        "integer",
        "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"
        },
        "subreddit": {
          "type": [
            "string",
            "null"
          ],
          "description": "Reddit subreddit name (search/list items) — pass to `/v1/reddit/subreddit/details?subreddit=`"
        },
        "type": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at post.ext.type"
        },
        "content_type": {
          "type": [
            "string",
            "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": [
            "integer",
            "null"
          ],
          "description": "Instagram reel legacy 3-second-view count. `engagement.views` carries the play count (the headline 'Views' the IG app shows); this preserves the older view count. Present only on `/v1/instagram/post` for video posts."
        },
        "published_at_epoch": {
          "type": [
            "integer",
            "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": {
            "type": "string",
            "description": "Leaf at post.ext.usertags"
          }
        },
        "coauthors": {
          "type": [
            "array",
            "null"
          ],
          "description": "Array at post.ext.coauthors",
          "items": {
            "type": "string",
            "description": "Leaf at post.ext.coauthors"
          }
        },
        "music": {
          "type": [
            "string",
            "null"
          ],
          "description": "Leaf at post.ext.music"
        },
        "sponsor_tags": {
          "type": [
            "array",
            "null"
          ],
          "description": "Array at post.ext.sponsor_tags",
          "items": {
            "type": "string",
            "description": "Leaf at post.ext.sponsor_tags"
          }
        },
        "location": {
          "type": [
            "string",
            "null"
          ],
          "description": "Leaf at post.ext.location"
        },
        "reaction_counts": {
          "type": [
            "array",
            "null"
          ],
          "description": "Array at post.ext.reaction_counts",
          "items": {
            "type": "string",
            "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"
        },
        "download_media_urls": {
          "type": [
            "array",
            "null"
          ],
          "description": "Array at post.ext.download_media_urls",
          "items": {
            "type": "string",
            "description": "Leaf 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": [
            "integer",
            "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"
        },
        "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"
        }
      }
    }
  },
  "additionalProperties": true
}
