{
  "$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": [
            "integer",
            "null"
          ],
          "description": "Like / upvote count"
        },
        "replies": {
          "type": [
            "integer",
            "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)"
        }
      }
    },
    "published_at": {
      "type": [
        "string",
        "integer",
        "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."
        },
        "published_at_epoch": {
          "type": [
            "integer",
            "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`"
        },
        "urn": {
          "type": [
            "string",
            "null"
          ],
          "description": "String at comment.ext.urn"
        },
        "reaction_counts": {
          "type": [
            "array",
            "null"
          ],
          "description": "Array at comment.ext.reaction_counts",
          "items": {
            "type": "string",
            "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"
        },
        "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": {
            "type": "string",
            "description": "Leaf at comment.ext.preview_replies"
          }
        }
      }
    },
    "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
}
