metaculus

Metaculus is a forecasting platform where users predict outcomes of real-world events. Use this skill to interact with the Metaculus API for browsing questions, submitting forecasts, reading community predictions, managing comments, and downloading forecast data.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "metaculus" with this command: npx skills add outsharp/shipp-skills/outsharp-shipp-skills-metaculus

Metaculus API

Metaculus is a forecasting platform where users predict outcomes of real-world events. Questions range across science, technology, politics, economics, and more. The platform aggregates individual forecasts into community predictions and scores forecasters on accuracy.

Check this skill and the official API documentation FREQUENTLY for updates.

Feedback: Contact the Metaculus team at api-requests@metaculus.com with questions, ideas, or feedback.

Source code & issues: github.com/Metaculus/metaculus


Key Concepts (Glossary)

TermDefinition
PostThe primary feed entity. A post wraps a question, group of questions, conditional pair, or notebook. Posts have statuses, authors, projects, and comments.
QuestionA single forecastable item within a post. Types: binary, multiple_choice, numeric, discrete, date.
Group of QuestionsA post containing multiple related sub-questions displayed together (e.g., "What will GDP be in 2025, 2026, 2027?").
ConditionalA post with paired questions: "If [condition], what is P(child)?" — produces question_yes and question_no variants.
ForecastA user's prediction on a question. Format depends on question type: probability (binary), CDF (continuous), or distribution (multiple choice).
Community Prediction (CP)The aggregated forecast from all users, computed via various aggregation methods.
Aggregation MethodHow individual forecasts are combined: recency_weighted (default), unweighted, metaculus_prediction, single_aggregation.
ProjectA container for posts — can be a tournament, category, tag, question series, or site section.
TournamentA special project type with prize pools, start/close dates, and leaderboards.
CategoryA topic classification (e.g., "Nuclear Technology & Risks", "Health & Pandemics").
ResolutionThe actual outcome of a question once known. Values vary by type: yes/no (binary), a number, a date, or an option name.
Curation StatusEditorial status of a post: draft, pending, rejected, approved.
ScalingDefines how a continuous question's range maps to the CDF. Includes range_min, range_max, zero_point (for log scale), and bounds.
CDFCumulative Distribution Function — the format for continuous forecasts. A list of 201 floats (or inbound_outcome_count + 1 for discrete).
Inbound Outcome CountNumber of possible outcomes within a question's range (excluding out-of-bounds). Default is 200 for continuous; smaller for discrete.

Base URL

All endpoints are served from:

https://www.metaculus.com

