> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/Metaculus/metaculus/llms.txt
> Use this file to discover all available pages before exploring further.

# Forecasts

> Submit and manage predictions

## Overview

The Forecasts endpoints allow you to submit predictions on questions and withdraw existing forecasts. All forecasts are submitted as arrays to support single questions, groups of questions, and conditional questions in a unified interface.

<Note>
  Before submitting forecasts, retrieve the question details from `/api/posts/{postId}/` or `/api/questions/{questionId}/` to understand the question type, bounds, and current status.
</Note>

***

## Submit Forecasts

<CodeGroup>
  ```bash Binary theme={null}
  curl -X POST "https://www.metaculus.com/api/questions/forecast/" \
    -H "Authorization: Token YOUR_TOKEN" \
    -H "Content-Type: application/json" \
    -d '[{
      "question": 1,
      "probability_yes": 0.63
    }]'
  ```

  ```bash Multiple Choice theme={null}
  curl -X POST "https://www.metaculus.com/api/questions/forecast/" \
    -H "Authorization: Token YOUR_TOKEN" \
    -H "Content-Type: application/json" \
    -d '[{
      "question": 20772,
      "probability_yes_per_category": {
        "Democratic": 0.5,
        "Republican": 0.3,
        "Libertarian": 0.1,
        "Green": 0.05,
        "Other": 0.05
      }
    }]'
  ```

  ```python Binary theme={null}
  import requests

  response = requests.post(
      "https://www.metaculus.com/api/questions/forecast/",
      headers={
          "Authorization": "Token YOUR_TOKEN",
          "Content-Type": "application/json"
      },
      json=[{
          "question": 1,
          "probability_yes": 0.63
      }]
  )
  print(response.status_code)
  ```

  ```python Continuous (Numeric) theme={null}
  import requests
  import numpy as np

  # Generate a simple CDF for a continuous question
  # This example assumes closed bounds
  cdf = []
  for i in range(201):
      cdf.append(i / 200.0)

  response = requests.post(
      "https://www.metaculus.com/api/questions/forecast/",
      headers={
          "Authorization": "Token YOUR_TOKEN",
          "Content-Type": "application/json"
      },
      json=[{
          "question": 3530,
          "continuous_cdf": cdf
      }]
  )
  print(response.status_code)
  ```
</CodeGroup>

`POST /api/questions/forecast/`

Submit one or more forecasts. The request body must always be an array, even for single questions.

### Request Body

The request body is an array of forecast objects. The structure depends on the question type:

#### Binary Questions

<ParamField body="question" type="integer" required>
  The question ID
</ParamField>

<ParamField body="probability_yes" type="number" required>
  Probability between 0 and 1 that the answer is "yes"
</ParamField>

<ParamField body="end_time" type="string (datetime)">
  Optional: When to automatically withdraw this forecast
</ParamField>

#### Multiple Choice Questions

<ParamField body="question" type="integer" required>
  The question ID
</ParamField>

<ParamField body="probability_yes_per_category" type="object" required>
  Object mapping option names to probabilities. Values must sum to 1.0.

  Example:

  ```json theme={null}
  {
    "Democratic": 0.5,
    "Republican": 0.3,
    "Other": 0.2
  }
  ```
</ParamField>

<ParamField body="end_time" type="string (datetime)">
  Optional: When to automatically withdraw this forecast
</ParamField>

#### Continuous Questions (Numeric, Date, Discrete)

<ParamField body="question" type="integer" required>
  The question ID (must be the question ID, not the post ID)
</ParamField>

<ParamField body="continuous_cdf" type="array" required>
  Array of CDF values. Length depends on `inbound_outcome_count` (default: 201 values for continuous, 11 for discrete with 10 outcomes).

  **Important CDF Requirements:**

  1. **Strictly Increasing**: Each value must be at least 0.00005 (0.01/200) greater than the previous value
  2. **Maximum Step**: No step can increase by more than 0.2 (or scaled based on outcome count)
  3. **Boundary Conditions**:
     * Closed lower bound: First value must be 0.0
     * Open lower bound: First value must be ≥ 0.001
     * Closed upper bound: Last value must be 1.0
     * Open upper bound: Last value must be ≤ 0.999

  See the OpenAPI spec documentation for helper functions to generate valid CDFs.
</ParamField>

<ParamField body="end_time" type="string (datetime)">
  Optional: When to automatically withdraw this forecast
