{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://socialcrawl.dev/schemas/comment.json",
  "title": "SocialCrawl Comment",
  "description": "Canonical unified comment object returned by every SocialCrawl endpoint with the Comment/CommentList 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 comment ID (always a string)"
    },
    "url": {
      "type": [
        "string",
        "null"
      ],
      "description": "Direct URL to the comment on the source platform"
    },
    "parent_id": {
      "type": [
        "string",
        "null"
      ],
      "description": "Parent comment ID for nested replies, or null for top-level comments"
    },
    "post_id": {
      "type": [
        "string",
        "null"
      ],
      "description": "ID of the post this comment belongs to"
    },
    "text": {
      "type": [
        "string",
        "null"
      ],
      "description": "Comment body. Tombstone sentinels ([deleted] / [removed]) are collapsed to null upstream: customers never see them."
    },
    "author": {
      "type": "object",
      "description": "Subset of Author fields for the comment author",
      "properties": {
        "username": {
          "type": [
            "string",
            "null"
          ],
          "description": "Comment author username (null when tombstoned)"
        },
        "display_name": {
          "type": [
            "string",
            "null"
          ],
          "description": "Comment author display name"
        },
        "avatar_url": {
          "type": [
            "string",
            "null"
          ],
          "description": "URL to comment author profile picture"
        },
        "verified": {
          "type": [
            "boolean",
            "null"
          ],
          "description": "Whether the comment author is verified"
        }
      }
    },
    "engagement": {
      "type": "object",
      "description": "Engagement counts on the comment",
      "properties": {
        "likes": {
          "type": [
            "number",
            "null"
          ],
          "description": "Like / upvote count"
        },
        "replies": {
          "type": [
            "number",
            "null"
          ],
          "description": "Reply / child-comment count. `0` when the upstream structurally reports the count and the comment has no replies; `null` only when the upstream does not surface a reply count at all."
        }
      }
    },
    "flags": {
      "type": "object",
      "description": "Boolean flags on the comment",
      "properties": {
        "pinned": {
          "type": [
            "boolean",
            "null"
          ],
          "description": "Pinned flag (null when platform does not surface)"
        },
        "deleted": {
          "type": "boolean",
          "description": "Whether the comment is tombstoned (always present, even when false)"
        },
        "liked_by_creator": {
          "type": [
            "boolean",
            "null"
          ],
          "description": "`/v1/instagram/post/comments` only: true when the post's creator liked (hearted) the comment, false when not, null when the source that served the call could not tell. Absent on every other endpoint."
        }
      }
    },
    "published_at": {
      "type": [
        "string",
        "number",
        "null"
      ],
      "description": "Comment 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 `comment.ext.published_at_epoch` for one deprecation cycle."
    },
    "ext": {
      "type": [
        "object",
        "null"
      ],
      "description": "Platform-specific passthrough tokens for reply expansion (present only on platforms that surface them)",
      "properties": {
        "replies_token": {
          "type": [
            "string",
            "null"
          ],
          "description": "YouTube reply continuation token: pass to `/v1/youtube/video/comment/replies?continuationToken=`"
        },
        "replies_cursor": {
          "type": [
            "string",
            "null"
          ],
          "description": "Reddit reply-branch continuation cursor. Present when this comment's reply tree is truncated upstream: pass it back as `?cursor=` on `/v1/reddit/post/comments` to load the rest of the branch."
        },
        "depth": {
          "type": [
            "number",
            "null"
          ],
          "description": "Reddit comment nesting depth as the source reports it: 0 on a top-level comment, 1 on a direct reply, and so on. Present when the thread arrived flat and was re-nested into `replies[]`, so you can verify that nesting independently."
        },
        "published_at_epoch": {
          "type": [
            "number",
            "null"
          ],
          "description": "Raw Unix epoch for `published_at` (present only when the upstream sent a numeric epoch that was normalised to the ISO 8601 string)."
        },
        "feedback_id": {
          "type": [
            "string",
            "null"
          ],
          "description": "Facebook comment feedback id: feeds `/v1/facebook/post/comment/replies`"
        },
        "expansion_token": {
          "type": [
            "string",
            "null"
          ],
          "description": "Facebook comment expansion/paginator token: feeds `/v1/facebook/post/comment/replies`"
        },
        "author_id": {
          "type": [
            "string",
            "null"
          ],
          "description": "The commenter's platform-native numeric user id, where the upstream exposes one."
        },
        "ip_location": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at comment.ext.ip_location"
        },
        "urn": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at comment.ext.urn"
        },
        "reaction_counts": {
          "type": [
            "array",
            "null"
          ],
          "description": "Array at comment.ext.reaction_counts",
          "items": {
            "description": "Leaf at comment.ext.reaction_counts"
          }
        },
        "is_edited": {
          "type": [
            "boolean",
            "null"
          ],
          "description": "Boolean at comment.ext.is_edited"
        },
        "previous_replies_token": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at comment.ext.previous_replies_token"
        },
        "author_headline": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at comment.ext.author_headline"
        },
        "updated_at": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at comment.ext.updated_at"
        },
        "author_channel_id": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at comment.ext.author_channel_id"
        },
        "author_url": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at comment.ext.author_url"
        },
        "viewer_rating": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at comment.ext.viewer_rating"
        },
        "text_original": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at comment.ext.text_original"
        },
        "preview_replies": {
          "type": [
            "array",
            "null"
          ],
          "description": "Array at comment.ext.preview_replies",
          "items": {
            "description": "Leaf at comment.ext.preview_replies"
          }
        },
        "lookup": {
          "type": [
            "object",
            "null"
          ],
          "description": "Object at comment.ext.lookup"
        },
        "post_title": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at comment.ext.post_title"
        },
        "post_url": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at comment.ext.post_url"
        },
        "subreddit": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at comment.ext.subreddit"
        },
        "subreddit_subscribers": {
          "type": [
            "number",
            "null"
          ],
          "description": "Numeric at comment.ext.subreddit_subscribers"
        },
        "post_score": {
          "type": [
            "number",
            "null"
          ],
          "description": "Numeric at comment.ext.post_score"
        },
        "post_comment_count": {
          "type": [
            "number",
            "null"
          ],
          "description": "Numeric at comment.ext.post_comment_count"
        },
        "post_author": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at comment.ext.post_author"
        },
        "post_published_at": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at comment.ext.post_published_at"
        },
        "post_flair": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at comment.ext.post_flair"
        },
        "is_submitter": {
          "type": [
            "boolean",
            "null"
          ],
          "description": "Boolean at comment.ext.is_submitter"
        },
        "edited_at": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at comment.ext.edited_at"
        },
        "controversiality": {
          "type": [
            "number",
            "null"
          ],
          "description": "Numeric at comment.ext.controversiality"
        },
        "content_language": {
          "type": [
            "string",
            "null"
          ],
          "description": "ISO 639-1 language tag for this comment, when the source reports one. X writes `zxx` / `und` for no linguistic content; those collapse to null."
        },
        "author_followers": {
          "type": [
            "number",
            "null"
          ],
          "description": "The commenter's follower count as embedded in this row. Snapshot as of whenever the replies source saw the account, not a live figure. On X this is present on every `tweet/replies` row."
        },
        "author_following": {
          "type": [
            "number",
            "null"
          ],
          "description": "The commenter's following count as embedded in this row. Same snapshot caveat as `author_followers`."
        },
        "author_posts_count": {
          "type": [
            "number",
            "null"
          ],
          "description": "The commenter's post count as embedded in this row. Same snapshot caveat as `author_followers`."
        },
        "author_location": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at comment.ext.author_location"
        },
        "quote_count": {
          "type": [
            "number",
            "null"
          ],
          "description": "How many quote-posts this comment has, when the source reports it. On X a reply is a tweet, so this is the same figure `post.ext.quote_count` carries on tweet lanes."
        },
        "views": {
          "type": [
            "number",
            "null"
          ],
          "description": "View count for this comment, when the source reports it. On X a reply is a tweet and the count is public on posts from ~2022 onward."
        },
        "saves": {
          "type": [
            "number",
            "null"
          ],
          "description": "Bookmark / save count for this comment, when the source reports it. On X this is `legacy.bookmark_count`."
        }
      }
    },
    "replies": {
      "type": [
        "array",
        "null"
      ],
      "description": "Nested reply tree: array of child comments, each itself a full Comment object with its own `replies`. Populated on threaded platforms (Reddit); absent on flat-comment platforms, which thread only via `parent_id`.",
      "items": {
        "type": "object",
        "description": "Nested reply tree: array of child comments, each itself a full Comment object with its own `replies`. Populated on threaded platforms (Reddit); absent on flat-comment platforms, which thread only via `parent_id`."
      }
    }
  },
  "additionalProperties": true
}
