{
  "title": "Bluesky Public Posts Scraper - Feeds & Threads",
  "description": "Read public author feeds or supplied posts from the official public API. Empty authors and postUrls use bsky.app. One result is one root-post observation, with optional bounded thread context.",
  "type": "object",
  "schemaVersion": 1,
  "properties": {
    "authors": {
      "title": "Public authors",
      "type": "array",
      "editor": "json",
      "description": "Up to 20 explicit public handles, DIDs or https://bsky.app/profile/<identity> URLs. Each entry can also be {\"value\":\"bsky.app\",\"externalId\":\"crm-123\"}. Empty with no postUrls uses bsky.app. No follower discovery or keyword search.",
      "default": [],
      "prefill": [
        "bsky.app"
      ],
      "sectionCaption": "1 · Choose public sources",
      "sectionDescription": "Authors are processed before supplied posts; empty sources use the documented sample account."
    },
    "postUrls": {
      "title": "Public post URLs or AT-URIs",
      "type": "array",
      "editor": "json",
      "description": "Up to 50 supplied https://bsky.app/profile/<identity>/post/<record> URLs or at://<identity>/app.bsky.feed.post/<record> references. Objects {value,externalId} are accepted. Authors are processed first, so leave authors empty for post-only runs.",
      "default": []
    },
    "maxPosts": {
      "title": "Maximum saved root posts",
      "type": "integer",
      "description": "Global unique root-post selection limit. Feed rows and supplied threads share this limit; author inputs are processed first. Context posts within a row do not add billable rows.",
      "default": 3,
      "minimum": 1,
      "maximum": 200
    },
    "includeReplies": {
      "title": "Include authors’ reply posts",
      "type": "boolean",
      "description": "Include reply posts as root observations in author feeds. Supplied post URLs are always eligible even if they are replies.",
      "default": false,
      "sectionCaption": "2 · Control feed and thread scope",
      "sectionDescription": "Each limit bounds the public view and does not guarantee every reply is visible."
    },
    "includeReposts": {
      "title": "Include reposted posts",
      "type": "boolean",
      "description": "Include third-party posts reposted by a supplied author. requestedBy marks repost provenance. A post selected more than once is saved once with source attribution.",
      "default": false
    },
    "maxPostsPerAuthor": {
      "title": "Posts per author",
      "type": "integer",
      "description": "Maximum accepted observations per supplied author within maxPosts. This is a bounded recent feed, not all historical posts.",
      "default": 25,
      "minimum": 1,
      "maximum": 100
    },
    "maxPagesPerAuthor": {
      "title": "Feed pages per author",
      "type": "integer",
      "description": "Stop after this many API pages per author even when repost/reply filtering yields fewer posts. Pagination caps are recorded in SUMMARY.",
      "default": 3,
      "minimum": 1,
      "maximum": 10
    },
    "includeThread": {
      "title": "Include thread context",
      "type": "boolean",
      "description": "Fetch a bounded public view of parent and reply posts for each root. Hidden, deleted or blocked replies may be absent. An unavailable context fetch leaves a valid feed observation marked incomplete.",
      "default": true
    },
    "threadDepth": {
      "title": "Reply depth",
      "type": "integer",
      "description": "Requested public API reply depth. 0 excludes reply context. This does not guarantee that every reply at the chosen depth is visible.",
      "default": 1,
      "minimum": 0,
      "maximum": 3
    },
    "parentHeight": {
      "title": "Parent context levels",
      "type": "integer",
      "description": "Requested maximum number of ancestor levels. Context output also shares maxContextPosts.",
      "default": 1,
      "minimum": 0,
      "maximum": 5
    },
    "maxContextPosts": {
      "title": "Context posts per result",
      "type": "integer",
      "description": "Maximum visible parent/reply posts retained inside each root row. Intentional truncation is reported. These are context entries, not separate paid results.",
      "default": 10,
      "minimum": 0,
      "maximum": 50
    },
    "comparePrevious": {
      "title": "Compare previous saved observations",
      "type": "boolean",
      "description": "Compare root content hashes and engagement metrics with a named baseline. First observations are labeled first_observation and have no deltas. Unchanged posts are still output and billed. Only saved, complete observations update history. Missing posts/replies never mean deletion.",
      "default": false,
      "sectionCaption": "3 · Optional saved comparisons",
      "sectionDescription": "Root content and metrics are compared; unchanged rows remain in output."
    },
    "baselineKey": {
      "title": "Baseline namespace",
      "type": "string",
      "editor": "textfield",
      "description": "1–80 letters, digits, hyphens or underscores within maydit-bluesky-post-observations-v1. Use one writer per namespace. Snapshots retain URI, content hash, metrics and observation time, not post text.",
      "default": "default"
    },
    "maxRunSeconds": {
      "title": "Run time budget (seconds)",
      "type": "integer",
      "description": "Own deadline in seconds, additionally shortened to 25 seconds before the platform deadline. Partial runs identify failed/unfinished work in SUMMARY.",
      "default": 240,
      "minimum": 30,
      "maximum": 900,
      "sectionCaption": "4 · Run budget",
      "sectionDescription": "Unfinished or failed work is reported in SUMMARY."
    }
  }
}