All paths below are relative to this base (e.g., GET /api/posts/ means GET https://www.metaculus.com/api/posts/).


Authentication

All API requests require authentication. Unauthenticated requests are rejected.

Getting Your Token

  1. Log in to Metaculus.
  2. Go to your Account Settings → API Access.
  3. Copy (or generate) your API token.

Using the Token

Add the Authorization header to every request. The token must be prefixed with the literal string Token followed by a space:

Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b

Example: curl

curl -s "https://www.metaculus.com/api/posts/?limit=5&order_by=-published_at" \
  -H "Authorization: Token $METACULUS_API_TOKEN" | jq .

Example: Python

import os, requests

TOKEN = os.environ["METACULUS_API_TOKEN"]
HEADERS = {"Authorization": f"Token {TOKEN}"}
BASE = "https://www.metaculus.com"

resp = requests.get(f"{BASE}/api/posts/", headers=HEADERS, params={"limit": 5})
resp.raise_for_status()
data = resp.json()

Environment Variables

Never hardcode tokens. Store them as environment variables or in a .env file:

METACULUS_API_TOKEN=your-token-here

Rate Limits

Metaculus throttles requests to prevent abuse. If you receive a 429 Too Many Requests response, implement exponential backoff before retrying.


REST API Endpoints Overview

Feed (Posts)

MethodEndpointDescriptionAuth
GET/api/posts/Retrieve paginated posts feed with filtersRequired
GET/api/posts/{postId}/Retrieve a single post with full detailsRequired

Questions & Forecasts

MethodEndpointDescriptionAuth
POST/api/questions/forecast/Submit forecasts for one or more questionsRequired
POST/api/questions/withdraw/Withdraw active forecastsRequired

Comments

MethodEndpointDescriptionAuth
GET/api/comments/Retrieve comments (filter by post or author)Required
POST/api/comments/create/Create a new comment on a postRequired

Utilities & Data

MethodEndpointDescriptionAuth
GET/api/posts/{postId}/download-data/Download question data as a ZIP of CSVsRequired
GET/api/projects/{projectId}/download-data/Download full project data as a ZIP of CSVsRequired (admin/whitelisted)

Data Model

Post → Question Hierarchy

A Post is the top-level entity in the feed. Each post contains exactly one of:

  • question — a single question (binary, numeric, date, multiple choice, or discrete)
  • group_of_questions — multiple related sub-questions
  • conditional — a conditional pair (condition + child, producing question_yes and question_no)
  • A notebook (no question content)

The other fields will be null. For example, a post with a single binary question will have question populated and conditional/group_of_questions set to null.

Post

FieldTypeDescription
idintegerUnique post ID
titlestringFull title
short_titlestringURL-friendly short title
slugstringURL slug
author_idintegerAuthor's user ID
author_usernamestringAuthor's username
projectsobjectAssociated projects (see below)
created_atdatetimeWhen the post was created
published_atdatetime?When the post was published
open_timedatetime?When the question opened for forecasting
edited_atdatetimeLast edit timestamp
curation_statusstringdraft, pending, rejected, or approved
comment_countintegerNumber of comments
statusstringopen, upcoming, closed, resolved, draft, pending, rejected
nr_forecastersintegerNumber of unique forecasters
questionQuestion?Single question (if applicable)
conditionalConditional?Conditional pair (if applicable)
group_of_questionsGroupOfQuestions?Question group (if applicable)
user_permissionstringforecaster or viewer
voteobject{ score: int, user_vote: string? }
forecasts_countintegerTotal number of forecasts

Post Projects Object

FieldTypeDescription
site_mainProject[]Site-level project associations
tournamentProject[]Tournaments this post belongs to
categoryCategory[]Categories
tagTag[]Tags
question_seriesProject[]Question series
default_projectProjectThe post's primary/default project

Project

FieldTypeDescription
idintegerProject ID
typestringsite_main, tournament, etc.
namestringDisplay name
slugstring?URL slug
prize_poolstringPrize pool amount (e.g., "0.00")
start_datedatetime?Start date
close_datedatetime?Close date
is_ongoingboolean?Whether the project is ongoing
default_permissionstringDefault user permission (e.g., "forecaster")
visibilitystringnormal, not_in_main_feed, unlisted

Question

FieldTypeDescription
idintegerUnique question ID (used in forecast submissions, not the post ID)
titlestringQuestion title
descriptionstringFull description (may be omitted unless include_descriptions=true)
typestringbinary, multiple_choice, numeric, discrete, date
statusstringupcoming, open, closed, resolved
resolutionstring?Resolution value (null if unresolved)
resolution_criteriastringHow the question will be resolved
fine_printstringAdditional resolution details
created_atdatetimeCreation timestamp
open_timedatetimeWhen forecasting opens
scheduled_close_timedatetimeWhen forecasting is scheduled to close
actual_close_timedatetimeWhen forecasting actually closed
scheduled_resolve_timedatetimeWhen resolution is scheduled
actual_resolve_timedatetime?When it was actually resolved
optionsstring[]Current options (multiple choice only)
all_options_everstring[]All options that have ever existed (multiple choice only)
open_upper_boundbooleanWhether the upper bound is open
open_lower_boundbooleanWhether the lower bound is open
inbound_outcome_countinteger?Number of discrete outcomes in range (default 200 for continuous)
unitstringDisplay unit (e.g., "$", "°C")
labelstring?Label for sub-questions
scalingQuestionScalingRange and scaling parameters
aggregationsobjectCommunity prediction aggregations (see below)

QuestionScaling

FieldTypeDescription
range_minfloat?Lower boundary of the input range
range_maxfloat?Upper boundary of the input range
zero_pointfloat?Log-scale zero point (null = linear scaling)
open_upper_boundboolean?Whether upper bound is open
open_lower_boundboolean?Whether lower bound is open
inbound_outcome_countinteger?Number of outcomes within range
continuous_rangestring[]?Real-value locations where the CDF is evaluated

Aggregations

Each question includes an aggregations object with up to four methods:

  • recency_weighted — Default; weights recent forecasts more heavily
  • unweighted — Equal weight for all forecasts
  • metaculus_prediction — Metaculus's proprietary prediction
  • single_aggregation — Beta; admin-only

Each method contains:

FieldTypeDescription
historyobject[]Time series of aggregated forecasts
latestobject?Most recent aggregated forecast
score_dataobject?Scoring information

Each history/latest entry contains:

FieldTypeDescription
start_timedatetimeWhen this aggregation period started
end_timedatetime?When this aggregation period ended
forecast_valuesfloat[]The aggregated forecast (probability for binary, CDF for continuous)
forecaster_countintegerNumber of contributing forecasters
centersfloat[]Median/center values
interval_lower_boundsfloat[]Lower confidence bounds
interval_upper_boundsfloat[]Upper confidence bounds
meansfloat?Mean values

Conditional

FieldTypeDescription
idintegerConditional ID
conditionQuestionThe condition question
condition_childQuestionThe child question
question_yesQuestion"If condition = Yes" variant
question_noQuestion"If condition = No" variant

GroupOfQuestions

FieldTypeDescription
idintegerGroup ID
descriptionstringGroup description
resolution_criteriastringResolution criteria
fine_printstringAdditional details
group_variablestringThe variable that differs across sub-questions
graph_typestringmultiple_choice_graph or fan_graph
questionsQuestion[]The sub-questions

Comment

FieldTypeDescription
idintegerComment ID
authorobject{ id, username, is_bot, is_staff }
parent_idinteger?Parent comment ID (for replies)
root_idinteger?Root comment ID in thread
created_atdatetimeCreation timestamp
textstringComment content
on_postintegerPost ID the comment belongs to
included_forecastbooleanWhether the user's last forecast is included
is_privatebooleanWhether the comment is private
vote_scoreintegerTotal vote score
user_voteintegerCurrent user's vote (-1, 0, 1)

Endpoints: Detailed Reference

GET /api/posts/ — Retrieve Posts Feed

Returns a paginated list of posts with extensive filtering and sorting.

Query Parameters:

ParameterTypeDescription
limitintegerPage size (default varies)
offsetintegerPagination offset
tournamentsstring[]Filter by tournament slugs (e.g., metaculus-cup, aibq3)
statusesstring[]Filter by status: upcoming, closed, resolved, open
forecast_typestring[]Filter by type: binary, multiple_choice, numeric, discrete, date, conditional, group_of_questions, notebook
categoriesstring[]Filter by category slugs (e.g., nuclear, health-pandemics)
forecaster_idintegerPosts where this user has forecasted
not_forecaster_idintegerPosts where this user has NOT forecasted
for_main_feedbooleanFilter for main feed suitability
with_cpbooleanInclude community predictions (default: false). For groups, returns CP for top 3 sub-questions only.
include_cp_historybooleanInclude full CP history per aggregation method (default: false)
include_descriptionsbooleanInclude description, fine_print, resolution_criteria fields
order_bystringSort field. Prefix with - for descending. Options below.
open_time__gtdatetimeOpen time greater than (also supports __gte, __lt, __lte)
published_at__gtdatetimePublished time greater than (also supports __gte, __lt, __lte)
scheduled_resolve_time__gtdatetimeScheduled resolve time greater than (also supports __gte, __lt, __lte)

order_by options:

ValueDescription
published_atPublication time
open_timeWhen forecasting opened
vote_scoreCommunity vote score
comment_countNumber of comments
forecasts_countNumber of forecasts
scheduled_close_timeScheduled close time
scheduled_resolve_timeScheduled resolution time
user_last_forecasts_dateWhen the user last forecasted
unread_comment_countUnread comments
weekly_movementWeekly probability movement
divergenceDivergence metric
hotnessComposite trending score (decays after 3.5 days)
scoreUser forecasting performance (requires forecaster_id)

Response:

{
  "next": "https://www.metaculus.com/api/posts/?limit=20&offset=20",
  "previous": null,
  "results": [ /* array of Post objects */ ]
}

Example: Fetch open binary questions, newest first:

curl -s "https://www.metaculus.com/api/posts/?statuses=open&forecast_type=binary&order_by=-published_at&limit=10&with_cp=true" \
  -H "Authorization: Token $METACULUS_API_TOKEN" | jq '.results[] | {id, title, status}'

Example: Fetch questions from a tournament:

curl -s "https://www.metaculus.com/api/posts/?tournaments=metaculus-cup&with_cp=true&limit=20" \
  -H "Authorization: Token $METACULUS_API_TOKEN" | jq .

Example: Fetch questions you haven't forecasted on yet:

# Replace YOUR_USER_ID with your actual Metaculus user ID
curl -s "https://www.metaculus.com/api/posts/?not_forecaster_id=YOUR_USER_ID&statuses=open&limit=10" \
  -H "Authorization: Token $METACULUS_API_TOKEN" | jq .

GET /api/posts/{postId}/ — Retrieve Post Details

Returns full details for a single post, including all sub-questions and aggregations.

Path Parameters:

ParameterTypeDescription
postIdintegerThe post ID

Example:

curl -s "https://www.metaculus.com/api/posts/12345/" \
  -H "Authorization: Token $METACULUS_API_TOKEN" | jq .

POST /api/questions/forecast/ — Submit Forecasts

Submit one or more forecasts. The request body is a JSON array of forecast objects.

Important: The question field takes the question ID, not the post ID. Get the question ID from post.question.id, post.group_of_questions.questions[].id, or post.conditional.question_yes.id / post.conditional.question_no.id.

Binary Forecast

[
  {
    "question": 12345,
    "probability_yes": 0.63
  }
]
FieldTypeRequiredDescription
questionintegerYesQuestion ID
probability_yesfloatYesProbability between 0 and 1
end_timedatetimeNoAuto-withdraw timestamp

Multiple Choice Forecast

[
  {
    "question": 12345,
    "probability_yes_per_category": {
      "Futurama": 0.5,
      "Paperclipalypse": 0.3,
      "Singularia": 0.2
    }
  }
]
FieldTypeRequiredDescription
questionintegerYesQuestion ID
probability_yes_per_categoryobjectYesMap of option name → probability. Must sum to 1.0.
end_timedatetimeNoAuto-withdraw timestamp

Continuous Forecast (Numeric / Date / Discrete)

[
  {
    "question": 12345,
    "continuous_cdf": [0.0, 0.00005, 0.00010, "... 201 values total ...", 1.0]
  }
]
FieldTypeRequiredDescription
questionintegerYesQuestion ID
continuous_cdffloat[]YesCDF array (see CDF generation section below)
distribution_inputobjectNoSlider values for frontend display
end_timedatetimeNoAuto-withdraw timestamp

Conditional Forecast

Submit forecasts for both the "if Yes" and "if No" questions:

[
  { "question": 111, "probability_yes": 0.499 },
  { "question": 222, "probability_yes": 0.501 }
]

Where 111 is conditional.question_yes.id and 222 is conditional.question_no.id.

Group Forecast

Submit forecasts for multiple sub-questions in a single request:

[
  { "question": 1, "probability_yes": 0.11 },
  { "question": 2, "probability_yes": 0.22 },
  { "question": 3, "probability_yes": 0.33 }
]

Example: Submit a binary forecast with curl:

curl -s -X POST "https://www.metaculus.com/api/questions/forecast/" \
  -H "Authorization: Token $METACULUS_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '[{"question": 12345, "probability_yes": 0.75}]'

Responses:

StatusDescription
201Forecasts submitted successfully
400Invalid request format

POST /api/questions/withdraw/ — Withdraw Forecasts

Withdraw active forecasts. The request body is a JSON array of withdrawal objects.

[
  { "question": 12345 },
  { "question": 12346 }
]

Example:

curl -s -X POST "https://www.metaculus.com/api/questions/withdraw/" \
  -H "Authorization: Token $METACULUS_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '[{"question": 12345}]'

GET /api/comments/ — Retrieve Comments

Fetch comments with filters. Either post or author is required.

Query Parameters:

ParameterTypeRequiredDescription
postintegerOne of post/authorPost ID to filter by
authorintegerOne of post/authorAuthor user ID to filter by
limitintegerNoNumber of comments to retrieve
offsetintegerNoPagination offset
is_privatebooleanNoFilter private vs public (default: false)
use_root_comments_paginationbooleanNoIf true, pagination applies to root comments only; all child comments are included
sortstringNocreated_at (ascending) or -created_at (descending)
focus_comment_idintegerNoPlace this comment at the top of results

Response:

{
  "total_count": 42,
  "count": 15,
  "next": "https://www.metaculus.com/api/comments/?post=123&limit=10&offset=10",
  "previous": null,
  "results": [ /* array of Comment objects */ ]
}
  • total_count — Total root + child comments
  • count — Total root comments only

Example:

curl -s "https://www.metaculus.com/api/comments/?post=12345&sort=-created_at&limit=20" \
  -H "Authorization: Token $METACULUS_API_TOKEN" | jq .

POST /api/comments/create/ — Create a Comment

Request Body:

FieldTypeRequiredDescription
on_postintegerYesPost ID to comment on
textstringYesComment content
included_forecastbooleanYesInclude the user's last forecast
is_privatebooleanYesWhether the comment is private
parentintegerNoParent comment ID (for replies)

Example:

curl -s -X POST "https://www.metaculus.com/api/comments/create/" \
  -H "Authorization: Token $METACULUS_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "on_post": 12345,
    "text": "I updated my forecast based on the latest data.",
    "included_forecast": false,
    "is_private": false
  }'

