{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/upriver-ai/json-schema/upriver-ai-trends-broad-input-schema.json",
  "title": "TrendsBroadInput",
  "description": "Payload model for the /trends/broad endpoint.",
  "x-generated": "2026-10-31",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/upriver-ai-openapi.json#/components/schemas/TrendsBroadInput",
  "properties": {
    "audience": {
      "$ref": "#/$defs/TrendsBroadTargetAudience"
    },
    "scope": {
      "$ref": "#/$defs/ScopeConfig",
      "description": "Time period to search."
    },
    "response_config": {
      "$ref": "#/$defs/TrendsBroadResponseConfig"
    },
    "cursor": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Cursor",
      "description": "Pagination cursor from the previous response's next_cursor field."
    },
    "sort_by": {
      "type": "string",
      "enum": [
        "recommended",
        "emerging",
        "newest",
        "rising"
      ],
      "title": "Sort By",
      "description": "Sort order for trend ranking. 'recommended' returns trends sorted by relevance and engagement (default). 'emerging' (legacy; prefer 'newest') surfaces trends that recently crossed from noise to signal. 'newest' returns trends by when they were first confirmed as real. 'rising' surfaces trends whose posting activity is accelerating right now; the signal fades as a trend's latest evidence ages. Legacy values 'momentum' and 'timeliness' are accepted and resolve to 'rising' and 'newest' respectively. Guide: https://docs.upriver.ai/trends/sorting",
      "default": "recommended"
    },
    "trend_ids": {
      "anyOf": [
        {
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        {
          "type": "null"
        }
      ],
      "title": "Trend Ids",
      "description": "Return exactly these trends in the given order. When set, all other filters and ranking are ignored."
    },
    "uses_specific_sound": {
      "anyOf": [
        {
          "type": "boolean"
        },
        {
          "type": "null"
        }
      ],
      "title": "Uses Specific Sound",
      "description": "Filter for trends that use a specific recognizable sound, derived from dominant-sound evidence and signature-sound classification."
    },
    "participation_type": {
      "anyOf": [
        {
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        {
          "type": "null"
        }
      ],
      "title": "Participation Type",
      "description": "Filter by participation type(s): 'open', 'timed', 'lip_sync', 'choreography'. Accepts a single string or a list."
    },
    "content_structure": {
      "anyOf": [
        {
          "type": "string",
          "enum": [
            "none",
            "loose",
            "structured"
          ]
        },
        {
          "items": {
            "type": "string",
            "enum": [
              "none",
              "loose",
              "structured"
            ]
          },
          "type": "array"
        },
        {
          "type": "null"
        }
      ],
      "title": "Content Structure",
      "description": "Filter by shared non-audio pattern across videos: 'none' (sound-led only), 'loose' (shared theme/aesthetic without a repeatable template), or 'structured' (repeatable text, editing, or visual template). Accepts a single string or a list; lists match any requested value."
    },
    "has_text_template": {
      "anyOf": [
        {
          "type": "boolean"
        },
        {
          "type": "null"
        }
      ],
      "title": "Has Text Template",
      "description": "Filter for trends with repeatable text overlay format"
    },
    "topic_scope": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Topic Scope",
      "description": "Filter by topic scope: 'universal', 'vertical', 'internet_culture', 'event_specific', 'person_specific'"
    },
    "topic_scope_exclude": {
      "anyOf": [
        {
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        {
          "type": "null"
        }
      ],
      "title": "Topic Scope Exclude",
      "description": "Exclude trends matching these topic scopes"
    },
    "tags": {
      "anyOf": [
        {
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        {
          "type": "null"
        }
      ],
      "title": "Tags",
      "description": "Include trends having at least one of these tags"
    },
    "exclude_tags": {
      "anyOf": [
        {
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        {
          "type": "null"
        }
      ],
      "title": "Exclude Tags",
      "description": "Exclude trends having any of these tags"
    },
    "min_duration": {
      "anyOf": [
        {
          "type": "number",
          "minimum": 0.0
        },
        {
          "type": "null"
        }
      ],
      "title": "Min Duration",
      "description": "Minimum typical duration in seconds. Trends with unknown duration are excluded when set."
    },
    "max_duration": {
      "anyOf": [
        {
          "type": "number",
          "minimum": 0.0
        },
        {
          "type": "null"
        }
      ],
      "title": "Max Duration",
      "description": "Maximum typical duration in seconds. Trends with unknown duration are excluded when set."
    },
    "query": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Query",
      "description": "Full-text search query across trend name, description, sound title, artist, genre, mood, and catchphrase. Supports phrases and OR operators."
    },
    "genre": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Genre",
      "description": "Filter by sound genre (e.g. pop, hip_hop, electronic)"
    },
    "mood": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Mood",
      "description": "Filter by sound mood (e.g. happy, sad, energetic, chill)"
    },
    "sound_category": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Sound Category",
      "description": "Filter by sound category: \"music\", \"speech\", \"sound_effect\", \"mixed\""
    },
    "has_lyrics": {
      "anyOf": [
        {
          "type": "boolean"
        },
        {
          "type": "null"
        }
      ],
      "title": "Has Lyrics",
      "description": "Filter for trends with/without sung or rapped lyrics"
    },
    "is_catchphrase": {
      "anyOf": [
        {
          "type": "boolean"
        },
        {
          "type": "null"
        }
      ],
      "title": "Is Catchphrase",
      "description": "Filter for trends built around a repeatable phrase"
    },
    "min_bpm": {
      "anyOf": [
        {
          "type": "integer",
          "minimum": 0.0
        },
        {
          "type": "null"
        }
      ],
      "title": "Min Bpm",
      "description": "Minimum tempo in BPM"
    },
    "max_bpm": {
      "anyOf": [
        {
          "type": "integer",
          "minimum": 0.0
        },
        {
          "type": "null"
        }
      ],
      "title": "Max Bpm",
      "description": "Maximum tempo in BPM"
    },
    "content_scenario": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Content Scenario",
      "description": "Filter by observed content scenario"
    },
    "energy": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Energy",
      "description": "Filter by energy level: \"calm\", \"low\", \"moderate\", \"high\", \"intense\""
    },
    "is_song": {
      "anyOf": [
        {
          "type": "boolean"
        },
        {
          "type": "null"
        }
      ],
      "title": "Is Song",
      "description": "Filter for trends with/without a recognizable song"
    },
    "has_vocals": {
      "anyOf": [
        {
          "type": "boolean"
        },
        {
          "type": "null"
        }
      ],
      "title": "Has Vocals",
      "description": "Filter for trends with/without vocal content"
    },
    "detected_language": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Detected Language",
      "description": "Filter by content language — the primary language of captions, hashtags, and text overlays (ISO 639-1 code, e.g. \"en\", \"es\"). Falls back to audio language when content language is unavailable."
    },
    "timeliness_status": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Timeliness Status",
      "description": "Minimum freshness level. 'recent' (>=0.7), 'active' (>=0.3), 'stale' (>=0.1). Filter is 'at least this fresh' -- timeliness_status=active returns recent + active trends."
    },
    "discovered_within_hours": {
      "anyOf": [
        {
          "type": "integer",
          "maximum": 8760.0,
          "minimum": 1.0
        },
        {
          "type": "null"
        }
      ],
      "title": "Discovered Within Hours",
      "description": "Only include trends discovered within the past N hours (e.g. 24 for the last day, 168 for the last week)."
    },
    "earliest_evidence_at_or_after": {
      "anyOf": [
        {
          "type": "string",
          "format": "date-time"
        },
        {
          "type": "null"
        }
      ],
      "title": "Earliest Evidence At Or After",
      "description": "Inclusive ISO 8601 lower bound for the result's `earliest_evidence_at`. Use it to restrict results to activity that first appeared within a recent window. Results with earlier dated evidence, or with no dated evidence, are excluded as a whole; evidence returned within a matching result is not time-filtered. The comparison uses all known supporting evidence, including records omitted by response filters or limits."
    },
    "brand_safety": {
      "anyOf": [
        {
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        {
          "type": "null"
        }
      ],
      "title": "Brand Safety",
      "description": "Filter by brand safety level(s): \"safe\", \"caution\", \"unsafe\". Accepts a single string or a list. When omitted, unsafe trends are excluded by default."
    }
  },
  "type": "object",
  "$defs": {
    "ScopeConfig": {
      "properties": {
        "from_time": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "string",
              "format": "date-time"
            },
            {
              "type": "null"
            }
          ],
          "title": "From Time",
          "description": "The start of the time window for the scan. This can be an absolute timestamp in ISO 8601 format (e.g., '2025-08-11T00:00:00Z'), or a relative duration string from the current time (e.g., '7d' for 7 days ago, '24h' for 24 hours ago, '2w' for 2 weeks ago).",
          "default": "90d"
        },
        "to_time": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "string",
              "format": "date-time"
            },
            {
              "type": "null"
            }
          ],
          "title": "To Time",
          "description": "The end of the time window for the scan. This can be the string 'now', an absolute timestamp in ISO 8601 format (e.g., '2025-08-11T00:00:00Z'), or a relative duration string from the current time (e.g., '24h' for 24 hours ago). A relative duration for `to_time` should be more recent than `from_time`.",
          "default": "now"
        },
        "platforms": {
          "anyOf": [
            {
              "items": {
                "type": "string",
                "enum": [
                  "tiktok",
                  "instagram",
                  "youtube",
                  "twitch",
                  "reddit",
                  "x",
                  "discord",
                  "facebook",
                  "snapchat",
                  "pinterest",
                  "forums",
                  "blogs",
                  "reviews"
                ]
              },
              "type": "array"
            },
            {
              "type": "null"
            }
          ],
          "title": "Platforms",
          "description": "Platforms to scan and include in results (e.g., Reddit, blogs, forums)"
        }
      },
      "type": "object",
      "title": "ScopeConfig",
      "description": "Where/when to look for signals and which platforms to use.\nThese are query filters (not brand metadata).\n\nTime window:\n  - Use 'from_time' (start) and 'to_time' (end).\n  - Accepts ISO8601 UTC or relative durations like '7d', '24h', '2w'.\n  - If only 'from_time' is relative, window is [now - duration, now].\n  - 'to_time' may be 'now' (default), ISO8601 UTC, or a relative duration\n    interpreted as now - duration.\n  - After normalization the service validates start < end.\n\nPlatforms:\n  - Use 'platforms' to specify which platforms to both scan AND return\n    (e.g., reddit, blogs, forums).\n  - This controls both the data sources analyzed and the results returned."
    },
    "TrendsBroadResponseConfig": {
      "properties": {
        "include_rollup": {
          "type": "boolean",
          "title": "Include Rollup",
          "description": "Include a summary paragraph of all the trends found.",
          "default": false
        },
        "verbosity": {
          "type": "string",
          "title": "Verbosity",
          "description": "How much detail to include: compact | standard | verbose.",
          "default": "standard"
        },
        "max_results": {
          "type": "integer",
          "maximum": 50.0,
          "minimum": 1.0,
          "title": "Max Results",
          "description": "Maximum number of trends to return.",
          "default": 10
        },
        "min_confidence": {
          "type": "number",
          "maximum": 1.0,
          "minimum": 0.0,
          "title": "Min Confidence",
          "description": "Only return trends with confidence above this score.",
          "default": 0.55
        },
        "media_limit": {
          "anyOf": [
            {
              "type": "integer",
              "maximum": 50.0,
              "minimum": 1.0
            },
            {
              "type": "null"
            }
          ],
          "title": "Media Limit",
          "description": "Max media samples per trend. Default: 3 (basic/none), 10 (debug)."
        }
      },
      "type": "object",
      "title": "TrendsBroadResponseConfig",
      "description": "How to format the response."
    },
    "TrendsBroadTargetAudience": {
      "properties": {
        "geo": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "title": "Geo",
          "description": "Which countries to focus on (use ISO country codes like 'US', 'CA')",
          "default": [
            "US"
          ]
        }
      },
      "type": "object",
      "title": "TrendsBroadTargetAudience",
      "description": "Where to look for trends."
    }
  }
}