{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/foresea-ink/json-schema/foresea-ink-predict-request-schema.json",
  "title": "PredictRequest",
  "description": "Input payload for a single forecasting prediction.",
  "x-generated": "2026-11-08",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/foresea-ink-openapi.json#/components/schemas/PredictRequest",
  "properties": {
    "question": {
      "type": "string",
      "maxLength": 2000,
      "minLength": 1,
      "title": "Question",
      "description": "The forecasting question to evaluate. It should ask about a future or otherwise resolvable event, option, quantity, or date."
    },
    "description": {
      "type": "string",
      "maxLength": 4000,
      "title": "Description",
      "description": "Extended background context that clarifies what the question is asking.",
      "default": ""
    },
    "resolution_criteria": {
      "type": "string",
      "maxLength": 12000,
      "title": "Resolution Criteria",
      "description": "Full venue conditions and measurement source used to resolve the forecast.",
      "default": ""
    },
    "categories": {
      "items": {
        "type": "string"
      },
      "type": "array",
      "maxItems": 20,
      "title": "Categories",
      "description": "Topic tags (e.g. `['Economics', 'United States']`)."
    },
    "news_articles": {
      "items": {
        "$ref": "#/$defs/NewsArticle"
      },
      "type": "array",
      "maxItems": 20,
      "title": "News Articles",
      "description": "Pre-fetched venue or caller news articles to preserve as evidence. When `attach_evidence` is true, fresh retrieved news is merged with them."
    },
    "variant": {
      "type": "string",
      "maxLength": 100,
      "title": "Variant",
      "description": "Prompt variant that controls the reasoning instruction style. See the variant table in the Overview section.",
      "default": "variant0_neutral_baseline"
    },
    "attach_evidence": {
      "type": "boolean",
      "title": "Attach Evidence",
      "description": "If `true`, fetch live evidence and merge it with any supplied articles.",
      "default": true
    },
    "evidence_top_k": {
      "type": "integer",
      "maximum": 100.0,
      "minimum": 1.0,
      "title": "Evidence Top K",
      "description": "Maximum number of evidence articles to retrieve (1–100).",
      "default": 20
    },
    "evidence_detail": {
      "type": "string",
      "pattern": "^(summary|full)$",
      "title": "Evidence Detail",
      "description": "Evidence detail level in the model prompt. Use `summary` for scheduled multi-model runs to keep prompts bounded; default `full` preserves the interactive API behaviour.",
      "default": "full"
    },
    "created_time": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 50
        },
        {
          "type": "null"
        }
      ],
      "title": "Created Time",
      "description": "ISO 8601 question creation time."
    },
    "publish_time": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 50
        },
        {
          "type": "null"
        }
      ],
      "title": "Publish Time",
      "description": "ISO 8601 question publish time."
    },
    "resolve_time": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 50
        },
        {
          "type": "null"
        }
      ],
      "title": "Resolve Time",
      "description": "ISO 8601 resolution deadline."
    },
    "days_open": {
      "anyOf": [
        {
          "type": "integer",
          "maximum": 36500.0,
          "minimum": 0.0
        },
        {
          "type": "null"
        }
      ],
      "title": "Days Open",
      "description": "Days the question has been open."
    },
    "history": {
      "items": {
        "additionalProperties": {
          "type": "string"
        },
        "type": "object"
      },
      "type": "array",
      "maxItems": 12,
      "title": "History",
      "description": "Prior conversation turns for multi-turn context, oldest first. Each item is `{\"role\": \"user\"|\"assistant\", \"content\": \"...\"}`."
    },
    "conversation_steer": {
      "type": "string",
      "maxLength": 1000,
      "title": "Conversation Steer",
      "description": "Optional per-conversation steering instruction for tone, emphasis, or analytical stance. It cannot override safety rules or response contracts.",
      "default": ""
    },
    "question_type": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Question Type",
      "description": "Question type: `binary`, `multiple_choice`, `numeric`, or `date`. Set this explicitly for API clients; auto-detected from the question when omitted."
    },
    "options": {
      "items": {
        "type": "string"
      },
      "type": "array",
      "maxItems": 12,
      "title": "Options",
      "description": "Candidate answers for `multiple_choice` questions (optional; the model can infer them)."
    },
    "market_platform": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 80
        },
        {
          "type": "null"
        }
      ],
      "title": "Market Platform",
      "description": "Prediction market venue, e.g. `Polymarket`, `Kalshi`, `Manifold`, or `Metaculus`."
    },
    "market_url": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 2000
        },
        {
          "type": "null"
        }
      ],
      "title": "Market Url",
      "description": "URL for the prediction market being analyzed. Must start with `http://` or `https://`."
    },
    "market_outcome": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 120
        },
        {
          "type": "null"
        }
      ],
      "title": "Market Outcome",
      "description": "Outcome whose market-implied probability is provided. Defaults to `Yes` for binary markets; set this to `No` or to a multiple-choice option label when needed."
    },
    "market_ident": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 200
        },
        {
          "type": "null"
        }
      ],
      "title": "Market Ident",
      "description": "Venue market id (Polymarket slug / Kalshi ticker). Lets Foresea fetch live odds when `market_probability` is omitted."
    },
    "market_probability": {
      "anyOf": [
        {
          "type": "number"
        },
        {
          "type": "null"
        }
      ],
      "title": "Market Probability",
      "description": "Current market-implied probability for `market_outcome`. Use 0-1, or pass a percentage from 0-100 and Foresea will normalize it. When omitted but the market is identifiable (`market_url` or `market_platform`+`market_ident`), Foresea fetches the current live odds."
    },
    "market_volume": {
      "anyOf": [
        {
          "type": "number"
        },
        {
          "type": "null"
        }
      ],
      "title": "Market Volume",
      "description": "24-hour trading volume in USD. Populated automatically from live odds when the market is identifiable."
    },
    "market_liquidity": {
      "anyOf": [
        {
          "type": "number"
        },
        {
          "type": "null"
        }
      ],
      "title": "Market Liquidity",
      "description": "Total open interest / liquidity in USD. Populated automatically from live odds."
    },
    "market_price_change_24h": {
      "anyOf": [
        {
          "type": "number"
        },
        {
          "type": "null"
        }
      ],
      "title": "Market Price Change 24H",
      "description": "Price change in the last 24 hours (in probability points, -1..1). Populated automatically from live odds."
    },
    "market_bid": {
      "anyOf": [
        {
          "type": "number"
        },
        {
          "type": "null"
        }
      ],
      "title": "Market Bid",
      "description": "Current best bid (YES) in probability units (0..1). Kalshi only; Polymarket uses the mid price."
    },
    "market_ask": {
      "anyOf": [
        {
          "type": "number"
        },
        {
          "type": "null"
        }
      ],
      "title": "Market Ask",
      "description": "Current best ask (YES) in probability units (0..1). Kalshi only."
    },
    "market_last_trade_price": {
      "anyOf": [
        {
          "type": "number"
        },
        {
          "type": "null"
        }
      ],
      "title": "Market Last Trade Price",
      "description": "Last actual trade price (0..1). More reliable than resting bid/ask in thin markets. Populated automatically."
    },
    "market_price_change_7d": {
      "anyOf": [
        {
          "type": "number"
        },
        {
          "type": "null"
        }
      ],
      "title": "Market Price Change 7D",
      "description": "7-day price change in probability points (-1..1). Reveals slow drift that 24h misses. Populated automatically."
    },
    "market_resolution_source": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Market Resolution Source",
      "description": "Who resolves the market (e.g. UMA oracle, Kalshi team). Affects how much to trust the market price."
    },
    "market_no_sub_title": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Market No Sub Title",
      "description": "What the NO outcome represents (Kalshi). Useful when the question wording is ambiguous."
    },
    "market_expected_expiration_time": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Market Expected Expiration Time",
      "description": "When the venue expects to settle (Kalshi). May differ from trading close date."
    },
    "market_floor_strike": {
      "anyOf": [
        {},
        {
          "type": "null"
        }
      ],
      "title": "Market Floor Strike",
      "description": "Lower bound of resolution range for scalar Kalshi markets."
    },
    "market_cap_strike": {
      "anyOf": [
        {},
        {
          "type": "null"
        }
      ],
      "title": "Market Cap Strike",
      "description": "Upper bound of resolution range for scalar Kalshi markets."
    },
    "market_price_history": {
      "anyOf": [
        {
          "items": {
            "additionalProperties": true,
            "type": "object"
          },
          "type": "array"
        },
        {
          "type": "null"
        }
      ],
      "title": "Market Price History",
      "description": "Recent price points for this market, newest first. Each entry: {ts: ISO timestamp, probability: float 0..1, volume?: float, liquidity?: float, bid?: float, ask?: float}. Up to 8 entries. Shown to the model as market-trajectory context so it can detect price and tradability changes that the current price alone would hide."
    },
    "forecast_history": {
      "anyOf": [
        {
          "items": {
            "additionalProperties": true,
            "type": "object"
          },
          "type": "array"
        },
        {
          "type": "null"
        }
      ],
      "title": "Forecast History",
      "description": "Prior Foresea forecast snapshots for the same market, newest first. Used by scheduled re-forecasts so repeat forecasts are stateful updates, not independent stateless calls."
    },
    "openrouter_api_key": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 256
        },
        {
          "type": "null"
        }
      ],
      "title": "Openrouter Api Key",
      "description": "User-supplied OpenRouter API key. When set, this request is routed through OpenRouter using `openrouter_model` instead of the server's default provider."
    },
    "openrouter_model": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 128
        },
        {
          "type": "null"
        }
      ],
      "title": "Openrouter Model",
      "description": "OpenRouter model ID (e.g. `openai/gpt-4o`, `anthropic/claude-3.5-sonnet`)."
    },
    "model": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 64
        },
        {
          "type": "null"
        }
      ],
      "title": "Model",
      "description": "Optional alternate server-hosted model to forecast with, from the SCADS allowlist (for example `gpt-oss-120b`, `scads-alias-reasoning`, `kimi-k2.7-code`). Uses the server's own key — no BYOK needed. Powers the multi-model paper-trading comparison."
    },
    "provider_base_url": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 2000
        },
        {
          "type": "null"
        }
      ],
      "title": "Provider Base Url",
      "description": "Optional OpenAI-compatible chat-completions endpoint (e.g. `https://api.openai.com/v1/chat/completions`). When set with `openrouter_api_key` + `openrouter_model`, the request goes to your own endpoint instead of OpenRouter. Must be public HTTPS."
    },
    "chat_mode": {
      "type": "boolean",
      "title": "Chat Mode",
      "description": "When `true`, skips the forecast output template entirely. The model responds in plain natural language with no structured JSON.",
      "default": true
    },
    "effort_tier": {
      "anyOf": [
        {
          "type": "string",
          "enum": [
            "simple",
            "standard",
            "deep"
          ]
        },
        {
          "type": "null"
        }
      ],
      "title": "Effort Tier",
      "description": "How much reasoning effort the model call uses, when the provider supports it (best-effort; unsupported providers ignore it). Set explicitly to override; left unset, no reasoning-effort hint is sent."
    },
    "max_tokens": {
      "anyOf": [
        {
          "type": "integer",
          "maximum": 4096.0,
          "minimum": 64.0
        },
        {
          "type": "null"
        }
      ],
      "title": "Max Tokens",
      "description": "Optional per-request generation cap. The server still clamps this to its configured model limit."
    },
    "ollama_base_url": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 500
        },
        {
          "type": "null"
        }
      ],
      "title": "Ollama Base Url",
      "description": "Base URL of an Ollama instance, e.g. `http://localhost:11434`. No API key required. Cloud-metadata hosts are blocked; for foresea.ink the instance must be reachable from the server."
    }
  },
  "type": "object",
  "required": [
    "question"
  ],
  "$defs": {
    "NewsArticle": {
      "properties": {
        "title": {
          "anyOf": [
            {
              "type": "string",
              "maxLength": 500
            },
            {
              "type": "null"
            }
          ],
          "title": "Title",
          "description": "Article headline."
        },
        "summary": {
          "anyOf": [
            {
              "type": "string",
              "maxLength": 4000
            },
            {
              "type": "null"
            }
          ],
          "title": "Summary",
          "description": "Short human-written summary."
        },
        "summary_llm": {
          "anyOf": [
            {
              "type": "string",
              "maxLength": 4000
            },
            {
              "type": "null"
            }
          ],
          "title": "Summary Llm",
          "description": "LLM-generated summary."
        },
        "text": {
          "anyOf": [
            {
              "type": "string",
              "maxLength": 20000
            },
            {
              "type": "null"
            }
          ],
          "title": "Text",
          "description": "Full article body text."
        },
        "source": {
          "anyOf": [
            {
              "type": "string",
              "maxLength": 200
            },
            {
              "type": "null"
            }
          ],
          "title": "Source",
          "description": "Publisher name (e.g. Reuters)."
        },
        "credibility": {
          "anyOf": [
            {
              "additionalProperties": true,
              "type": "object"
            },
            {
              "type": "null"
            }
          ],
          "title": "Credibility",
          "description": "Credibility score breakdown."
        },
        "frs": {
          "anyOf": [
            {
              "additionalProperties": true,
              "type": "object"
            },
            {
              "type": "null"
            }
          ],
          "title": "Frs",
          "description": "Future-Resolution Statement metadata."
        },
        "url": {
          "anyOf": [
            {
              "type": "string",
              "maxLength": 2000
            },
            {
              "type": "null"
            }
          ],
          "title": "Url",
          "description": "Canonical article URL."
        },
        "authors": {
          "anyOf": [
            {},
            {
              "type": "null"
            }
          ],
          "title": "Authors",
          "description": "Author name(s)."
        },
        "publish_date": {
          "anyOf": [
            {
              "type": "string",
              "maxLength": 100
            },
            {
              "type": "null"
            }
          ],
          "title": "Publish Date",
          "description": "ISO 8601 publish timestamp."
        },
        "keywords": {
          "anyOf": [
            {},
            {
              "type": "null"
            }
          ],
          "title": "Keywords",
          "description": "Extracted keywords."
        },
        "relevance_score": {
          "anyOf": [
            {
              "type": "number",
              "maximum": 1.0,
              "minimum": 0.0
            },
            {
              "type": "null"
            }
          ],
          "title": "Relevance Score",
          "description": "Cosine similarity to the question (0–1)."
        },
        "search_query": {
          "anyOf": [
            {
              "type": "string",
              "maxLength": 500
            },
            {
              "type": "null"
            }
          ],
          "title": "Search Query",
          "description": "Query used to retrieve this article."
        }
      },
      "type": "object",
      "title": "NewsArticle",
      "description": "A news article passed as evidence context for the prediction."
    }
  }
}