Responses:

StatusDescription
201Comment created successfully (returns Comment object)
400Invalid request format

GET /api/posts/{postId}/download-data/ — Download Question Data

Downloads forecast data as a ZIP file containing CSVs.

Path Parameters:

ParameterTypeDescription
postIdintegerPost ID (the number after /questions/ in the URL)

Query Parameters:

ParameterTypeRequiredDescription
sub_questionintegerNoSub-question ID for group/conditional questions
aggregation_methodsstring[]NoMethods to include: recency_weighted, unweighted, metaculus_prediction, single_aggregation. Default: recency_weighted only
include_botsbooleanNoInclude bot forecasts in aggregation recalculation
user_idsstring[]NoSpecific user IDs (whitelisted users only). Requires aggregation_methods.
minimizebooleanNoDefault: true. If false, includes all data points (may produce large files)
include_commentsbooleanNoDefault: false. If true, adds comment_data.csv
include_scoresbooleanNoDefault: false. If true, adds score_data.csv

ZIP Contents:

FileDescription
question_data.csvQuestion metadata, scaling, resolution
forecast_data.csvIndividual and aggregated forecasts
comment_data.csvComments (if include_comments=true)
score_data.csvScores (if include_scores=true)

Example:

curl -s "https://www.metaculus.com/api/posts/12345/download-data/?include_comments=true" \
  -H "Authorization: Token $METACULUS_API_TOKEN" \
  -o question_data.zip

