Metaculus — Tool Reference
Browse Metaculus community forecasts, submit and withdraw predictions, and track your own forecasting record
Your AI assistant discovers and invokes these tools through the
MCP Server's meta tools —
it calls execute_tool with the tool name and arguments below.
# download_question_data read
Download a Metaculus question's CSV export (question, forecast, score and comment data) — a restricted endpoint that returns an access hint when refused.
Full description
Download the CSV-zip export for a post, question or project. Supply at least one of `post_id`, `question_id` or `project_id` (`project_id` only works if your account is whitelisted for that project). This is the richest source of calibration history — full forecast series and per-user scores rather than a single aggregate snapshot. **The endpoint is restricted.** Without approval Metaculus answers 401/403, and this tool returns `status="access_denied"` with an `access_hint` and the application link instead of raising — every other failure (404, 429, 5xx, transport) still raises. Caps, applied in this order: the zip is streamed under BOTH an 8 MB ceiling and this invocation's remaining time budget — exceeding either abandons the download and returns `status="too_large"` or `status="timed_out"` with no files, and Metaculus's asynchronous /api/data/email/ export (which this toolkit does not wrap) is the route for those. Reading the archive is a SECOND bounded phase with its own share of the remaining budget, so an export that downloads but cannot be opened in time is reported as such rather than running the invocation into its ceiling. Then at most 1000 archive members are listed (`members_total` and `members_truncated` say when there were more), each CSV is read up to 20000 characters, and the whole response stops adding text at 40000 characters. Files past the TEXT budgets are still LISTED with their sizes, just with `text=null`; members past the 1000-member cap are not listed at all, only counted. Set `include_file_text=false` for the listing only.
| Parameter | Type | Required | Description |
|---|---|---|---|
post_id | integer | null | No | |
question_id | integer | null | No | |
project_id | integer | null | No | Only accepted when your account is whitelisted for that project. |
sub_question | integer | null | No | Restrict a group/conditional export to one subquestion. |
aggregation_methods | string | null | No | Comma-separated subset of recency_weighted, unweighted, metaculus_prediction, single_aggregation — or 'all'. Omit for recency_weighted only; supplying it triggers a recalculation upstream. |
include_comments | boolean | null | No | |
include_scores | boolean | null | No | |
include_key_factors | boolean | null | No | |
include_file_text | boolean | No | Return CSV text, not just the file listing. Default: true |
Parameter schema (JSON)
{
"properties": {
"post_id": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Post Id"
},
"question_id": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Question Id"
},
"project_id": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Only accepted when your account is whitelisted for that project.",
"title": "Project Id"
},
"sub_question": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Restrict a group/conditional export to one subquestion.",
"title": "Sub Question"
},
"aggregation_methods": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Comma-separated subset of recency_weighted, unweighted, metaculus_prediction, single_aggregation — or 'all'. Omit for recency_weighted only; supplying it triggers a recalculation upstream.",
"title": "Aggregation Methods"
},
"include_comments": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"default": null,
"title": "Include Comments"
},
"include_scores": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"default": null,
"title": "Include Scores"
},
"include_key_factors": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"default": null,
"title": "Include Key Factors"
},
"include_file_text": {
"default": true,
"description": "Return CSV text, not just the file listing.",
"title": "Include File Text",
"type": "boolean"
}
},
"title": "DownloadQuestionDataParams",
"type": "object",
"additionalProperties": false
} Result schema (JSON)
{
"$defs": {
"AccessHint": {
"description": "Machine-readable note that a response was shortened by access tier.",
"properties": {
"code": {
"description": "tier_gated (data withheld for this token) or restricted_endpoint (the endpoint itself needs approval).",
"title": "Code",
"type": "string"
},
"message": {
"description": "Human-readable explanation of what is missing.",
"title": "Message",
"type": "string"
},
"data_needs_form_url": {
"default": "https://docs.google.com/forms/d/e/1FAIpQLSeJhtZzHl5qMvBjbXbatyaqoS4IU7RE0GGw_vlhs6I9syqn1g/viewform?usp=pp_url&entry.192763438=https://www.metaculus.com/api/",
"description": "Metaculus Data Needs form — the free Bot Benchmarking tier and bespoke grants are both requested here.",
"title": "Data Needs Form Url",
"type": "string"
},
"contact_email": {
"default": "api-requests@metaculus.com",
"description": "Metaculus API contact address.",
"title": "Contact Email",
"type": "string"
},
"unlock_note": {
"default": "Submitting a forecast on a question permanently unlocks that question's text and resolution for your account, so access widens as you forecast.",
"description": "How access grows without an upgrade.",
"title": "Unlock Note",
"type": "string"
}
},
"required": [
"code",
"message"
],
"title": "AccessHint",
"type": "object"
},
"ZipMember": {
"properties": {
"name": {
"title": "Name",
"type": "string"
},
"compressed_bytes": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Compressed Bytes"
},
"uncompressed_bytes": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Size declared by the archive, not measured.",
"title": "Uncompressed Bytes"
},
"text": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "CSV text, present only when include_file_text was true and the per-file / total character budget still had room.",
"title": "Text"
},
"text_truncated": {
"default": false,
"title": "Text Truncated",
"type": "boolean"
}
},
"required": [
"name"
],
"title": "ZipMember",
"type": "object"
}
},
"properties": {
"status": {
"description": "ok, access_denied, too_large, timed_out, or empty. `timed_out` means the download or the archive read stopped ITSELF because this invocation ran out of time — nothing is still in flight, and no forecast or comment was written either way, so retrying is safe. A download abandoned in a way this side could not stop raises instead of returning, so it can say so.",
"title": "Status",
"type": "string"
},
"http_status": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Http Status"
},
"requested": {
"additionalProperties": true,
"title": "Requested",
"type": "object"
},
"zip_bytes": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Zip Bytes"
},
"files": {
"description": "The archive's members, capped at 1000. When the archive holds more, `members_truncated` is true and `members_total` says how many there were; the ones past the cap are neither listed nor read.",
"items": {
"$ref": "#/$defs/ZipMember"
},
"title": "Files",
"type": "array"
},
"members_total": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "How many members the archive's central directory declared — which can exceed len(files). Null unless the archive was opened, i.e. on every status other than `ok`.",
"title": "Members Total"
},
"members_truncated": {
"default": false,
"description": "True when the archive held more than the 1000 members `files` lists. Independent of `text_truncated` on a member, which is about one file's text rather than about the list.",
"title": "Members Truncated",
"type": "boolean"
},
"total_text_chars": {
"default": 0,
"title": "Total Text Chars",
"type": "integer"
},
"notes": {
"items": {
"type": "string"
},
"title": "Notes",
"type": "array"
},
"access_hint": {
"anyOf": [
{
"$ref": "#/$defs/AccessHint"
},
{
"type": "null"
}
],
"default": null
}
},
"required": [
"status",
"requested"
],
"title": "DownloadDataResult",
"type": "object"
} # get_access_status read
Probe what your Metaculus token can actually see: how much Community Prediction data comes back, and whether bulk export works.
Full description
Measure this token's real data access. Metaculus exposes NO endpoint that reports your access tier, so this probes empirically: it samples open posts and resolved posts with the Community Prediction requested, counts how many came back with one, and asks /api/data/download/ for a single post to see whether bulk CSV export is permitted (the export body is never downloaded — only its status). `tier_suggestion` is a Toolforest-side LABEL over those counts, not a Metaculus fact: `no_cp_in_sample` when nothing carried a CP, `base_tier_likely` under 25% coverage, `expanded_tier_likely` at or above it. `tier_suggestion_basis` spells out the arithmetic. Nothing in this toolkit gates a request on this label. The counts describe THE SAMPLE — the default feed ordering for each status — not the whole site, and CP coverage is question-specific, so a different sample can give a different fraction. Re-run it after an access upgrade to confirm the change landed. This tool makes up to four sequential requests, and EVERY one of them — including the first sample — is admitted only if enough of the invocation's time budget is left to start it. A phase that is not admitted is SKIPPED rather than run into the Lambda ceiling, and rather than raised: nothing was sent, so the tool still returns whatever the earlier phases learned. A skipped phase is never dressed up as evidence. Its sample count stays 0 but its status field says `insufficient_budget`, so `open_sampled = 0` means "not sampled" there and "sampled and empty" under `sampled` — the two are never conflated. When NO sample ran, `tier_suggestion` is `insufficient_budget` rather than `no_cp_in_sample`, and no tier hint is emitted, because an unsampled feed is evidence of nothing. When the samples ran but the feeds returned no posts at all, the label is `no_posts_in_sample` for the same reason. `notes` names every skipped phase, and the export probe reports `attempted=false`.
| Parameter | Type | Required | Description |
|---|---|---|---|
sample_size | integer | No | Posts to sample per status (open and resolved are sampled separately). Default: 40 |
probe_post_id | integer | null | No | Post id for the /api/data/download/ probe. Defaults to the first post in the open sample. |
Parameter schema (JSON)
{
"properties": {
"sample_size": {
"default": 40,
"description": "Posts to sample per status (open and resolved are sampled separately).",
"maximum": 100,
"minimum": 1,
"title": "Sample Size",
"type": "integer"
},
"probe_post_id": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Post id for the /api/data/download/ probe. Defaults to the first post in the open sample.",
"title": "Probe Post Id"
}
},
"title": "GetAccessStatusParams",
"type": "object",
"additionalProperties": false
} Result schema (JSON)
{
"$defs": {
"AccessHint": {
"description": "Machine-readable note that a response was shortened by access tier.",
"properties": {
"code": {
"description": "tier_gated (data withheld for this token) or restricted_endpoint (the endpoint itself needs approval).",
"title": "Code",
"type": "string"
},
"message": {
"description": "Human-readable explanation of what is missing.",
"title": "Message",
"type": "string"
},
"data_needs_form_url": {
"default": "https://docs.google.com/forms/d/e/1FAIpQLSeJhtZzHl5qMvBjbXbatyaqoS4IU7RE0GGw_vlhs6I9syqn1g/viewform?usp=pp_url&entry.192763438=https://www.metaculus.com/api/",
"description": "Metaculus Data Needs form — the free Bot Benchmarking tier and bespoke grants are both requested here.",
"title": "Data Needs Form Url",
"type": "string"
},
"contact_email": {
"default": "api-requests@metaculus.com",
"description": "Metaculus API contact address.",
"title": "Contact Email",
"type": "string"
},
"unlock_note": {
"default": "Submitting a forecast on a question permanently unlocks that question's text and resolution for your account, so access widens as you forecast.",
"description": "How access grows without an upgrade.",
"title": "Unlock Note",
"type": "string"
}
},
"required": [
"code",
"message"
],
"title": "AccessHint",
"type": "object"
},
"DataDownloadProbe": {
"properties": {
"attempted": {
"title": "Attempted",
"type": "boolean"
},
"post_id": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Post Id"
},
"http_status": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Http Status"
},
"accessible": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"default": null,
"description": "True when the probe got a 2xx, false when it got any other status. Null means unknown: either no probe ran (attempted=false) or the probe ran but could not complete, so no status came back — `note` says which.",
"title": "Accessible"
},
"note": {
"title": "Note",
"type": "string"
}
},
"required": [
"attempted",
"note"
],
"title": "DataDownloadProbe",
"type": "object"
}
},
"properties": {
"user_id": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "User Id"
},
"user_id_source": {
"description": "provided, users_me_endpoint, unavailable, or insufficient_budget. `insufficient_budget` means the probe was never run because too little of the invocation's time was left — it is not evidence that the id could not be resolved.",
"title": "User Id Source",
"type": "string"
},
"open_sampled": {
"description": "Posts returned by the open sample. Read it together with `open_sample_status`: 0 with status `insufficient_budget` means NOT SAMPLED, not 'sampled and empty'.",
"title": "Open Sampled",
"type": "integer"
},
"open_with_cp": {
"title": "Open With Cp",
"type": "integer"
},
"open_sample_status": {
"default": "sampled",
"description": "`sampled` when the open feed sample ran (whatever it returned), `insufficient_budget` when it was skipped because too little of this invocation's time budget was left to start it. Nothing was sent in that case, so retrying is safe.",
"enum": [
"sampled",
"insufficient_budget"
],
"title": "Open Sample Status",
"type": "string"
},
"resolved_sampled": {
"description": "Posts returned by the resolved sample. Read it together with `resolved_sample_status`, exactly as for `open_sampled`.",
"title": "Resolved Sampled",
"type": "integer"
},
"resolved_with_cp": {
"title": "Resolved With Cp",
"type": "integer"
},
"resolved_sample_status": {
"default": "sampled",
"description": "`sampled` or `insufficient_budget`, with the same meaning as `open_sample_status`.",
"enum": [
"sampled",
"insufficient_budget"
],
"title": "Resolved Sample Status",
"type": "string"
},
"data_download": {
"$ref": "#/$defs/DataDownloadProbe"
},
"tier_suggestion": {
"description": "Heuristic label: insufficient_budget (no sample ran, so there is no evidence either way), no_posts_in_sample (the samples ran and returned no posts at all), no_cp_in_sample, base_tier_likely, or expanded_tier_likely. Only the last three are statements about access.",
"title": "Tier Suggestion",
"type": "string"
},
"tier_suggestion_basis": {
"description": "The counts and the Toolforest-side thresholds behind the suggestion. Metaculus publishes no tier endpoint and no such thresholds; these gate nothing, they only label the evidence.",
"title": "Tier Suggestion Basis",
"type": "string"
},
"notes": {
"items": {
"type": "string"
},
"title": "Notes",
"type": "array"
},
"access_hint": {
"anyOf": [
{
"$ref": "#/$defs/AccessHint"
},
{
"type": "null"
}
],
"default": null
}
},
"required": [
"user_id_source",
"open_sampled",
"open_with_cp",
"resolved_sampled",
"resolved_with_cp",
"data_download",
"tier_suggestion",
"tier_suggestion_basis"
],
"title": "AccessStatusResult",
"type": "object"
} # get_my_comments read
List your own Metaculus comments, optionally scoped to one post or to your private notes.
Full description
List comments you have written. Metaculus requires comment reads to be filtered by author (or restricted to staff authors), so this tool always sends `author=<your id>` — it cannot fetch another user's comments. Comment bodies are cut at 2000 characters; each comment says whether it was (`text_truncated`). Set `is_private=True` for your private notes instead of public comments. Your user id is resolved automatically through Metaculus's undocumented /api/users/me/ endpoint; pass `user_id` to skip that probe.
| Parameter | Type | Required | Description |
|---|---|---|---|
user_id | integer | null | No | Your Metaculus user id. Omit to resolve it automatically. |
post_id | integer | null | No | Only comments on this post. |
is_private | boolean | null | No | True for your private notes, False for public comments. Metaculus defaults to public when omitted. |
sort | "created_at" | "-created_at" | null | No | |
limit | integer | No | Default: 20 |
offset | integer | No | Default: 0 |
Parameter schema (JSON)
{
"properties": {
"user_id": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Your Metaculus user id. Omit to resolve it automatically.",
"title": "User Id"
},
"post_id": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Only comments on this post.",
"title": "Post Id"
},
"is_private": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"default": null,
"description": "True for your private notes, False for public comments. Metaculus defaults to public when omitted.",
"title": "Is Private"
},
"sort": {
"anyOf": [
{
"enum": [
"created_at",
"-created_at"
],
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Sort"
},
"limit": {
"default": 20,
"maximum": 100,
"minimum": 1,
"title": "Limit",
"type": "integer"
},
"offset": {
"default": 0,
"minimum": 0,
"title": "Offset",
"type": "integer"
}
},
"title": "GetMyCommentsParams",
"type": "object",
"additionalProperties": false
} Result schema (JSON)
{
"$defs": {
"CommentSummary": {
"properties": {
"comment_id": {
"title": "Comment Id",
"type": "integer"
},
"post_id": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Post Id"
},
"post_url": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Post Url"
},
"created_at": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Created At"
},
"text": {
"title": "Text",
"type": "string"
},
"text_truncated": {
"description": "True when the body was cut at 2000 characters.",
"title": "Text Truncated",
"type": "boolean"
},
"is_private": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"default": null,
"title": "Is Private"
},
"included_forecast": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"default": null,
"title": "Included Forecast"
},
"parent_id": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Parent Id"
},
"vote_score": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Vote Score"
}
},
"required": [
"comment_id",
"text",
"text_truncated"
],
"title": "CommentSummary",
"type": "object"
}
},
"properties": {
"comments": {
"items": {
"$ref": "#/$defs/CommentSummary"
},
"title": "Comments",
"type": "array"
},
"user_id": {
"title": "User Id",
"type": "integer"
},
"user_id_source": {
"title": "User Id Source",
"type": "string"
},
"returned": {
"title": "Returned",
"type": "integer"
},
"offset": {
"title": "Offset",
"type": "integer"
},
"limit": {
"title": "Limit",
"type": "integer"
},
"total_count": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Upstream total_count (root and child comments).",
"title": "Total Count"
},
"next_offset": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Offset to request for the next page. Null whenever has_more is not true, and never equal to the offset you just requested.",
"title": "Next Offset"
},
"has_more": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"default": null,
"description": "Whether another page exists. False when Metaculus returned fewer raw rows than `limit` (its `next` link lies on short pages) — judged on the upstream row count, so a page that lost malformed rows to client-side filtering still pages on. On a full page this is the upstream `next` link's presence; null only when the response carried no `next` field.",
"title": "Has More"
}
},
"required": [
"comments",
"user_id",
"user_id_source",
"returned",
"offset",
"limit"
],
"title": "MyCommentsResult",
"type": "object"
} # get_my_predictions read
List the Metaculus posts you have forecast on, surfacing your own latest forecast — value, when you made it, when it stops standing — and resolutions on single-question posts; order_by='score' ranks them by your own forecasting performance.
Full description
List the posts you have forecast on. This is the posts feed filtered by your own `forecaster_id`, so the same compact summaries come back. `resolution` is populated for single-question posts; groups and conditionals carry theirs on their subquestions, which only get_question returns. Resolution text stays readable on questions you have forecast on, which is every question this tool returns. `my_forecast` answers "what did I say, and when": your latest forecast's value alongside `start_time` (when you made it), `end_time` (when it stops standing — the auto-withdrawal you scheduled through submit_forecast, or the moment you withdrew it by hand with withdraw_forecast; a null is not proof neither happened, see the field) and `withdrawn`. The value is compact and mirrors `community_prediction`'s shape so the two read side by side — `probability_yes`/`center` on binary, `option_probabilities` on multiple choice, and median plus a 25th/75th interval (raw and in the question's own units) on numeric, date and discrete questions; the stored 201-value CDF is never returned. `start_time` is the LATEST forecast's timestamp, NOT the first one you made on the question — full forecast history and per-forecast scores stay download_question_data territory. On a group or conditional post `my_forecast` is ONE subquestion's forecast, named by `my_forecast_question_id`; get_question carries one per subquestion. It is null when the response carried no forecast of yours for the post — and since this feed is already filtered by your own forecaster_id, that is NOT evidence you never forecast the post, only that Metaculus did not attach your forecasts to that row. Ask get_question for the post when it happens. `order_by='score'` ranks by your own forecasting performance. Metaculus sorts server-side but does not return the score VALUE in this feed, so the ranking is in the ORDER, not in a field; per-question personal scores come from download_question_data. Your user id is resolved automatically through Metaculus's undocumented /api/users/me/ endpoint. Pass `user_id` to skip that probe, and see `user_id_source` for which route was used.
| Parameter | Type | Required | Description |
|---|---|---|---|
user_id | integer | null | No | Your Metaculus user id. Omit to resolve it automatically. |
statuses | array<"upcoming" | "open" | "closed" | "resolved"> | null | No | |
order_by | string | null | No | Same values as list_questions. 'score' ranks by your own forecasting performance and works here because forecaster_id is always set. |
limit | integer | No | Default: 20 |
offset | integer | No | Default: 0 |
Parameter schema (JSON)
{
"properties": {
"user_id": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Your Metaculus user id. Omit to resolve it automatically.",
"title": "User Id"
},
"statuses": {
"anyOf": [
{
"items": {
"enum": [
"upcoming",
"open",
"closed",
"resolved"
],
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"title": "Statuses"
},
"order_by": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Same values as list_questions. 'score' ranks by your own forecasting performance and works here because forecaster_id is always set.",
"title": "Order By"
},
"limit": {
"default": 20,
"maximum": 100,
"minimum": 1,
"title": "Limit",
"type": "integer"
},
"offset": {
"default": 0,
"minimum": 0,
"title": "Offset",
"type": "integer"
}
},
"title": "GetMyPredictionsParams",
"type": "object",
"additionalProperties": false
} Result schema (JSON)
{
"$defs": {
"AccessHint": {
"description": "Machine-readable note that a response was shortened by access tier.",
"properties": {
"code": {
"description": "tier_gated (data withheld for this token) or restricted_endpoint (the endpoint itself needs approval).",
"title": "Code",
"type": "string"
},
"message": {
"description": "Human-readable explanation of what is missing.",
"title": "Message",
"type": "string"
},
"data_needs_form_url": {
"default": "https://docs.google.com/forms/d/e/1FAIpQLSeJhtZzHl5qMvBjbXbatyaqoS4IU7RE0GGw_vlhs6I9syqn1g/viewform?usp=pp_url&entry.192763438=https://www.metaculus.com/api/",
"description": "Metaculus Data Needs form — the free Bot Benchmarking tier and bespoke grants are both requested here.",
"title": "Data Needs Form Url",
"type": "string"
},
"contact_email": {
"default": "api-requests@metaculus.com",
"description": "Metaculus API contact address.",
"title": "Contact Email",
"type": "string"
},
"unlock_note": {
"default": "Submitting a forecast on a question permanently unlocks that question's text and resolution for your account, so access widens as you forecast.",
"description": "How access grows without an upgrade.",
"title": "Unlock Note",
"type": "string"
}
},
"required": [
"code",
"message"
],
"title": "AccessHint",
"type": "object"
},
"CommunityPrediction": {
"description": "The latest recency-weighted Community Prediction for one question.\n\nOnly the LATEST aggregation snapshot is returned; the aggregation history and\nthe full 201-value forecast_values array are dropped everywhere in this\ntoolkit. Metaculus does not label which quantiles ``interval_lower`` and\n``interval_upper`` represent, so they are passed through unlabelled.",
"properties": {
"forecaster_count": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Forecaster Count"
},
"as_of": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "ISO 8601 start time of the latest aggregation.",
"title": "As Of"
},
"center": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"description": "Aggregate center. For binary questions this is P(yes); for numeric/date/discrete questions it is an internal [0, 1] location; for multiple-choice questions it is the FIRST option's aggregate probability — read option_probabilities for the named per-option numbers. Null on a multiple-choice question whose options could not be paired with the aggregate, because then no option can be named.",
"title": "Center"
},
"interval_lower": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"title": "Interval Lower"
},
"interval_upper": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"title": "Interval Upper"
},
"center_nominal": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"description": "center converted to the question's own units via question.scaling (numeric/date/discrete only; a POSIX timestamp for date questions). Null when the scaling is absent or degenerate.",
"title": "Center Nominal"
},
"interval_lower_nominal": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"title": "Interval Lower Nominal"
},
"interval_upper_nominal": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"title": "Interval Upper Nominal"
},
"center_nominal_iso": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "center_nominal as ISO 8601 (date questions only).",
"title": "Center Nominal Iso"
},
"option_probabilities": {
"anyOf": [
{
"additionalProperties": {
"type": "number"
},
"type": "object"
},
{
"type": "null"
}
],
"default": null,
"description": "Per-option probabilities for multiple-choice questions, present only when the option list and the aggregate centers line up.",
"title": "Option Probabilities"
}
},
"title": "CommunityPrediction",
"type": "object"
},
"MyForecast": {
"description": "Your own LATEST forecast on one question, in the same compact shape as\nCommunityPrediction so the two can be read side by side.\n\nBuilt entirely from the ``my_forecasts.latest`` object Metaculus attaches to\nan authenticated posts response — personal data, which Metaculus does not\ngate by access tier, so this can be populated on a question whose Community\nPrediction is withheld. The forecast HISTORY is never read: ``start_time`` is\nthe latest forecast's timestamp, not the first one you made on the question,\nand on a continuous question the stored 201-value CDF is reduced to the\nquantiles below rather than returned. Full history and per-forecast scores\nare download_question_data territory.\n\nOne asymmetry with CommunityPrediction: Metaculus does not label which\nquantiles the CP's ``interval_lower``/``interval_upper`` represent, but it\ndoes label these — on a continuous question they are the 25th and 75th\npercentiles of your own stored distribution, whose median is ``center``.",
"properties": {
"start_time": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "ISO 8601 time the LATEST forecast was made — not the first one you made on this question. Null only when Metaculus returned no usable timestamp.",
"title": "Start Time"
},
"end_time": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "ISO 8601 time this forecast stops standing — either an auto-withdrawal you set through submit_forecast's end_time, or the moment you withdrew it. Null USUALLY means no withdrawal time applies, but not only: Metaculus suppresses an end_time that falls after the question's own close time, and this toolkit reports null for one it cannot parse or cannot render as a timestamp. A null here is therefore not proof you never set one.",
"title": "End Time"
},
"withdrawn": {
"description": "True when end_time is non-null and already in the past as of this response, i.e. the forecast no longer counts toward the aggregate. False when end_time is null OR still in the future — a scheduled auto-withdrawal that has not fired yet reads false, and so does a forecast that simply stood until the question closed, whose end_time Metaculus suppresses to null: it stopped counting because the question closed, not because you withdrew it. A forecast you withdrew BEFORE the question closed keeps its end_time and so still reads true on a closed or resolved question.",
"title": "Withdrawn",
"type": "boolean"
},
"probability_yes": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"description": "Your P(yes) on a BINARY question. Null on every other question type, and null on a binary question whose stored forecast did not come back as a usable P(no)/P(yes) pair. On a binary question `center` carries this same number under the Community Prediction's own field name; on other question types `center` carries a median or nothing while this stays null.",
"title": "Probability Yes"
},
"center": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"description": "Your forecast's center. For binary questions this is P(yes); for numeric/date/discrete questions it is the MEDIAN as an internal [0, 1] location. Null on multiple-choice questions (read option_probabilities — a per-option forecast has no single center), on any question type this toolkit does not summarize, and wherever the stored forecast carried no usable number.",
"title": "Center"
},
"interval_lower": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"description": "Your 25th percentile as an internal [0, 1] location, numeric/date/discrete questions only. Null on binary and multiple-choice: your own forecast there is a point estimate with no interval, unlike the Community Prediction, whose interval is the spread across forecasters.",
"title": "Interval Lower"
},
"interval_upper": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"description": "Your 75th percentile as an internal [0, 1] location, numeric/date/discrete questions only. Null on binary and multiple-choice — see interval_lower.",
"title": "Interval Upper"
},
"center_nominal": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"description": "center converted to the question's own units via question.scaling (numeric/date/discrete only; a POSIX timestamp for date questions). Null when the scaling is absent or degenerate.",
"title": "Center Nominal"
},
"interval_lower_nominal": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"title": "Interval Lower Nominal"
},
"interval_upper_nominal": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"title": "Interval Upper Nominal"
},
"center_nominal_iso": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "center_nominal as ISO 8601 (date questions only).",
"title": "Center Nominal Iso"
},
"option_probabilities": {
"anyOf": [
{
"additionalProperties": {
"type": "number"
},
"type": "object"
},
{
"type": "null"
}
],
"default": null,
"description": "Your per-option probabilities for multiple-choice questions. The stored array is aligned to the question's ever-list of options (`all_options_ever`), so that is what these are paired against, or the current option list when the payload carries no ever-list — on a question that has had an option DELETED this map can therefore name an option that no longer appears in `options`. Null when the list used is not the same length as the stored array, since then no number can be attributed to an option. Metaculus permits a null probability on an individual option; such an option is OMITTED from this map, so the entries need not sum to 1.",
"title": "Option Probabilities"
}
},
"required": [
"withdrawn"
],
"title": "MyForecast",
"type": "object"
},
"QuestionSummary": {
"description": "Compact view of one post. Free text, aggregation history and CDFs are\nomitted — use get_question for a post's full detail.",
"properties": {
"post_id": {
"title": "Post Id",
"type": "integer"
},
"question_id": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Question id for single-question posts. Null for groups, conditionals and notebooks — forecast tools need a question id, so use get_question to list a group's subquestion ids.",
"title": "Question Id"
},
"title": {
"title": "Title",
"type": "string"
},
"url": {
"title": "Url",
"type": "string"
},
"post_type": {
"description": "question, group_of_questions, conditional, or notebook.",
"title": "Post Type",
"type": "string"
},
"question_type": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "binary, numeric, discrete, date or multiple_choice.",
"title": "Question Type"
},
"status": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Status"
},
"resolved": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"default": null,
"title": "Resolved"
},
"resolution": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Resolution of a single-question post. Always null for groups, conditionals and notebooks — their resolutions sit on the individual subquestions, which get_question returns.",
"title": "Resolution"
},
"open_time": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Open Time"
},
"scheduled_close_time": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Scheduled Close Time"
},
"scheduled_resolve_time": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Scheduled Resolve Time"
},
"actual_resolve_time": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Actual Resolve Time"
},
"nr_forecasters": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Nr Forecasters"
},
"forecasts_count": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Forecasts Count"
},
"comment_count": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Comment Count"
},
"unit": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Unit"
},
"categories": {
"items": {
"type": "string"
},
"title": "Categories",
"type": "array"
},
"tournaments": {
"items": {
"type": "string"
},
"title": "Tournaments",
"type": "array"
},
"subquestion_count": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Subquestion Count"
},
"community_prediction": {
"anyOf": [
{
"$ref": "#/$defs/CommunityPrediction"
},
{
"type": "null"
}
],
"default": null,
"description": "Community Prediction for a single-question post. On a group or conditional post this is ONE subquestion's aggregate — the first that carried one and that the payload names with a question id, reported in community_prediction_question_id — not a post-level number, so do not quote it as the group's forecast. Use get_question for every subquestion's own aggregate."
},
"community_prediction_question_id": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Question id that community_prediction belongs to. Equals question_id on single-question posts, and names the stand-in subquestion on groups and conditionals, where the scan prefers a subquestion the payload gives a usable id. Null whenever community_prediction is null, and also on the malformed payload that carried no usable id for the question the aggregate came from.",
"title": "Community Prediction Question Id"
},
"cp_access": {
"description": "available when a Community Prediction came back for this post — on a group or conditional, for at least one subquestion, not all of them. absent when none did — usually the access tier, but also the case for a question nobody has forecast yet, and for notebooks. On groups, absent is weak evidence of gating: the feed only populates the top few subquestions, so ask get_question before concluding anything.",
"title": "Cp Access",
"type": "string"
},
"my_forecast": {
"anyOf": [
{
"$ref": "#/$defs/MyForecast"
},
{
"type": "null"
}
],
"default": null,
"description": "YOUR own latest forecast on a single-question post. On a group or conditional post this is ONE subquestion's forecast — the first that carried one and that the payload names with a question id, reported in my_forecast_question_id — not a post-level forecast; use get_question for every subquestion's own. Null when the response carried no forecast of yours for this post: you have not forecast it, or Metaculus did not attach your forecasts to this response. Unlike community_prediction this is never withheld by access tier."
},
"my_forecast_question_id": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Question id that my_forecast belongs to. Equals question_id on single-question posts, and names the stand-in subquestion on groups and conditionals, where the scan prefers a subquestion the payload gives a usable id. Null whenever my_forecast is null, and also on the malformed payload that carried no usable id for the question the forecast came from.",
"title": "My Forecast Question Id"
}
},
"required": [
"post_id",
"title",
"url",
"post_type",
"cp_access"
],
"title": "QuestionSummary",
"type": "object"
}
},
"properties": {
"questions": {
"items": {
"$ref": "#/$defs/QuestionSummary"
},
"title": "Questions",
"type": "array"
},
"user_id": {
"title": "User Id",
"type": "integer"
},
"user_id_source": {
"title": "User Id Source",
"type": "string"
},
"returned": {
"title": "Returned",
"type": "integer"
},
"offset": {
"title": "Offset",
"type": "integer"
},
"limit": {
"title": "Limit",
"type": "integer"
},
"next_offset": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Offset to request for the next page. Null whenever has_more is not true, and never equal to the offset you just requested.",
"title": "Next Offset"
},
"has_more": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"default": null,
"description": "Whether another page exists. False when Metaculus returned fewer raw rows than `limit` (its `next` link lies on short pages) — judged on the upstream row count, so a page that lost malformed rows to client-side filtering still pages on. On a full page this is the upstream `next` link's presence; null only when the response carried no `next` field.",
"title": "Has More"
},
"cp_available_count": {
"title": "Cp Available Count",
"type": "integer"
},
"access_hint": {
"anyOf": [
{
"$ref": "#/$defs/AccessHint"
},
{
"type": "null"
}
],
"default": null
}
},
"required": [
"questions",
"user_id",
"user_id_source",
"returned",
"offset",
"limit",
"cp_available_count"
],
"title": "MyPredictionsResult",
"type": "object"
} # get_question read
Get one Metaculus post in full: description, resolution criteria, fine print, scaling, subquestions, the latest Community Prediction and your own latest forecast on each question you have forecast.
Full description
Retrieve one post's full detail by post id. Returns the description, resolution criteria and fine print (each cut at 8000 characters, with anything cut named in `truncated_fields`), the question's scaling (`range_min`, `range_max`, `zero_point`, open/closed bounds, `inbound_outcome_count`) — everything submit_forecast needs — and every subquestion of a group or conditional post, each with its own question id. Only the LATEST Community Prediction snapshot is returned: the aggregation history, the 201-value forecast_values array and the `continuous_range` array are all dropped. `cp_access` is `available` or `absent`, and an `access_hint` appears only when the post yielded at least one question and every one of them came back `absent` — a notebook post has no questions, so it gets no hint rather than a hint saying its CP was withheld. `my_forecast` carries your own latest forecast's value, `start_time` and `end_time`, plus `withdrawn`, on every question whose payload carried a forecast of yours. On a group or conditional post it sits on each subquestion, the same way `resolution` does, so only the subquestions you have forecast have one. `start_time` is the LATEST forecast's timestamp, not the first one you made on the question; the full forecast history and per-forecast scores come from download_question_data. Closed questions you have never forecast on may come back without their text or resolution — Metaculus unlocks those permanently once you forecast.
| Parameter | Type | Required | Description |
|---|---|---|---|
post_id | integer | Yes | Post id (the number in a metaculus.com/questions/<id>/ URL). |
Parameter schema (JSON)
{
"properties": {
"post_id": {
"description": "Post id (the number in a metaculus.com/questions/<id>/ URL).",
"title": "Post Id",
"type": "integer"
}
},
"required": [
"post_id"
],
"title": "GetQuestionParams",
"type": "object",
"additionalProperties": false
} Result schema (JSON)
{
"$defs": {
"AccessHint": {
"description": "Machine-readable note that a response was shortened by access tier.",
"properties": {
"code": {
"description": "tier_gated (data withheld for this token) or restricted_endpoint (the endpoint itself needs approval).",
"title": "Code",
"type": "string"
},
"message": {
"description": "Human-readable explanation of what is missing.",
"title": "Message",
"type": "string"
},
"data_needs_form_url": {
"default": "https://docs.google.com/forms/d/e/1FAIpQLSeJhtZzHl5qMvBjbXbatyaqoS4IU7RE0GGw_vlhs6I9syqn1g/viewform?usp=pp_url&entry.192763438=https://www.metaculus.com/api/",
"description": "Metaculus Data Needs form — the free Bot Benchmarking tier and bespoke grants are both requested here.",
"title": "Data Needs Form Url",
"type": "string"
},
"contact_email": {
"default": "api-requests@metaculus.com",
"description": "Metaculus API contact address.",
"title": "Contact Email",
"type": "string"
},
"unlock_note": {
"default": "Submitting a forecast on a question permanently unlocks that question's text and resolution for your account, so access widens as you forecast.",
"description": "How access grows without an upgrade.",
"title": "Unlock Note",
"type": "string"
}
},
"required": [
"code",
"message"
],
"title": "AccessHint",
"type": "object"
},
"CommunityPrediction": {
"description": "The latest recency-weighted Community Prediction for one question.\n\nOnly the LATEST aggregation snapshot is returned; the aggregation history and\nthe full 201-value forecast_values array are dropped everywhere in this\ntoolkit. Metaculus does not label which quantiles ``interval_lower`` and\n``interval_upper`` represent, so they are passed through unlabelled.",
"properties": {
"forecaster_count": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Forecaster Count"
},
"as_of": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "ISO 8601 start time of the latest aggregation.",
"title": "As Of"
},
"center": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"description": "Aggregate center. For binary questions this is P(yes); for numeric/date/discrete questions it is an internal [0, 1] location; for multiple-choice questions it is the FIRST option's aggregate probability — read option_probabilities for the named per-option numbers. Null on a multiple-choice question whose options could not be paired with the aggregate, because then no option can be named.",
"title": "Center"
},
"interval_lower": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"title": "Interval Lower"
},
"interval_upper": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"title": "Interval Upper"
},
"center_nominal": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"description": "center converted to the question's own units via question.scaling (numeric/date/discrete only; a POSIX timestamp for date questions). Null when the scaling is absent or degenerate.",
"title": "Center Nominal"
},
"interval_lower_nominal": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"title": "Interval Lower Nominal"
},
"interval_upper_nominal": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"title": "Interval Upper Nominal"
},
"center_nominal_iso": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "center_nominal as ISO 8601 (date questions only).",
"title": "Center Nominal Iso"
},
"option_probabilities": {
"anyOf": [
{
"additionalProperties": {
"type": "number"
},
"type": "object"
},
{
"type": "null"
}
],
"default": null,
"description": "Per-option probabilities for multiple-choice questions, present only when the option list and the aggregate centers line up.",
"title": "Option Probabilities"
}
},
"title": "CommunityPrediction",
"type": "object"
},
"MyForecast": {
"description": "Your own LATEST forecast on one question, in the same compact shape as\nCommunityPrediction so the two can be read side by side.\n\nBuilt entirely from the ``my_forecasts.latest`` object Metaculus attaches to\nan authenticated posts response — personal data, which Metaculus does not\ngate by access tier, so this can be populated on a question whose Community\nPrediction is withheld. The forecast HISTORY is never read: ``start_time`` is\nthe latest forecast's timestamp, not the first one you made on the question,\nand on a continuous question the stored 201-value CDF is reduced to the\nquantiles below rather than returned. Full history and per-forecast scores\nare download_question_data territory.\n\nOne asymmetry with CommunityPrediction: Metaculus does not label which\nquantiles the CP's ``interval_lower``/``interval_upper`` represent, but it\ndoes label these — on a continuous question they are the 25th and 75th\npercentiles of your own stored distribution, whose median is ``center``.",
"properties": {
"start_time": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "ISO 8601 time the LATEST forecast was made — not the first one you made on this question. Null only when Metaculus returned no usable timestamp.",
"title": "Start Time"
},
"end_time": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "ISO 8601 time this forecast stops standing — either an auto-withdrawal you set through submit_forecast's end_time, or the moment you withdrew it. Null USUALLY means no withdrawal time applies, but not only: Metaculus suppresses an end_time that falls after the question's own close time, and this toolkit reports null for one it cannot parse or cannot render as a timestamp. A null here is therefore not proof you never set one.",
"title": "End Time"
},
"withdrawn": {
"description": "True when end_time is non-null and already in the past as of this response, i.e. the forecast no longer counts toward the aggregate. False when end_time is null OR still in the future — a scheduled auto-withdrawal that has not fired yet reads false, and so does a forecast that simply stood until the question closed, whose end_time Metaculus suppresses to null: it stopped counting because the question closed, not because you withdrew it. A forecast you withdrew BEFORE the question closed keeps its end_time and so still reads true on a closed or resolved question.",
"title": "Withdrawn",
"type": "boolean"
},
"probability_yes": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"description": "Your P(yes) on a BINARY question. Null on every other question type, and null on a binary question whose stored forecast did not come back as a usable P(no)/P(yes) pair. On a binary question `center` carries this same number under the Community Prediction's own field name; on other question types `center` carries a median or nothing while this stays null.",
"title": "Probability Yes"
},
"center": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"description": "Your forecast's center. For binary questions this is P(yes); for numeric/date/discrete questions it is the MEDIAN as an internal [0, 1] location. Null on multiple-choice questions (read option_probabilities — a per-option forecast has no single center), on any question type this toolkit does not summarize, and wherever the stored forecast carried no usable number.",
"title": "Center"
},
"interval_lower": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"description": "Your 25th percentile as an internal [0, 1] location, numeric/date/discrete questions only. Null on binary and multiple-choice: your own forecast there is a point estimate with no interval, unlike the Community Prediction, whose interval is the spread across forecasters.",
"title": "Interval Lower"
},
"interval_upper": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"description": "Your 75th percentile as an internal [0, 1] location, numeric/date/discrete questions only. Null on binary and multiple-choice — see interval_lower.",
"title": "Interval Upper"
},
"center_nominal": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"description": "center converted to the question's own units via question.scaling (numeric/date/discrete only; a POSIX timestamp for date questions). Null when the scaling is absent or degenerate.",
"title": "Center Nominal"
},
"interval_lower_nominal": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"title": "Interval Lower Nominal"
},
"interval_upper_nominal": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"title": "Interval Upper Nominal"
},
"center_nominal_iso": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "center_nominal as ISO 8601 (date questions only).",
"title": "Center Nominal Iso"
},
"option_probabilities": {
"anyOf": [
{
"additionalProperties": {
"type": "number"
},
"type": "object"
},
{
"type": "null"
}
],
"default": null,
"description": "Your per-option probabilities for multiple-choice questions. The stored array is aligned to the question's ever-list of options (`all_options_ever`), so that is what these are paired against, or the current option list when the payload carries no ever-list — on a question that has had an option DELETED this map can therefore name an option that no longer appears in `options`. Null when the list used is not the same length as the stored array, since then no number can be attributed to an option. Metaculus permits a null probability on an individual option; such an option is OMITTED from this map, so the entries need not sum to 1.",
"title": "Option Probabilities"
}
},
"required": [
"withdrawn"
],
"title": "MyForecast",
"type": "object"
},
"QuestionDetail": {
"description": "One question (a post's own question, or one subquestion of a group or\nconditional) with its full text and scaling.",
"properties": {
"question_id": {
"title": "Question Id",
"type": "integer"
},
"title": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Title"
},
"question_type": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Question Type"
},
"status": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Status"
},
"resolution": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Resolution"
},
"label": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Subquestion label within a group, when present.",
"title": "Label"
},
"description": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Description"
},
"resolution_criteria": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Resolution Criteria"
},
"fine_print": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Fine Print"
},
"unit": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Unit"
},
"options": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"title": "Options"
},
"open_lower_bound": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"default": null,
"title": "Open Lower Bound"
},
"open_upper_bound": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"default": null,
"title": "Open Upper Bound"
},
"inbound_outcome_count": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Inbound Outcome Count"
},
"range_min": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"title": "Range Min"
},
"range_max": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"title": "Range Max"
},
"zero_point": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"description": "Present (including 0.0) only for logarithmically scaled questions.",
"title": "Zero Point"
},
"open_time": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Open Time"
},
"scheduled_close_time": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Scheduled Close Time"
},
"actual_close_time": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Actual Close Time"
},
"scheduled_resolve_time": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Scheduled Resolve Time"
},
"actual_resolve_time": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Actual Resolve Time"
},
"community_prediction": {
"anyOf": [
{
"$ref": "#/$defs/CommunityPrediction"
},
{
"type": "null"
}
],
"default": null
},
"cp_access": {
"title": "Cp Access",
"type": "string"
},
"my_forecast": {
"anyOf": [
{
"$ref": "#/$defs/MyForecast"
},
{
"type": "null"
}
],
"default": null,
"description": "YOUR own latest forecast on THIS question — on a group or conditional post each subquestion carries its own, so only the subquestions you have forecast have one. Null when the response carried no forecast of yours for this question."
},
"truncated_fields": {
"description": "Which free-text fields were cut at 8000 characters.",
"items": {
"type": "string"
},
"title": "Truncated Fields",
"type": "array"
}
},
"required": [
"question_id",
"cp_access"
],
"title": "QuestionDetail",
"type": "object"
}
},
"properties": {
"post_id": {
"title": "Post Id",
"type": "integer"
},
"title": {
"title": "Title",
"type": "string"
},
"short_title": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Short Title"
},
"url": {
"title": "Url",
"type": "string"
},
"post_type": {
"title": "Post Type",
"type": "string"
},
"status": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Status"
},
"resolved": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"default": null,
"title": "Resolved"
},
"author_username": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Author Username"
},
"published_at": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Published At"
},
"nr_forecasters": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Nr Forecasters"
},
"forecasts_count": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Forecasts Count"
},
"comment_count": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Comment Count"
},
"categories": {
"items": {
"type": "string"
},
"title": "Categories",
"type": "array"
},
"tournaments": {
"items": {
"type": "string"
},
"title": "Tournaments",
"type": "array"
},
"question": {
"anyOf": [
{
"$ref": "#/$defs/QuestionDetail"
},
{
"type": "null"
}
],
"default": null,
"description": "The post's own question, for single-question posts."
},
"subquestions": {
"description": "Subquestions of a group, or the condition/child pair of a conditional. Empty for single-question posts and notebooks.",
"items": {
"$ref": "#/$defs/QuestionDetail"
},
"title": "Subquestions",
"type": "array"
},
"access_hint": {
"anyOf": [
{
"$ref": "#/$defs/AccessHint"
},
{
"type": "null"
}
],
"default": null
}
},
"required": [
"post_id",
"title",
"url",
"post_type"
],
"title": "GetQuestionResult",
"type": "object"
} # list_questions read
Browse Metaculus questions by status, type, category, tournament, time range and ordering, plus a best-effort text search.
Full description
Browse the Metaculus feed. Discovery is primarily by structured filter: status, forecast type, category slug, tournament slug, opening / publication / scheduled-resolution time ranges, and ordering (`-hotness` for what is currently active, `weekly_movement` and `divergence` for questions whose Community Prediction is moving, `score` — which needs `forecaster_id` — for a forecaster's performance ranking). A `search` parameter is also passed through, but it is UNDOCUMENTED in Metaculus's published API: treat it as best-effort and fall back to the structured filters if it disappoints. Results are COMPACT: descriptions and aggregation histories are suppressed at the source (`include_descriptions=false`, `include_cp_history=false`) and the per-question forecast_values and continuous_range arrays are dropped here. Call get_question for a post's full text. The Community Prediction is always requested. Each post reports `cp_access`: `available`, or `absent` when the response carried no aggregation for it — most often the access tier, but also the case for a question nobody has forecast yet. An `access_hint` explaining the upgrade path is emitted only when the page returned at least one FORECASTABLE post and none of them carried a CP: a page of notebooks carries no forecasts at all, so it is no evidence of gating and gets no hint, and a page where some posts carried one gets none either — `cp_access` already says which. Forecasting on a question permanently unlocks that question's text and resolution for your account. `my_forecast` appears on any post whose payload carried a forecast of yours: your own latest forecast's value, `start_time` and `end_time`, plus `withdrawn`. That is personal data and is never gated, so it can be present on a post whose CP is `absent`. `start_time` is the LATEST forecast's timestamp, not the first one you made — the full history is download_question_data territory. On a group or conditional it is ONE subquestion's forecast, named by `my_forecast_question_id`. A null `my_forecast` usually means you have not forecast the post, but it can also mean the feed did not attach your forecasts to that row; get_question settles it.
| Parameter | Type | Required | Description |
|---|---|---|---|
search | string | null | No | Free-text search. UNDOCUMENTED: Metaculus's published API spec lists no search parameter, so this is best-effort and may stop working without notice. Prefer the structured filters when they fit. |
statuses | array<"upcoming" | "open" | "closed" | "resolved"> | null | No | Post lifecycle statuses to include. |
forecast_type | array<"binary" | "multiple_choice" | "numeric" | "discrete" | "date" | "conditional" | "group_of_questions" | "notebook"> | null | No | Post/question types to include, e.g. ['binary', 'numeric']. |
categories | array<string> | null | No | Category slugs, e.g. ['health-pandemics', 'nuclear']. |
tournaments | array<string> | null | No | Tournament slugs, e.g. ['metaculus-cup']. |
forecaster_id | integer | null | No | Only posts this user has forecast on. |
not_forecaster_id | integer | null | No | Only posts this user has NOT forecast on. |
main_feed_only | boolean | null | No | Restrict to posts suitable for the main feed. |
open_time_after | string | null | No | ISO 8601; posts that opened strictly after this. |
open_time_before | string | null | No | ISO 8601; posts that opened strictly before this. |
published_at_after | string | null | No | |
published_at_before | string | null | No | |
scheduled_resolve_time_after | string | null | No | |
scheduled_resolve_time_before | string | null | No | |
order_by | string | null | No | One of published_at, open_time, vote_score, comment_count, forecasts_count, scheduled_close_time, scheduled_resolve_time, user_last_forecasts_date, unread_comment_count, weekly_movement, divergence, hotness, score — prefix with '-' for descending. 'score' additionally requires forecaster_id. |
limit | integer | No | Posts per page. Default: 20 |
offset | integer | No | Posts to skip. Default: 0 |
Parameter schema (JSON)
{
"properties": {
"search": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Free-text search. UNDOCUMENTED: Metaculus's published API spec lists no search parameter, so this is best-effort and may stop working without notice. Prefer the structured filters when they fit.",
"title": "Search"
},
"statuses": {
"anyOf": [
{
"items": {
"enum": [
"upcoming",
"open",
"closed",
"resolved"
],
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"description": "Post lifecycle statuses to include.",
"title": "Statuses"
},
"forecast_type": {
"anyOf": [
{
"items": {
"enum": [
"binary",
"multiple_choice",
"numeric",
"discrete",
"date",
"conditional",
"group_of_questions",
"notebook"
],
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"description": "Post/question types to include, e.g. ['binary', 'numeric'].",
"title": "Forecast Type"
},
"categories": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"description": "Category slugs, e.g. ['health-pandemics', 'nuclear'].",
"title": "Categories"
},
"tournaments": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"description": "Tournament slugs, e.g. ['metaculus-cup'].",
"title": "Tournaments"
},
"forecaster_id": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Only posts this user has forecast on.",
"title": "Forecaster Id"
},
"not_forecaster_id": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Only posts this user has NOT forecast on.",
"title": "Not Forecaster Id"
},
"main_feed_only": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"default": null,
"description": "Restrict to posts suitable for the main feed.",
"title": "Main Feed Only"
},
"open_time_after": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "ISO 8601; posts that opened strictly after this.",
"title": "Open Time After"
},
"open_time_before": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "ISO 8601; posts that opened strictly before this.",
"title": "Open Time Before"
},
"published_at_after": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Published At After"
},
"published_at_before": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Published At Before"
},
"scheduled_resolve_time_after": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Scheduled Resolve Time After"
},
"scheduled_resolve_time_before": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Scheduled Resolve Time Before"
},
"order_by": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "One of published_at, open_time, vote_score, comment_count, forecasts_count, scheduled_close_time, scheduled_resolve_time, user_last_forecasts_date, unread_comment_count, weekly_movement, divergence, hotness, score — prefix with '-' for descending. 'score' additionally requires forecaster_id.",
"title": "Order By"
},
"limit": {
"default": 20,
"description": "Posts per page.",
"maximum": 100,
"minimum": 1,
"title": "Limit",
"type": "integer"
},
"offset": {
"default": 0,
"description": "Posts to skip.",
"minimum": 0,
"title": "Offset",
"type": "integer"
}
},
"title": "ListQuestionsParams",
"type": "object",
"additionalProperties": false
} Result schema (JSON)
{
"$defs": {
"AccessHint": {
"description": "Machine-readable note that a response was shortened by access tier.",
"properties": {
"code": {
"description": "tier_gated (data withheld for this token) or restricted_endpoint (the endpoint itself needs approval).",
"title": "Code",
"type": "string"
},
"message": {
"description": "Human-readable explanation of what is missing.",
"title": "Message",
"type": "string"
},
"data_needs_form_url": {
"default": "https://docs.google.com/forms/d/e/1FAIpQLSeJhtZzHl5qMvBjbXbatyaqoS4IU7RE0GGw_vlhs6I9syqn1g/viewform?usp=pp_url&entry.192763438=https://www.metaculus.com/api/",
"description": "Metaculus Data Needs form — the free Bot Benchmarking tier and bespoke grants are both requested here.",
"title": "Data Needs Form Url",
"type": "string"
},
"contact_email": {
"default": "api-requests@metaculus.com",
"description": "Metaculus API contact address.",
"title": "Contact Email",
"type": "string"
},
"unlock_note": {
"default": "Submitting a forecast on a question permanently unlocks that question's text and resolution for your account, so access widens as you forecast.",
"description": "How access grows without an upgrade.",
"title": "Unlock Note",
"type": "string"
}
},
"required": [
"code",
"message"
],
"title": "AccessHint",
"type": "object"
},
"CommunityPrediction": {
"description": "The latest recency-weighted Community Prediction for one question.\n\nOnly the LATEST aggregation snapshot is returned; the aggregation history and\nthe full 201-value forecast_values array are dropped everywhere in this\ntoolkit. Metaculus does not label which quantiles ``interval_lower`` and\n``interval_upper`` represent, so they are passed through unlabelled.",
"properties": {
"forecaster_count": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Forecaster Count"
},
"as_of": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "ISO 8601 start time of the latest aggregation.",
"title": "As Of"
},
"center": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"description": "Aggregate center. For binary questions this is P(yes); for numeric/date/discrete questions it is an internal [0, 1] location; for multiple-choice questions it is the FIRST option's aggregate probability — read option_probabilities for the named per-option numbers. Null on a multiple-choice question whose options could not be paired with the aggregate, because then no option can be named.",
"title": "Center"
},
"interval_lower": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"title": "Interval Lower"
},
"interval_upper": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"title": "Interval Upper"
},
"center_nominal": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"description": "center converted to the question's own units via question.scaling (numeric/date/discrete only; a POSIX timestamp for date questions). Null when the scaling is absent or degenerate.",
"title": "Center Nominal"
},
"interval_lower_nominal": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"title": "Interval Lower Nominal"
},
"interval_upper_nominal": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"title": "Interval Upper Nominal"
},
"center_nominal_iso": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "center_nominal as ISO 8601 (date questions only).",
"title": "Center Nominal Iso"
},
"option_probabilities": {
"anyOf": [
{
"additionalProperties": {
"type": "number"
},
"type": "object"
},
{
"type": "null"
}
],
"default": null,
"description": "Per-option probabilities for multiple-choice questions, present only when the option list and the aggregate centers line up.",
"title": "Option Probabilities"
}
},
"title": "CommunityPrediction",
"type": "object"
},
"MyForecast": {
"description": "Your own LATEST forecast on one question, in the same compact shape as\nCommunityPrediction so the two can be read side by side.\n\nBuilt entirely from the ``my_forecasts.latest`` object Metaculus attaches to\nan authenticated posts response — personal data, which Metaculus does not\ngate by access tier, so this can be populated on a question whose Community\nPrediction is withheld. The forecast HISTORY is never read: ``start_time`` is\nthe latest forecast's timestamp, not the first one you made on the question,\nand on a continuous question the stored 201-value CDF is reduced to the\nquantiles below rather than returned. Full history and per-forecast scores\nare download_question_data territory.\n\nOne asymmetry with CommunityPrediction: Metaculus does not label which\nquantiles the CP's ``interval_lower``/``interval_upper`` represent, but it\ndoes label these — on a continuous question they are the 25th and 75th\npercentiles of your own stored distribution, whose median is ``center``.",
"properties": {
"start_time": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "ISO 8601 time the LATEST forecast was made — not the first one you made on this question. Null only when Metaculus returned no usable timestamp.",
"title": "Start Time"
},
"end_time": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "ISO 8601 time this forecast stops standing — either an auto-withdrawal you set through submit_forecast's end_time, or the moment you withdrew it. Null USUALLY means no withdrawal time applies, but not only: Metaculus suppresses an end_time that falls after the question's own close time, and this toolkit reports null for one it cannot parse or cannot render as a timestamp. A null here is therefore not proof you never set one.",
"title": "End Time"
},
"withdrawn": {
"description": "True when end_time is non-null and already in the past as of this response, i.e. the forecast no longer counts toward the aggregate. False when end_time is null OR still in the future — a scheduled auto-withdrawal that has not fired yet reads false, and so does a forecast that simply stood until the question closed, whose end_time Metaculus suppresses to null: it stopped counting because the question closed, not because you withdrew it. A forecast you withdrew BEFORE the question closed keeps its end_time and so still reads true on a closed or resolved question.",
"title": "Withdrawn",
"type": "boolean"
},
"probability_yes": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"description": "Your P(yes) on a BINARY question. Null on every other question type, and null on a binary question whose stored forecast did not come back as a usable P(no)/P(yes) pair. On a binary question `center` carries this same number under the Community Prediction's own field name; on other question types `center` carries a median or nothing while this stays null.",
"title": "Probability Yes"
},
"center": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"description": "Your forecast's center. For binary questions this is P(yes); for numeric/date/discrete questions it is the MEDIAN as an internal [0, 1] location. Null on multiple-choice questions (read option_probabilities — a per-option forecast has no single center), on any question type this toolkit does not summarize, and wherever the stored forecast carried no usable number.",
"title": "Center"
},
"interval_lower": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"description": "Your 25th percentile as an internal [0, 1] location, numeric/date/discrete questions only. Null on binary and multiple-choice: your own forecast there is a point estimate with no interval, unlike the Community Prediction, whose interval is the spread across forecasters.",
"title": "Interval Lower"
},
"interval_upper": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"description": "Your 75th percentile as an internal [0, 1] location, numeric/date/discrete questions only. Null on binary and multiple-choice — see interval_lower.",
"title": "Interval Upper"
},
"center_nominal": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"description": "center converted to the question's own units via question.scaling (numeric/date/discrete only; a POSIX timestamp for date questions). Null when the scaling is absent or degenerate.",
"title": "Center Nominal"
},
"interval_lower_nominal": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"title": "Interval Lower Nominal"
},
"interval_upper_nominal": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"title": "Interval Upper Nominal"
},
"center_nominal_iso": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "center_nominal as ISO 8601 (date questions only).",
"title": "Center Nominal Iso"
},
"option_probabilities": {
"anyOf": [
{
"additionalProperties": {
"type": "number"
},
"type": "object"
},
{
"type": "null"
}
],
"default": null,
"description": "Your per-option probabilities for multiple-choice questions. The stored array is aligned to the question's ever-list of options (`all_options_ever`), so that is what these are paired against, or the current option list when the payload carries no ever-list — on a question that has had an option DELETED this map can therefore name an option that no longer appears in `options`. Null when the list used is not the same length as the stored array, since then no number can be attributed to an option. Metaculus permits a null probability on an individual option; such an option is OMITTED from this map, so the entries need not sum to 1.",
"title": "Option Probabilities"
}
},
"required": [
"withdrawn"
],
"title": "MyForecast",
"type": "object"
},
"QuestionSummary": {
"description": "Compact view of one post. Free text, aggregation history and CDFs are\nomitted — use get_question for a post's full detail.",
"properties": {
"post_id": {
"title": "Post Id",
"type": "integer"
},
"question_id": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Question id for single-question posts. Null for groups, conditionals and notebooks — forecast tools need a question id, so use get_question to list a group's subquestion ids.",
"title": "Question Id"
},
"title": {
"title": "Title",
"type": "string"
},
"url": {
"title": "Url",
"type": "string"
},
"post_type": {
"description": "question, group_of_questions, conditional, or notebook.",
"title": "Post Type",
"type": "string"
},
"question_type": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "binary, numeric, discrete, date or multiple_choice.",
"title": "Question Type"
},
"status": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Status"
},
"resolved": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"default": null,
"title": "Resolved"
},
"resolution": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Resolution of a single-question post. Always null for groups, conditionals and notebooks — their resolutions sit on the individual subquestions, which get_question returns.",
"title": "Resolution"
},
"open_time": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Open Time"
},
"scheduled_close_time": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Scheduled Close Time"
},
"scheduled_resolve_time": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Scheduled Resolve Time"
},
"actual_resolve_time": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Actual Resolve Time"
},
"nr_forecasters": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Nr Forecasters"
},
"forecasts_count": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Forecasts Count"
},
"comment_count": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Comment Count"
},
"unit": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Unit"
},
"categories": {
"items": {
"type": "string"
},
"title": "Categories",
"type": "array"
},
"tournaments": {
"items": {
"type": "string"
},
"title": "Tournaments",
"type": "array"
},
"subquestion_count": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Subquestion Count"
},
"community_prediction": {
"anyOf": [
{
"$ref": "#/$defs/CommunityPrediction"
},
{
"type": "null"
}
],
"default": null,
"description": "Community Prediction for a single-question post. On a group or conditional post this is ONE subquestion's aggregate — the first that carried one and that the payload names with a question id, reported in community_prediction_question_id — not a post-level number, so do not quote it as the group's forecast. Use get_question for every subquestion's own aggregate."
},
"community_prediction_question_id": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Question id that community_prediction belongs to. Equals question_id on single-question posts, and names the stand-in subquestion on groups and conditionals, where the scan prefers a subquestion the payload gives a usable id. Null whenever community_prediction is null, and also on the malformed payload that carried no usable id for the question the aggregate came from.",
"title": "Community Prediction Question Id"
},
"cp_access": {
"description": "available when a Community Prediction came back for this post — on a group or conditional, for at least one subquestion, not all of them. absent when none did — usually the access tier, but also the case for a question nobody has forecast yet, and for notebooks. On groups, absent is weak evidence of gating: the feed only populates the top few subquestions, so ask get_question before concluding anything.",
"title": "Cp Access",
"type": "string"
},
"my_forecast": {
"anyOf": [
{
"$ref": "#/$defs/MyForecast"
},
{
"type": "null"
}
],
"default": null,
"description": "YOUR own latest forecast on a single-question post. On a group or conditional post this is ONE subquestion's forecast — the first that carried one and that the payload names with a question id, reported in my_forecast_question_id — not a post-level forecast; use get_question for every subquestion's own. Null when the response carried no forecast of yours for this post: you have not forecast it, or Metaculus did not attach your forecasts to this response. Unlike community_prediction this is never withheld by access tier."
},
"my_forecast_question_id": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Question id that my_forecast belongs to. Equals question_id on single-question posts, and names the stand-in subquestion on groups and conditionals, where the scan prefers a subquestion the payload gives a usable id. Null whenever my_forecast is null, and also on the malformed payload that carried no usable id for the question the forecast came from.",
"title": "My Forecast Question Id"
}
},
"required": [
"post_id",
"title",
"url",
"post_type",
"cp_access"
],
"title": "QuestionSummary",
"type": "object"
}
},
"properties": {
"questions": {
"items": {
"$ref": "#/$defs/QuestionSummary"
},
"title": "Questions",
"type": "array"
},
"returned": {
"title": "Returned",
"type": "integer"
},
"offset": {
"title": "Offset",
"type": "integer"
},
"limit": {
"title": "Limit",
"type": "integer"
},
"next_offset": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Offset to request for the next page. Null whenever has_more is not true, and never equal to the offset you just requested.",
"title": "Next Offset"
},
"has_more": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"default": null,
"description": "Whether another page exists. False when Metaculus returned fewer raw rows than `limit` (its `next` link lies on short pages) — judged on the upstream row count, so a page that lost malformed rows to client-side filtering still pages on. On a full page this is the upstream `next` link's presence; null only when the response carried no `next` field.",
"title": "Has More"
},
"cp_available_count": {
"description": "How many of the returned posts carried a Community Prediction.",
"title": "Cp Available Count",
"type": "integer"
},
"access_hint": {
"anyOf": [
{
"$ref": "#/$defs/AccessHint"
},
{
"type": "null"
}
],
"default": null
}
},
"required": [
"questions",
"returned",
"offset",
"limit",
"cp_available_count"
],
"title": "QuestionListResult",
"type": "object"
} # post_comment write
Post a comment on a Metaculus post, optionally attaching your latest forecast or keeping it private.
Full description
Post a comment on a Metaculus post. Takes a POST id (the number in a metaculus.com/questions/<id>/ URL). Set `included_forecast=true` to attach your latest forecast to the comment — the usual way to publish a rationale alongside a prediction — or `is_private=true` to keep it as a note only you can see. `parent_comment_id` makes it a reply. Metaculus returns the created comment; `comment_id` is null if the response carried no body. If this invocation runs out of time after the request was sent, the error says the comment MAY OR MAY NOT have been posted — check the post with get_my_comments before retrying, so a retry does not post it twice.
| Parameter | Type | Required | Description |
|---|---|---|---|
post_id | integer | Yes | Post to comment on. |
text | string | Yes | Comment body (Markdown). |
included_forecast | boolean | No | Attach your latest forecast to the comment. Default: false |
is_private | boolean | No | True makes it a private note visible only to you. Default: false |
parent_comment_id | integer | null | No | Reply to this comment instead of starting a thread. |
Parameter schema (JSON)
{
"properties": {
"post_id": {
"description": "Post to comment on.",
"title": "Post Id",
"type": "integer"
},
"text": {
"description": "Comment body (Markdown).",
"minLength": 1,
"title": "Text",
"type": "string"
},
"included_forecast": {
"default": false,
"description": "Attach your latest forecast to the comment.",
"title": "Included Forecast",
"type": "boolean"
},
"is_private": {
"default": false,
"description": "True makes it a private note visible only to you.",
"title": "Is Private",
"type": "boolean"
},
"parent_comment_id": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Reply to this comment instead of starting a thread.",
"title": "Parent Comment Id"
}
},
"required": [
"post_id",
"text"
],
"title": "PostCommentParams",
"type": "object",
"additionalProperties": false
} Result schema (JSON)
{
"properties": {
"comment_id": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Comment Id"
},
"post_id": {
"title": "Post Id",
"type": "integer"
},
"post_url": {
"title": "Post Url",
"type": "string"
},
"is_private": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"default": null,
"title": "Is Private"
},
"included_forecast": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"default": null,
"title": "Included Forecast"
},
"created_at": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Created At"
},
"message": {
"title": "Message",
"type": "string"
}
},
"required": [
"post_id",
"post_url",
"message"
],
"title": "PostCommentResult",
"type": "object"
} # submit_forecast write
Submit a Metaculus forecast: a binary probability, multiple-choice probabilities, or a continuous distribution from percentiles.
Full description
Submit a forecast on one question.
Takes a QUESTION id, not a post id — for a group or conditional post, read
the subquestion ids from get_question first.
Supply exactly one of:
- `probability_yes` — binary questions, strictly between 0 and 1.
- `probability_yes_per_category` — multiple choice; the values must sum to 1
and the labels must be the question's own options.
- `percentiles` — continuous questions (numeric, discrete, date). Give your
distribution as percentile -> value in the question's own units, e.g.
`{"percentile_25": 500, "50": 650, "percentile_75": 700}`. The Lambda
fetches the question's scaling, maps each value to Metaculus's internal
[0, 1] location (linear, or logarithmic when `zero_point` is set; date
questions accept ISO 8601 strings or POSIX timestamps),
interpolates linearly between the set points, and standardizes the result
into the inbound_outcome_count+1 point CDF the API accepts. If your
percentiles do not reach an OPEN bound, add `below_lower_bound` /
`above_upper_bound` for the mass outside it; a CLOSED bound is anchored at
zero outside mass automatically and rejects a non-zero value.
Standardizing adds a 1%
uniform component, which is what enforces the minimum step and the bound
rules; it slightly flattens the distribution.
- `continuous_cdf` — a ready-made CDF, submitted UNCHANGED. It is checked
against Metaculus's rules (length = inbound_outcome_count+1, monotonicity,
minimum step, bound pinning) and rejected with the specific rule if it
breaks one; it is never rescaled to fit. Its maximum-step check uses the
looser 0.59 the API schema allows rather than the 0.2 the generation guide
recommends — the spec states both, so a CDF between them is forwarded and
Metaculus decides.
`end_time` sets a timestamp at which Metaculus automatically withdraws the
forecast. `return_cdf=true` echoes the submitted CDF back.
Forecasting permanently unlocks that question's text and resolution for your
account.
Forecast writes additionally need the account's own "API forecasting" switch
turned on (a one-button confirmation at
https://www.metaculus.com/accounts/settings/account/#api-forecasting-access,
separate from the data access tier, with a linked bot account as the route
for automated forecasting); the 403 you get before that says so and how.
If this invocation runs out of time mid-request, the error says which of two
things happened. When nothing reached Metaculus it says so outright and the
call is safe to repeat. When it cannot tell — the request had already been
sent — it says the forecast MAY OR MAY NOT have been recorded; read it back
with get_my_predictions before retrying, rather than assuming it failed — its
`my_forecast` reports your LATEST forecast's value, when it was made, and
`withdrawn`. Latest is not the same as standing: a forecast whose `end_time`
has already passed is still reported as the latest one, flagged
`withdrawn: true`, so check `withdrawn` before treating the value as live. | Parameter | Type | Required | Description |
|---|---|---|---|
question_id | integer | Yes | QUESTION id, not post id. For a group or conditional post, take the id from get_question's subquestions. |
post_id | integer | null | No | Post that owns the question. Only needed for continuous forecasts, and only when it differs from question_id. |
probability_yes | number | null | No | Binary questions: P(yes), strictly between 0 and 1. |
probability_yes_per_category | object | null | No | Multiple-choice questions: probability per option label. Must sum to 1. |
percentiles | object | null | No | Continuous questions: your distribution as percentile -> value, e.g. {'percentile_25': 500, '50': 650, 'percentile_75': 700}. Values are in the question's own units; date questions accept an ISO 8601 string or a POSIX timestamp. |
below_lower_bound | number | null | No | Probability mass strictly below range_min. Required with percentiles when the question's lower bound is open and your percentiles do not reach it; must be 0 or omitted on a closed lower bound. |
above_upper_bound | number | null | No | Probability mass strictly above range_max. |
continuous_cdf | array<number> | null | No | Power users: a ready-made CDF with inbound_outcome_count+1 values (201 normally). Submitted unchanged after validation — it is NOT standardized. |
end_time | string | null | No | ISO 8601 timestamp at which Metaculus automatically withdraws this forecast. |
return_cdf | boolean | No | Echo the full submitted CDF back. It is long — leave off unless you need to inspect it. Default: false |
Parameter schema (JSON)
{
"properties": {
"question_id": {
"description": "QUESTION id, not post id. For a group or conditional post, take the id from get_question's subquestions.",
"title": "Question Id",
"type": "integer"
},
"post_id": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Post that owns the question. Only needed for continuous forecasts, and only when it differs from question_id.",
"title": "Post Id"
},
"probability_yes": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"description": "Binary questions: P(yes), strictly between 0 and 1.",
"title": "Probability Yes"
},
"probability_yes_per_category": {
"anyOf": [
{
"additionalProperties": {
"type": "number"
},
"type": "object"
},
{
"type": "null"
}
],
"default": null,
"description": "Multiple-choice questions: probability per option label. Must sum to 1.",
"title": "Probability Yes Per Category"
},
"percentiles": {
"anyOf": [
{
"additionalProperties": {
"anyOf": [
{
"type": "number"
},
{
"type": "string"
}
]
},
"type": "object"
},
{
"type": "null"
}
],
"default": null,
"description": "Continuous questions: your distribution as percentile -> value, e.g. {'percentile_25': 500, '50': 650, 'percentile_75': 700}. Values are in the question's own units; date questions accept an ISO 8601 string or a POSIX timestamp.",
"title": "Percentiles"
},
"below_lower_bound": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"description": "Probability mass strictly below range_min. Required with percentiles when the question's lower bound is open and your percentiles do not reach it; must be 0 or omitted on a closed lower bound.",
"title": "Below Lower Bound"
},
"above_upper_bound": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"description": "Probability mass strictly above range_max.",
"title": "Above Upper Bound"
},
"continuous_cdf": {
"anyOf": [
{
"items": {
"type": "number"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"description": "Power users: a ready-made CDF with inbound_outcome_count+1 values (201 normally). Submitted unchanged after validation — it is NOT standardized.",
"title": "Continuous Cdf"
},
"end_time": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "ISO 8601 timestamp at which Metaculus automatically withdraws this forecast.",
"title": "End Time"
},
"return_cdf": {
"default": false,
"description": "Echo the full submitted CDF back. It is long — leave off unless you need to inspect it.",
"title": "Return Cdf",
"type": "boolean"
}
},
"required": [
"question_id"
],
"title": "SubmitForecastParams",
"type": "object",
"additionalProperties": false
} Result schema (JSON)
{
"properties": {
"question_id": {
"title": "Question Id",
"type": "integer"
},
"mode": {
"description": "binary, multiple_choice, continuous_percentiles, or continuous_cdf.",
"title": "Mode",
"type": "string"
},
"end_time": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "End Time"
},
"cdf_length": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Cdf Length"
},
"cdf_first": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"title": "Cdf First"
},
"cdf_last": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"title": "Cdf Last"
},
"standardized": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"default": null,
"description": "True when the CDF was built from percentiles and passed through standardize_cdf. False when a raw continuous_cdf was submitted unchanged. Null for binary and multiple-choice forecasts.",
"title": "Standardized"
},
"continuous_cdf": {
"anyOf": [
{
"items": {
"type": "number"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"description": "The submitted CDF, only when return_cdf was true.",
"title": "Continuous Cdf"
},
"message": {
"title": "Message",
"type": "string"
}
},
"required": [
"question_id",
"mode",
"message"
],
"title": "SubmitForecastResult",
"type": "object"
} # withdraw_forecast write
Withdraw your current Metaculus forecast on one or several questions.
Full description
Withdraw your current forecast on one or more questions. Takes QUESTION ids (up to 50 per call — a Toolforest-side cap; Metaculus documents no limit) and sends them in a single request, which is how group and conditional questions are withdrawn together. Withdrawing removes your standing forecast from the aggregate going forward; it does not erase your forecast history. Metaculus returns no per-question result, so `withdrawn_count` is the size of the accepted request, not confirmation that each question had a live forecast to withdraw. All the ids go in ONE request, so a time-out is all-or-nothing rather than partial — but which of the two it is may be unknown. When the invocation runs out of time after the request was sent, the error says the withdrawal MAY OR MAY NOT have been applied; check with get_my_predictions before retrying — and read its `withdrawn` flag, not the presence of `my_forecast`, because a withdrawn forecast Metaculus still reports as your latest comes back with a value and `withdrawn: true` rather than as a null `my_forecast`.
| Parameter | Type | Required | Description |
|---|---|---|---|
question_ids | array<integer> | Yes | QUESTION ids whose current forecasts should be withdrawn. |
Parameter schema (JSON)
{
"properties": {
"question_ids": {
"description": "QUESTION ids whose current forecasts should be withdrawn.",
"items": {
"type": "integer"
},
"maxItems": 50,
"minItems": 1,
"title": "Question Ids",
"type": "array"
}
},
"required": [
"question_ids"
],
"title": "WithdrawForecastParams",
"type": "object",
"additionalProperties": false
} Result schema (JSON)
{
"properties": {
"question_ids": {
"items": {
"type": "integer"
},
"title": "Question Ids",
"type": "array"
},
"withdrawn_count": {
"description": "How many question ids were sent in the accepted request. Metaculus returns no per-question result, so this is the request size, not a confirmation that each had a live forecast.",
"title": "Withdrawn Count",
"type": "integer"
},
"message": {
"title": "Message",
"type": "string"
}
},
"required": [
"question_ids",
"withdrawn_count",
"message"
],
"title": "WithdrawForecastResult",
"type": "object"
}