</ParamField>

#### Conditional Questions

For conditional questions, submit forecasts for both the "if yes" and "if no" scenarios:

```json theme={null}
[
  {
    "question": 21477,
    "probability_yes": 0.499
  },
  {
    "question": 21476,
    "probability_yes": 0.501
  }
]
```

#### Group of Questions

For groups, submit a forecast for each question in the group:

```json theme={null}
[
  { "question": 10880, "probability_yes": 0.11 },
  { "question": 10923, "probability_yes": 0.22 },
  { "question": 10924, "probability_yes": 0.33 }
]
```

### Response

<ResponseField name="201 Created" type="Empty object">
  Forecasts submitted successfully
</ResponseField>

<ResponseField name="400 Bad Request" type="object">
  Invalid request format or validation error

  <ResponseField name="error" type="string">
    Error description
  </ResponseField>
</ResponseField>

<ResponseField name="405 Method Not Allowed" type="object">
  Question is not open for forecasting

  <ResponseField name="error" type="string">
    Reason why forecasting is not allowed
  </ResponseField>
</ResponseField>

### Continuous CDF Guidelines

<Note>
  **Generating Valid CDFs**

  Creating a valid CDF can be tricky. Here are the key steps:

  1. **Check question scaling**: Look at `question.scaling` to get `range_min`, `range_max`, `zero_point`, and boundary settings
  2. **Determine your percentiles**: Identify key percentiles of your belief (e.g., 5th, 25th, 50th, 75th, 95th)
  3. **Generate the CDF**: Linearly interpolate between your percentiles
  4. **Standardize**: Add a small uniform component to ensure monotonicity

  See the OpenAPI spec for complete Python helper functions.
</Note>

#### Example: Binary Forecast

```json theme={null}
[
  {
    "question": 1,
    "probability_yes": 0.75
  }
]
```

#### Example: Numeric Forecast

```json theme={null}
[
  {
    "question": 3530,
    "continuous_cdf": [
      0.0000, 0.0001, 0.0002, ...198 more values..., 0.9998, 0.9999, 1.0000
    ]
  }
]
```

#### Example: Discrete Forecast (10 outcomes)

```json theme={null}
[
  {
    "question": 1234,
    "continuous_cdf": [0, 0.05, 0.1, 0.15, 0.2, 0.35, 0.6, 0.75, 0.85, 0.95, 1.0]
  }
]
```

***

## Withdraw Forecasts

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://www.metaculus.com/api/questions/withdraw/" \
    -H "Authorization: Token YOUR_TOKEN" \
    -H "Content-Type: application/json" \
    -d '[{
      "question": 1
    }]'
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      "https://www.metaculus.com/api/questions/withdraw/",
      headers={
          "Authorization": "Token YOUR_TOKEN",
          "Content-Type": "application/json"
      },
      json=[{
          "question": 1
      }]
  )
  print(response.status_code)
  ```
</CodeGroup>

`POST /api/questions/withdraw/`

Withdraw your current forecast(s) from one or more questions. This removes your prediction from the community aggregation going forward.

### Request Body

<ParamField body="question" type="integer" required>
  The question ID to withdraw from
</ParamField>

### Response

<ResponseField name="201 Created" type="Empty object">
  Forecast(s) withdrawn successfully
</ResponseField>

<ResponseField name="400 Bad Request" type="object">
  Invalid request format
</ResponseField>

### Example: Withdraw from Multiple Questions

```json theme={null}
[
  { "question": 1 },
  { "question": 2 },
  { "question": 3 }
]
```

***

## Important Notes

<Warning>
  **Forecast Validation**

  * Questions must be in `open` status
  * Current time must be between `open_time` and `scheduled_close_time`
  * You must have `forecaster` permission on the question's post
  * CDFs must be strictly monotonic and respect boundary conditions
</Warning>

<Note>
  **Automatic Withdrawals**

  You can set an `end_time` on forecasts to have them automatically withdrawn. This is useful for:

  * Testing temporary predictions
  * Setting expiring forecasts that represent near-term beliefs
  * Implementing time-based forecast strategies
</Note>

<Note>
  **Question IDs vs Post IDs**

  Always use the **question ID** in forecast submissions, not the post ID. For posts with single questions, these IDs are often the same, but for groups and conditionals, they differ. Get question IDs from the post details endpoint.
</Note>