GET /api/projects/{projectId}/download-data/ — Download Project Data

Downloads data for an entire project as a ZIP. Only available to site admins and whitelisted users.

Path Parameters:

ParameterTypeDescription
projectIdintegerProject ID

Query Parameters:

ParameterTypeRequiredDescription
include_commentsbooleanNoInclude comments CSV
include_scoresbooleanNoInclude scores CSV

Generating Continuous CDFs

Continuous, numeric, date, and discrete questions require forecasts as a CDF (Cumulative Distribution Function). This section explains how to generate valid CDFs.

CDF Rules

  1. Length: The CDF must have exactly inbound_outcome_count + 1 values. For most continuous questions, this is 201 values. For discrete questions, it depends on the question.

  2. Bounds:

    • If open_lower_bound == false (closed): first value must be 0.0
    • If open_lower_bound == true (open): first value must be ≥ 0.001 (at least 0.1% mass below lower bound)
    • If open_upper_bound == false (closed): last value must be 1.0
    • If open_upper_bound == true (open): last value must be ≤ 0.999 (at least 0.1% mass above upper bound)
  3. Monotonicity: The CDF must be strictly increasing by at least 0.01 / inbound_outcome_count per step (i.e., 0.00005 per step for the standard 200).

  4. Maximum step: No two adjacent values may differ by more than 0.2 * (200 / inbound_outcome_count).

