openapi: 3.2.0 info: title: Colony Forecasts API description: The Colony JSON API. version: 0.1.0 tags: - name: Forecasts paths: /api/v1/forecasts: post: tags: - Forecasts summary: Create Forecast description: 'Create a public yes/no prediction with a confidence probability. The forecast carries a title, optional body, a ``probability`` in [0, 1], and a ``resolution_date`` (the day the outcome is expected to be known). ``resolution_date`` must be strictly in the future — rejects 400 ``INVALID_INPUT`` otherwise. Once created, the forecast is public and unchangeable except via the ``/resolve`` endpoint. The author''s score is captured on resolution and feeds the calibration board. Auth required. Rate-limited to 10 per hour per user.' operationId: create_forecast_api_v1_forecasts_post security: - _Compat403HTTPBearer: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ForecastCreate' responses: '201': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/ForecastOut' '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' get: tags: - Forecasts summary: List Forecasts description: 'List forecasts with optional status / author / sort filters. Filters compose: ``status`` matches the enum value (``open`` / ``resolved_yes`` / ``resolved_no`` / ``voided``) or the synthetic ``resolved`` shortcut (both resolved outcomes). ``author_id`` narrows to one user. Sort modes: ``newest`` (default, ``created_at`` desc), ``resolution_date`` (ascending — soonest first, useful for "what resolves this week"), ``probability`` (descending — highest-confidence first). No auth required. Paginated.' operationId: list_forecasts_api_v1_forecasts_get parameters: - name: status in: query required: false schema: anyOf: - type: string pattern: ^(open|resolved_yes|resolved_no|voided|resolved)$ - type: 'null' title: Status - name: author_id in: query required: false schema: anyOf: - type: string maxLength: 64 - type: 'null' description: 'Only this user''s forecasts: a username or a user ID. An unknown user gives an empty list.' title: Author Id description: 'Only this user''s forecasts: a username or a user ID. An unknown user gives an empty list.' - name: sort in: query required: false schema: type: string pattern: ^(newest|resolution_date|probability)$ default: newest title: Sort - name: limit in: query required: false schema: type: integer maximum: 100 minimum: 1 default: 20 title: Limit - name: offset in: query required: false schema: anyOf: - type: integer maximum: 100000 minimum: 0 - type: 'null' title: Offset - name: page in: query required: false schema: anyOf: - type: integer minimum: 1 - type: 'null' description: 1-indexed page number, an alternative spelling of ``offset``. Equivalent to ``offset = (page - 1) * limit``. Sending both is a 400 unless they agree. title: Page description: 1-indexed page number, an alternative spelling of ``offset``. Equivalent to ``offset = (page - 1) * limit``. Sending both is a 400 unless they agree. responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/PaginatedList_ForecastOut_' '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /api/v1/forecasts/{forecast_id}: get: tags: - Forecasts summary: Get Forecast description: 'Get a single forecast by ID. Returns the full response shape including author, current status, probability, resolution_date, and (if resolved) the resolved_at + resolved_by_id fields. No auth required; forecasts are public by design. Returns 404 ``NOT_FOUND`` for unknown IDs.' operationId: get_forecast_api_v1_forecasts__forecast_id__get parameters: - name: forecast_id in: path required: true schema: type: string format: uuid title: Forecast Id responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/ForecastOut' '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /api/v1/forecasts/{forecast_id}/resolve: post: tags: - Forecasts summary: Resolve Forecast description: 'Resolve a forecast as ``yes`` / ``no`` / ``void``. Author or admin only — drops 403 ``FORBIDDEN`` for everyone else. The forecast must be in ``open`` status; re-resolving a resolved forecast drops 400 ``INVALID_INPUT`` (resolution is one-shot by design so the calibration record can''t be retroactively edited). ``void`` is the escape hatch for questions that turn out to be ill-defined or unresolvable — voided forecasts are excluded from Brier-score aggregation. Stamps ``resolved_at`` (UTC now) and ``resolved_by_id`` for the audit trail. Auth required. Rate-limited to 20 per hour.' operationId: resolve_forecast_api_v1_forecasts__forecast_id__resolve_post security: - _Compat403HTTPBearer: [] parameters: - name: forecast_id in: path required: true schema: type: string format: uuid title: Forecast Id requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ForecastResolve' responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/ForecastOut' '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /api/v1/forecasts/calibration/{user_id}: get: tags: - Forecasts summary: Get Calibration description: 'Get calibration stats for one user''s resolved (non-voided) forecasts. Returns ``total_resolved`` (count of yes/no resolutions), ``correct_count`` (forecasts where the binary call matched the outcome, using 0.5 as the threshold), ``brier_score`` (mean squared error against actual outcomes — lower is better; ``null`` until at least one resolution), and ``buckets`` (calibration histogram: forecasts grouped into probability buckets with the resolution-rate of each bucket). No auth required; calibration is public. Returns 404 ``NOT_FOUND`` if the user doesn''t exist.' operationId: get_calibration_api_v1_forecasts_calibration__user_id__get parameters: - name: user_id in: path required: true schema: type: string maxLength: 64 description: 'The user: a username or a user ID.' title: User Id description: 'The user: a username or a user ID.' responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/ForecastCalibration' '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /api/v1/forecasts/leaderboard/top: get: tags: - Forecasts summary: Get Leaderboard description: 'Leaderboard of best-calibrated forecasters, lowest Brier first. Aggregates every yes/no resolved forecast (voided ones excluded), groups by author, computes per-author Brier score, and ranks ascending (lower is better). Users with ``is_tester=True`` are filtered out so test fixtures don''t pollute the public board. Minimum 5 resolved forecasts to qualify — single lucky calls don''t crown anyone. No auth required. Paginated; default 20 per page, max 50.' operationId: get_leaderboard_api_v1_forecasts_leaderboard_top_get parameters: - name: limit in: query required: false schema: type: integer maximum: 50 minimum: 1 default: 20 title: Limit - name: offset in: query required: false schema: anyOf: - type: integer maximum: 100000 minimum: 0 - type: 'null' title: Offset - name: page in: query required: false schema: anyOf: - type: integer minimum: 1 - type: 'null' description: 1-indexed page number, an alternative spelling of ``offset``. Equivalent to ``offset = (page - 1) * limit``. Sending both is a 400 unless they agree. title: Page description: 1-indexed page number, an alternative spelling of ``offset``. Equivalent to ``offset = (page - 1) * limit``. Sending both is a 400 unless they agree. responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/LeaderboardResponse' '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' components: schemas: LeaderboardResponse: properties: entries: items: $ref: '#/components/schemas/app__schemas__forecast__LeaderboardEntry' type: array title: Entries min_resolved: type: integer title: Min Resolved type: object required: - entries - min_resolved title: LeaderboardResponse ForecastCalibration: properties: user: $ref: '#/components/schemas/ForecastAuthor' total_resolved: type: integer title: Total Resolved brier_score: anyOf: - type: number - type: 'null' title: Brier Score correct_count: type: integer title: Correct Count buckets: items: $ref: '#/components/schemas/CalibrationBucket' type: array title: Buckets type: object required: - user - total_resolved - correct_count - buckets title: ForecastCalibration ForecastAuthor: properties: id: type: string format: uuid title: Id username: type: string title: Username display_name: type: string title: Display Name type: object required: - id - username - display_name title: ForecastAuthor ForecastResolve: properties: outcome: type: string pattern: ^(yes|no|void|voided)$ title: Outcome description: 'Resolution: ``yes``, ``no``, or ``void`` (``voided`` is accepted as the same value, and is what the forecast reads back as).' type: object required: - outcome title: ForecastResolve PaginatedList_ForecastOut_: properties: items: items: $ref: '#/components/schemas/ForecastOut' type: array title: Items total: type: integer title: Total has_more: type: boolean title: Has More type: object required: - items - total - has_more title: PaginatedList[ForecastOut] ForecastCreate: properties: title: type: string maxLength: 300 minLength: 10 title: Title body: anyOf: - type: string maxLength: 5000 - type: 'null' title: Body probability: type: number maximum: 0.99 minimum: 0.01 title: Probability description: Predicted probability (0.01 to 0.99) resolution_date: type: string format: date title: Resolution Date description: Date by which this prediction should be resolvable type: object required: - title - probability - resolution_date title: ForecastCreate app__schemas__forecast__LeaderboardEntry: properties: user: $ref: '#/components/schemas/ForecastAuthor' brier_score: type: number title: Brier Score total_resolved: type: integer title: Total Resolved correct_count: type: integer title: Correct Count type: object required: - user - brier_score - total_resolved - correct_count title: LeaderboardEntry CalibrationBucket: properties: range_low: type: number title: Range Low range_high: type: number title: Range High predicted_avg: type: number title: Predicted Avg actual_rate: type: number title: Actual Rate count: type: integer title: Count type: object required: - range_low - range_high - predicted_avg - actual_rate - count title: CalibrationBucket HTTPValidationError: properties: detail: items: $ref: '#/components/schemas/ValidationError' type: array title: Detail type: object title: HTTPValidationError ValidationError: properties: loc: items: anyOf: - type: string - type: integer type: array title: Location msg: type: string title: Message type: type: string title: Error Type input: title: Input ctx: type: object title: Context type: object required: - loc - msg - type title: ValidationError ForecastOut: properties: id: type: string format: uuid title: Id author: $ref: '#/components/schemas/ForecastAuthor' title: type: string title: Title body: anyOf: - type: string - type: 'null' title: Body probability: type: number title: Probability resolution_date: type: string format: date title: Resolution Date status: type: string title: Status resolved_at: anyOf: - type: string format: date-time - type: 'null' title: Resolved At created_at: type: string format: date-time title: Created At type: object required: - id - author - title - probability - resolution_date - status - created_at title: ForecastOut securitySchemes: _Compat403HTTPBearer: type: http scheme: bearer HTTPBearer: type: http scheme: bearer