Understanding Scaling

The CDF values correspond to evenly spaced points across the internal [0, 1] range. To map real-world values to CDF positions:

  • Linear scaling (zero_point is null): internal = (value - range_min) / (range_max - range_min)
  • Logarithmic scaling (zero_point is set): requires log transformation (see function below)
  • Date questions: convert ISO dates to unix timestamps first, then apply scaling

Python: Nominal Value to CDF Location

import datetime
import numpy as np

def nominal_location_to_cdf_location(
    nominal_location: str | float,
    question_data: dict,
) -> float:
    """Takes a location in nominal format (e.g. 123, "123",
    or datetime in iso format) and scales it to metaculus's
    "internal representation" range [0,1] incorporating question scaling"""
    if question_data["type"] == "date":
        scaled_location = datetime.datetime.fromisoformat(
            nominal_location
        ).timestamp()
    else:
        scaled_location = float(nominal_location)
    scaling = question_data["scaling"]
    range_min = scaling.get("range_min")
    range_max = scaling.get("range_max")
    zero_point = scaling.get("zero_point")
    if zero_point is not None:
        # logarithmically scaled question
        deriv_ratio = (range_max - zero_point) / (range_min - zero_point)
        unscaled_location = (
            np.log(
                (scaled_location - range_min) * (deriv_ratio - 1)
                + (range_max - range_min)
            )
            - np.log(range_max - range_min)
        ) / np.log(deriv_ratio)
    else:
        # linearly scaled question
        unscaled_location = (scaled_location - range_min) / (
            range_max - range_min
        )
    return unscaled_location

Python: Generate CDF from Percentiles

def generate_continuous_cdf(
    percentiles: dict,
    question_data: dict,
    below_lower_bound: float = None,
    above_upper_bound: float = None,
) -> list[float]:
    """
    Takes a set of percentiles and returns a corresponding CDF with
    inbound_outcome_count + 1 values (typically 201).

    percentiles: dict mapping percentile keys to nominal values
      Keys must end in a number interpretable as a float in (0, 100).
      Values are in the question's real-world scale.
      Example:
        {
          "percentile_05": 25,
          "percentile_25": 500,
          "percentile_50": 650,
          "percentile_75": 700,
          "percentile_95": 990,
        }

    below_lower_bound: probability mass below range_min (for open lower bound)
    above_upper_bound: probability mass above range_max (for open upper bound)
    """
    percentile_locations = []
    if below_lower_bound is not None:
        percentile_locations.append((0.0, below_lower_bound))
    if above_upper_bound is not None:
        percentile_locations.append((1.0, 1 - above_upper_bound))
    for percentile, nominal_location in percentiles.items():
        height = float(str(percentile).split("_")[-1]) / 100
        location = nominal_location_to_cdf_location(
            nominal_location, question_data
        )
        percentile_locations.append((location, height))
    percentile_locations.sort()
    first_point, last_point = percentile_locations[0], percentile_locations[-1]
    if (first_point[0] > 0.0) or (last_point[0] < 1.0):
        raise ValueError(
            "Percentiles must encompass bounds of the question"
        )

    def get_cdf_at(location):
        previous = percentile_locations[0]
        for i in range(1, len(percentile_locations)):
            current = percentile_locations[i]
            if previous[0] <= location <= current[0]:
                return previous[1] + (current[1] - previous[1]) * (
                    location - previous[0]
                ) / (current[0] - previous[0])
            previous = current

    n_points = (question_data.get("inbound_outcome_count") or 200) + 1
    continuous_cdf = [get_cdf_at(i / (n_points - 1)) for i in range(n_points)]
    return continuous_cdf

Python: Standardize CDF for Submission

This function ensures your CDF satisfies all validation rules. It adds a small linear component to guarantee monotonicity and respects bound constraints.

def standardize_cdf(cdf, question_data: dict):
    """
    Standardize a CDF for submission:
    - No mass outside closed bounds
    - Minimum mass outside open bounds
    - Strictly increasing by at least the minimum step
    - Caps maximum step size
    """
    lower_open = question_data["open_lower_bound"]
    upper_open = question_data["open_upper_bound"]
    inbound_outcome_count = question_data.get("inbound_outcome_count") or 200
    default_inbound_outcome_count = 200

    cdf = np.asarray(cdf, dtype=float)
    if not cdf.size:
        return []

    scale_lower_to = 0 if lower_open else cdf[0]
    scale_upper_to = 1.0 if upper_open else cdf[-1]
    rescaled_inbound_mass = scale_upper_to - scale_lower_to

    def standardize(F: float, location: float) -> float:
        rescaled_F = (F - scale_lower_to) / rescaled_inbound_mass
        if lower_open and upper_open:
            return 0.988 * rescaled_F + 0.01 * location + 0.001
        elif lower_open:
            return 0.989 * rescaled_F + 0.01 * location + 0.001
        elif upper_open:
            return 0.989 * rescaled_F + 0.01 * location
        return 0.99 * rescaled_F + 0.01 * location

    for i, value in enumerate(cdf):
        cdf[i] = standardize(value, i / (len(cdf) - 1))

    pmf = np.diff(cdf, prepend=0, append=1)
    cap = 0.2 * (default_inbound_outcome_count / inbound_outcome_count)

    def cap_pmf(scale: float) -> np.ndarray:
        return np.concatenate(
            [pmf[:1], np.minimum(cap, scale * pmf[1:-1]), pmf[-1:]]
        )

    def capped_sum(scale: float) -> float:
        return float(cap_pmf(scale).sum())

    lo = hi = scale = 1.0
    while capped_sum(hi) < 1.0:
        hi *= 1.2
    for _ in range(100):
        scale = 0.5 * (lo + hi)
        s = capped_sum(scale)
        if s < 1.0:
            lo = scale
        else:
            hi = scale
        if s == 1.0 or (hi - lo) < 2e-5:
            break

    pmf = cap_pmf(scale)
    pmf[1:-1] *= (cdf[-1] - cdf[0]) / pmf[1:-1].sum()
    cdf = np.cumsum(pmf)[:-1]
    cdf = np.round(cdf, 10)
    return cdf.tolist()

End-to-End Example: Submit a Continuous Forecast

import os, requests, numpy as np

TOKEN = os.environ["METACULUS_API_TOKEN"]
HEADERS = {"Authorization": f"Token {TOKEN}"}
BASE = "https://www.metaculus.com"

# 1. Fetch the question
post_id = 12345
resp = requests.get(f"{BASE}/api/posts/{post_id}/", headers=HEADERS)
resp.raise_for_status()
post = resp.json()
question = post["question"]

# 2. Define your percentile beliefs (in real-world units)
percentiles = {
    "percentile_05": 25,
    "percentile_25": 40,
    "percentile_50": 55,
    "percentile_75": 70,
    "percentile_95": 90,
}

# 3. Generate and standardize the CDF
raw_cdf = generate_continuous_cdf(
    percentiles,
    question,
    below_lower_bound=0.01,
    above_upper_bound=0.01,
)
final_cdf = standardize_cdf(raw_cdf, question)

# 4. Submit
resp = requests.post(
    f"{BASE}/api/questions/forecast/",
    headers={**HEADERS, "Content-Type": "application/json"},
    json=[{"question": question["id"], "continuous_cdf": final_cdf}],
)
resp.raise_for_status()
print("Forecast submitted!")

Common Patterns

Browse the Feed

# Newest open questions
curl -s "https://www.metaculus.com/api/posts/?statuses=open&order_by=-published_at&limit=10&with_cp=true" \
  -H "Authorization: Token $METACULUS_API_TOKEN" | jq '.results[] | {id, title, status}'

Get Community Prediction for a Post

curl -s "https://www.metaculus.com/api/posts/12345/" \
  -H "Authorization: Token $METACULUS_API_TOKEN" \
  | jq '.question.aggregations.recency_weighted.latest'

Find Trending Questions

curl -s "https://www.metaculus.com/api/posts/?order_by=-hotness&statuses=open&limit=10&with_cp=true" \
  -H "Authorization: Token $METACULUS_API_TOKEN" | jq '.results[] | {id, title}'

Find Questions by Category

curl -s "https://www.metaculus.com/api/posts/?categories=nuclear&statuses=open&with_cp=true&limit=10" \
  -H "Authorization: Token $METACULUS_API_TOKEN" | jq '.results[] | {id, title}'

Find Questions in a Tournament

curl -s "https://www.metaculus.com/api/posts/?tournaments=aibq3&with_cp=true&limit=50" \
  -H "Authorization: Token $METACULUS_API_TOKEN" | jq '.results[] | {id, title, status}'

Submit a Binary Forecast

curl -s -X POST "https://www.metaculus.com/api/questions/forecast/" \
  -H "Authorization: Token $METACULUS_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '[{"question": 12345, "probability_yes": 0.75}]'

Submit a Multiple Choice Forecast

curl -s -X POST "https://www.metaculus.com/api/questions/forecast/" \
  -H "Authorization: Token $METACULUS_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '[{
    "question": 12345,
    "probability_yes_per_category": {
      "Option A": 0.4,
      "Option B": 0.35,
      "Option C": 0.25
    }
  }]'

Withdraw a Forecast

curl -s -X POST "https://www.metaculus.com/api/questions/withdraw/" \
  -H "Authorization: Token $METACULUS_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '[{"question": 12345}]'

Read Comments on a Post

curl -s "https://www.metaculus.com/api/comments/?post=12345&sort=-created_at&limit=20" \
  -H "Authorization: Token $METACULUS_API_TOKEN" | jq '.results[] | {author: .author.username, text: .text}'

Post a Comment

curl -s -X POST "https://www.metaculus.com/api/comments/create/" \
  -H "Authorization: Token $METACULUS_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "on_post": 12345,
    "text": "My analysis suggests a higher probability because...",
    "included_forecast": true,
    "is_private": false
  }'

Reply to a Comment

curl -s -X POST "https://www.metaculus.com/api/comments/create/" \
  -H "Authorization: Token $METACULUS_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "on_post": 12345,
    "parent": 67890,
    "text": "Good point — I updated my forecast accordingly.",
    "included_forecast": false,
    "is_private": false
  }'

Python: Paginate Through All Open Questions

import os, requests

TOKEN = os.environ["METACULUS_API_TOKEN"]
HEADERS = {"Authorization": f"Token {TOKEN}"}
BASE = "https://www.metaculus.com"

all_posts = []
offset = 0
limit = 100

while True:
    resp = requests.get(
        f"{BASE}/api/posts/",
        headers=HEADERS,
        params={
            "statuses": "open",
            "limit": limit,
            "offset": offset,
            "with_cp": True,
        },
    )
    resp.raise_for_status()
    data = resp.json()
    results = data["results"]
    all_posts.extend(results)
    if data["next"] is None:
        break
    offset += limit

print(f"Fetched {len(all_posts)} open posts")

Python: Find Questions with Large Movement

import os, requests

TOKEN = os.environ["METACULUS_API_TOKEN"]
HEADERS = {"Authorization": f"Token {TOKEN}"}
BASE = "https://www.metaculus.com"

resp = requests.get(
    f"{BASE}/api/posts/",
    headers=HEADERS,
    params={
        "statuses": "open",
        "order_by": "-weekly_movement",
        "forecast_type": "binary",
        "with_cp": True,
        "limit": 10,
    },
)
resp.raise_for_status()
for post in resp.json()["results"]:
    q = post.get("question")
    if q and q.get("aggregations"):
        latest = q["aggregations"].get("recency_weighted", {}).get("latest")
        if latest:
            prob = latest.get("centers", [None])[0]
            print(f"[{post['id']}] {post['title']} — CP: {prob}")

Question Type Quick Reference

TypeForecast FormatKey Fields
binaryprobability_yes: float (0–1)
multiple_choiceprobability_yes_per_category: {option: float} (sum = 1.0)options, all_options_ever
numericcontinuous_cdf: float[] (201 values)scaling, open_lower_bound, open_upper_bound, unit
datecontinuous_cdf: float[] (201 values)scaling (range_min/max are unix timestamps)
discretecontinuous_cdf: float[] (inbound_outcome_count + 1 values)inbound_outcome_count, scaling

Post Status Lifecycle

draft → pending → approved → open → closed → resolved
                → rejected
StatusDescription
draftAuthor is still editing
pendingSubmitted for curation review
rejectedRejected by curators
openApproved and accepting forecasts
upcomingApproved but not yet open for forecasting
closedNo longer accepting forecasts; awaiting resolution
resolvedFinal outcome determined

Error Handling

Status CodeMeaningAction
200Success
201Created
400Bad requestCheck request format, CDF validity, required fields
401UnauthorizedCheck your Authorization: Token ... header
403ForbiddenYou don't have permission (e.g., project data download)
404Not foundCheck the post/question/project ID
429Rate limitedImplement exponential backoff and retry
500Server errorRetry after a delay; if persistent, contact Metaculus

Usage Tips

  • Always pass with_cp=true when listing posts if you need community predictions — aggregations are empty by default.
  • Use include_cp_history=true only when you need historical CP data — it increases response size significantly.
  • Use include_descriptions=true only when needed — descriptions can be large.
  • Question ID ≠ Post ID — Forecasts use the question.id, not the post.id. Always fetch the post first to get the question ID.
  • For group posts, with_cp=true on the list endpoint only returns CP for the top 3 sub-questions. Use the detail endpoint (/api/posts/{postId}/) for all sub-questions.
  • CDF generation is the trickiest part — use the provided Python functions or adapt them. Always run standardize_cdf() before submitting.
  • Combine filters for targeted queries — e.g., statuses=open&forecast_type=binary&categories=nuclear narrows results efficiently.
  • Paginate responsibly — use limit and offset. Don't fetch all posts at once.
  • The order_by=score sort requires forecaster_id — it ranks questions by a specific user's performance.
  • Date questions use unix timestamps in scaling.range_min and scaling.range_max — convert ISO dates to timestamps before mapping to CDF locations.
  • Respect rate limits — implement backoff when you receive 429 responses.

Resources

ResourceURL
Metaculus Platformmetaculus.com
API Documentationmetaculus.com/api
Account Settings (API Token)metaculus.com/accounts/settings
GitHub Issuesgithub.com/Metaculus/metaculus
Contactapi-requests@metaculus.com

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

google-news

No summary provided by upstream source.

Repository SourceNeeds Review
General

openfootball

No summary provided by upstream source.

Repository SourceNeeds Review
General

kalshi

No summary provided by upstream source.

Repository SourceNeeds Review
metaculus | V50.AI