API
Create questions, start forecasts and read results from your own code, with examples in TypeScript, Python and curl.
Use the Prolepsia API to create questions, start forecasts and read the results from your own code. Every example on this page comes in TypeScript, Python and curl.
- Base URL:
https://app.prolepsia.com/api/v1/external - Format: JSON in and out, UTF-8. File uploads use
multipart/form-data. - Size: 182 operations, all listed at the end of this page.
- Try it in the browser: interactive API reference · OpenAPI schema
A forecast takes from about 5 minutes to about 1.5 hours, depending on its run mode. Starting one returns a task id within a second. The forecast keeps running on our servers after your request ends. Save the task id, then check on it every few minutes or let a webhook tell you when it is done.
Before you start
Create an API key
- In Prolepsia, open Settings and choose API and AI clients.
- Under API keys, choose Create new key.
- Name the key, tick the permissions it needs, pick when it expires and choose Create key.
- Copy the key straight away. It starts with
pre_, and Prolepsia shows it only once.
Keep the key on your server. The API sends no CORS headers, so a web page on another site cannot call it, and a key in browser code would leak.
Permissions
A key can do only what its permissions (scopes) allow, and never more than its owner can do in the app.
| Scope | Lets the key |
|---|---|
questions:read |
Read questions, their tags, context notes and files, question subscriptions and question templates. |
questions:write |
Create, edit and import questions, draft questions with AI, manage notes, files, share links, subscriptions and question templates. |
forecasts:read |
Read forecasts and their status, your forecast chats, schedules, watches, forecast presets, webhooks and webhook deliveries. Check your credit balance. |
forecasts:write |
Start and cancel forecasts, chat about a forecast, manage schedules, watches, forecast presets and webhooks, and test or replay webhook deliveries. |
connections:read |
List your connected MCP servers and the tools they offer. Keys and sign-ins are never returned. |
connections:write |
Add, change, test and delete connected MCP servers, and start their sign-in. |
Most integrations need the first four. Add the connection scopes only if the key manages connected servers. Sending context notes along with a new forecast needs questions:write as well as forecasts:write.
Credits
Every forecast costs credits. A forecast started through the API runs in your organization's default run mode. Check your balance and the prices with GET /credits. When the balance is too low, starting a forecast answers 409 and nothing is charged.
Make your first call
Send your API key in the Authorization header, as Authorization: Bearer pre_.... The X-Api-Key: pre_... header works too.
Set two environment variables, then check the key by reading your credit balance:
export PROLEPSIA_API_KEY="pre_..."
export PROLEPSIA_BASE_URL="https://app.prolepsia.com/api/v1/external"
The TypeScript and Python examples on this page reuse the small helper below. It sends the key, turns JSON into objects and raises an error with the API's message when a call fails. The TypeScript version runs on Node.js 20 or later, Bun and Deno with no packages. The Python version needs requests (pip install requests).
const BASE_URL =
process.env.PROLEPSIA_BASE_URL ?? "https://app.prolepsia.com/api/v1/external";
const API_KEY = process.env.PROLEPSIA_API_KEY ?? "";
/** Call the Prolepsia API and return the JSON answer. */
async function prolepsia<T = any>(
path: string,
init: {
method?: string;
body?: unknown;
headers?: Record<string, string>;
} = {},
): Promise<T> {
const isForm = init.body instanceof FormData;
const response = await fetch(`${BASE_URL}${path}`, {
method: init.method ?? (init.body === undefined ? "GET" : "POST"),
headers: {
Authorization: `Bearer ${API_KEY}`,
...(init.body === undefined || isForm
? {}
: { "Content-Type": "application/json" }),
...init.headers,
},
body: isForm
? (init.body as FormData)
: init.body === undefined
? undefined
: JSON.stringify(init.body),
});
if (response.status === 204) return undefined as T;
const data = await response.json();
if (!response.ok) {
throw new Error(
`${response.status} ${data.error.type}: ${data.error.message}`,
);
}
return data as T;
}
const credits = await prolepsia("/credits");
console.log(credits.balance, credits.unlimited);import os
import requests
BASE_URL = os.environ.get(
"PROLEPSIA_BASE_URL", "https://app.prolepsia.com/api/v1/external"
)
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['PROLEPSIA_API_KEY']}"
def prolepsia(method: str, path: str, **kwargs):
"""Call the Prolepsia API and return the JSON answer."""
response = session.request(method, f"{BASE_URL}{path}", timeout=60, **kwargs)
if response.status_code == 204:
return None
data = response.json()
if not response.ok:
error = data["error"]
raise RuntimeError(f"{response.status_code} {error['type']}: {error['message']}")
return data
credits = prolepsia("GET", "/credits")
print(credits["balance"], credits["unlimited"])curl -sS "$PROLEPSIA_BASE_URL/credits" \
-H "Authorization: Bearer $PROLEPSIA_API_KEY"The answer:
{
"balance": 240,
"unlimited": false,
"topUps": [],
"prices": [
{ "key": "forecast.custom_plan", "credits": 100 },
{ "key": "forecast.deep", "credits": 150 },
{ "key": "forecast.light", "credits": 2 },
{ "key": "forecast.max", "credits": 300 },
{ "key": "forecast.moderate", "credits": 10 },
{ "key": "forecast.standard", "credits": 100 },
{ "key": "monitoring.question_month", "credits": 5 }
],
"plan": {
"key": "pro",
"name": "Pro",
"status": "active",
"currentPeriodEnd": "2026-11-01T12:00:00.000Z",
"cancelAtPeriodEnd": false,
"creditsPerPeriod": 135
}
}
plan is your organization's plan. Its key is payg (Pay as you go),
pro, team or enterprise. currentPeriodEnd is the end of the paid
period, which is the renewal date unless cancelAtPeriodEnd is true. On
Pay as you go it is null and creditsPerPeriod is 0. status is
past_due when the last renewal payment failed: the plan stays on and adds no
new monthly credits until a retry works or the plan ends after seven days.
A missing or wrong key answers 401 with the error type invalid_api_key.
Forecast a question, step by step
1. Create the question
A question needs a type and a title. Write the resolutionCriteria with care: they decide what the forecast means. The question types section lists the fields for each type.
const question = await prolepsia("/questions", {
body: {
type: "binary",
title:
"Will the Bank of England cut Bank Rate at its December 2026 meeting?",
description:
"The Monetary Policy Committee announces its decision on 17 December 2026.",
resolutionCriteria:
"Resolves YES if the Bank of England announces a cut to Bank Rate on 17 December 2026. Resolves NO otherwise.",
tags: ["rates"],
},
});
console.log(question.id);question = prolepsia(
"POST",
"/questions",
json={
"type": "binary",
"title": "Will the Bank of England cut Bank Rate at its December 2026 meeting?",
"description": "The Monetary Policy Committee announces its decision on 17 December 2026.",
"resolutionCriteria": "Resolves YES if the Bank of England announces a cut to Bank Rate on 17 December 2026. Resolves NO otherwise.",
"tags": ["rates"],
},
)
print(question["id"])curl -sS -X POST "$PROLEPSIA_BASE_URL/questions" \
-H "Authorization: Bearer $PROLEPSIA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "binary",
"title": "Will the Bank of England cut Bank Rate at its December 2026 meeting?",
"description": "The Monetary Policy Committee announces its decision on 17 December 2026.",
"resolutionCriteria": "Resolves YES if the Bank of England announces a cut to Bank Rate on 17 December 2026. Resolves NO otherwise.",
"tags": ["rates"]
}'The answer is the saved question, with status 201:
{
"id": "2c54dab4-f4e6-439d-a35d-1b7c71205259",
"type": "binary",
"title": "Will the Bank of England cut Bank Rate at its December 2026 meeting?",
"description": "The Monetary Policy Committee announces its decision on 17 December 2026.",
"resolutionCriteria": "Resolves YES if the Bank of England announces a cut to Bank Rate on 17 December 2026. Resolves NO otherwise.",
"finePrint": "",
"options": [],
"lowerBound": null,
"upperBound": null,
"openLowerBound": false,
"openUpperBound": false,
"scaleType": null,
"valueFormat": null,
"unitLabel": null,
"currencyCode": null,
"period": null,
"callsign": "HYGROMETER-PLANET-MARGIN",
"optionsAreMutuallyExclusive": true,
"optionsAreComprehensive": true,
"visibility": "private",
"createdVia": "api",
"tags": [{ "name": "rates", "slug": "rates" }],
"createdAt": "2026-10-12T07:45:41.413Z",
"updatedAt": "2026-10-12T07:45:41.413Z"
}
2. Start a forecast
Start a forecast with POST /forecasts and the questionId. Send an Idempotency-Key header with a value you choose, such as a UUID. If the answer gets lost and you send the same request again with the same key, Prolepsia returns the first answer instead of starting a second paid forecast.
const task = await prolepsia("/forecasts", {
body: { questionId: question.id },
headers: { "Idempotency-Key": crypto.randomUUID() },
});
console.log(task.id, task.status); // "queued"import uuid
task = prolepsia(
"POST",
"/forecasts",
json={"questionId": question["id"]},
headers={"Idempotency-Key": str(uuid.uuid4())},
)
print(task["id"], task["status"]) # "queued"curl -sS -X POST "$PROLEPSIA_BASE_URL/forecasts" \
-H "Authorization: Bearer $PROLEPSIA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: boe-december-2026-run-1" \
-d '{"questionId": "2c54dab4-f4e6-439d-a35d-1b7c71205259"}'The answer has status 202 and a forecast task:
{
"id": "073193c3-bc0b-4218-b7a9-c2b54143b3d3",
"type": "forecast_task",
"status": "queued",
"createdAt": "2026-10-12T07:45:46.551Z",
"startedAt": null,
"finishedAt": null,
"question": {
"id": "2c54dab4-f4e6-439d-a35d-1b7c71205259",
"type": "binary",
"title": "Will the Bank of England cut Bank Rate at its December 2026 meeting?"
},
"pollingUrl": "/api/v1/external/forecasts/073193c3-bc0b-4218-b7a9-c2b54143b3d3",
"progressMessage": "Queued for forecast generation.",
"partial": true,
"completedSubforecastCount": 0,
"plannedSubforecastCount": 3,
"subforecasts": [],
"creditsCharged": 0,
"creditsRefunded": 0,
"runMode": "moderate"
}
The question object in the answer holds every question field. It is shortened here.
Add runMode to choose how deep the forecast goes, as the run mode picker in the app does: light, moderate, standard, deep or max. A deeper mode uses stronger models and more research, takes longer and costs more credits. GET /run-modes lists each mode's rough time and price, and the mode a forecast gets when you leave runMode out:
curl -sS "$PROLEPSIA_BASE_URL/run-modes" \
-H "Authorization: Bearer $PROLEPSIA_API_KEY"
curl -sS -X POST "$PROLEPSIA_BASE_URL/forecasts" \
-H "Authorization: Bearer $PROLEPSIA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: boe-december-2026-deep-1" \
-d '{"questionId": "2c54dab4-f4e6-439d-a35d-1b7c71205259", "runMode": "deep"}'
You can also skip step 1 and send the question inline: {"question": {"type": "binary", "title": "...", ...}}. Send question or questionId, not both. A question can have only one forecast running at a time; starting another answers 409 with the error type forecast_already_running and the running task's id in error.activeTaskId.
3. Wait for the result
Read the task with GET /forecasts/{taskId} until its status is one of the three final values: completed, failed or cancelled. While the task is still running, the answer carries a Retry-After header with the number of seconds to wait: 15 while queued and 120 while running. A webhook saves you the polling.
| Status | Meaning |
|---|---|
queued |
Waiting to start. |
running_subforecasts |
Several separate forecasts, called subforecasts, are researching the question at the same time. |
synthesizing |
The subforecasts are being combined into one answer. |
completed |
Final. The answer is in forecast. |
failed |
Final. Nothing to read except the subforecasts that finished. error.message says what happened. |
cancelled |
Final. Someone cancelled it. |
const FINAL = new Set(["completed", "failed", "cancelled"]);
async function waitForForecast(taskId: string) {
for (;;) {
const response = await fetch(`${BASE_URL}/forecasts/${taskId}`, {
headers: { Authorization: `Bearer ${API_KEY}` },
});
const task = await response.json();
if (!response.ok && response.status !== 429) {
throw new Error(`${task.error.type}: ${task.error.message}`);
}
if (response.ok && FINAL.has(task.status)) return task;
const seconds = Number(response.headers.get("Retry-After") ?? "120");
await new Promise((resolve) => setTimeout(resolve, seconds * 1000));
}
}
const finished = await waitForForecast(task.id);
console.log(finished.status, finished.forecast?.forecastData);import time
FINAL = {"completed", "failed", "cancelled"}
def wait_for_forecast(task_id: str) -> dict:
while True:
response = session.get(f"{BASE_URL}/forecasts/{task_id}", timeout=60)
if response.status_code != 429:
response.raise_for_status()
task = response.json()
if task["status"] in FINAL:
return task
time.sleep(int(response.headers.get("Retry-After", "120")))
finished = wait_for_forecast(task["id"])
print(finished["status"], finished.get("forecast", {}).get("forecastData"))curl -sS -i "$PROLEPSIA_BASE_URL/forecasts/073193c3-bc0b-4218-b7a9-c2b54143b3d3" \
-H "Authorization: Bearer $PROLEPSIA_API_KEY"A response that says partial: true has no combined answer yet. Never report a subforecast's number as the forecast: subforecasts disagree with each other on purpose, and the final step settles the answer.
4. Read the result
A completed task has the answer in forecast:
{
"id": "2e1cc0b7-fa66-4ad4-a583-6da0e1f07ac7",
"type": "forecast_task",
"status": "completed",
"createdAt": "2026-10-12T07:46:35.968Z",
"startedAt": "2026-10-12T07:46:36.120Z",
"finishedAt": "2026-10-12T08:31:02.114Z",
"question": {
"id": "2c54dab4-f4e6-439d-a35d-1b7c71205259",
"type": "binary",
"title": "Will the Bank of England cut Bank Rate at its December 2026 meeting?"
},
"pollingUrl": "/api/v1/external/forecasts/2e1cc0b7-fa66-4ad4-a583-6da0e1f07ac7",
"progressMessage": "Forecast generation completed.",
"forecast": {
"id": "695142eb-203f-415b-81c7-ccaf2747b0a6",
"generatedAt": "2026-10-12T08:29:44.984Z",
"forecastData": {
"format": "probability",
"payload": { "probability": 0.34 },
"questionType": "binary"
},
"report": {
"status": "completed",
"short": "Prolepsia puts the chance of a cut in December at 34%. ...",
"medium": "## Summary\n...",
"detailed": "## Summary\n..."
},
"sources": [
{
"url": "https://www.bankofengland.co.uk/monetary-policy-summary-and-minutes/2026/november-2026",
"kind": "web",
"display_name": "Bank of England: Monetary Policy Summary, November 2026"
}
]
},
"partial": false,
"completedSubforecastCount": 3,
"plannedSubforecastCount": 3,
"subforecasts": [
{
"sequenceIndex": 0,
"status": "completed",
"completedAt": "2026-10-12T08:12:40.118Z"
}
],
"creditsCharged": 10,
"creditsRefunded": 0
}
idis the task id.forecast.idis a different id: the forecast's own.forecast.forecastDatais the number. Its shape depends on the question type; see Forecast results.forecast.reportis the written report at three lengths. It is written after the number, so it can still bependingorwritingwhen the task is alreadycompleted.forecast.sourceslists what the forecast relied on. Each source has akindand adisplay_name, and usually aurl.pollingUrlis a path that already starts with/api/v1/external. Join it with the host, not with the base URL.
The whole flow in one file
Copy one of these files, set PROLEPSIA_API_KEY, and run it. It creates a question, starts a forecast and prints the result when it is ready.
// forecast.ts. Run with: npx tsx forecast.ts, bun forecast.ts or deno run -A forecast.ts
const BASE_URL =
process.env.PROLEPSIA_BASE_URL ?? "https://app.prolepsia.com/api/v1/external";
const HEADERS = {
Authorization: `Bearer ${process.env.PROLEPSIA_API_KEY}`,
"Content-Type": "application/json",
};
const FINAL = new Set(["completed", "failed", "cancelled"]);
async function call(path: string, init: RequestInit = {}) {
const response = await fetch(`${BASE_URL}${path}`, {
...init,
headers: { ...HEADERS, ...init.headers },
});
const data = await response.json();
if (!response.ok) {
throw new Error(
`${response.status} ${data.error.type}: ${data.error.message}`,
);
}
return {
data,
retryAfter: Number(response.headers.get("Retry-After") ?? 120),
};
}
const { data: task } = await call("/forecasts", {
method: "POST",
headers: { "Idempotency-Key": crypto.randomUUID() },
body: JSON.stringify({
question: {
type: "binary",
title: "Will Brent crude settle above $90 a barrel on 31 December 2026?",
resolutionCriteria:
"Resolves YES if the ICE Brent front-month settlement price on 31 December 2026 is above 90.00 USD. Resolves NO otherwise.",
},
}),
});
console.log(`Started task ${task.id} in ${task.runMode} mode.`);
for (;;) {
const { data: latest, retryAfter } = await call(`/forecasts/${task.id}`);
if (FINAL.has(latest.status)) {
if (latest.status !== "completed") {
console.log(
`The forecast ended as ${latest.status}.`,
latest.error ?? "",
);
break;
}
const probability = latest.forecast.forecastData.payload.probability;
console.log(`Chance of YES: ${(probability * 100).toFixed(1)}%`);
console.log(
latest.forecast.report?.short ?? "The report is still being written.",
);
break;
}
console.log(
`${latest.status}: ${latest.completedSubforecastCount} subforecasts done`,
);
await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));
}# forecast.py. Run with: python forecast.py
import os
import time
import uuid
import requests
BASE_URL = os.environ.get("PROLEPSIA_BASE_URL", "https://app.prolepsia.com/api/v1/external")
FINAL = {"completed", "failed", "cancelled"}
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['PROLEPSIA_API_KEY']}"
def call(method: str, path: str, **kwargs):
response = session.request(method, f"{BASE_URL}{path}", timeout=60, **kwargs)
data = response.json()
if not response.ok:
raise RuntimeError(f"{response.status_code} {data['error']['type']}: {data['error']['message']}")
return data, int(response.headers.get("Retry-After", "120"))
task, _ = call(
"POST",
"/forecasts",
headers={"Idempotency-Key": str(uuid.uuid4())},
json={
"question": {
"type": "binary",
"title": "Will Brent crude settle above $90 a barrel on 31 December 2026?",
"resolutionCriteria": "Resolves YES if the ICE Brent front-month settlement price on 31 December 2026 is above 90.00 USD. Resolves NO otherwise.",
}
},
)
print(f"Started task {task['id']} in {task['run_mode']} mode.")
while True:
latest, retry_after = call("GET", f"/forecasts/{task['id']}")
if latest["status"] in FINAL:
if latest["status"] != "completed":
print(f"The forecast ended as {latest['status']}.", latest.get("error", ""))
break
probability = latest["forecast"]["forecastData"]["payload"]["probability"]
print(f"Chance of YES: {probability:.1%}")
report = latest["forecast"].get("report") or {}
print(report.get("short") or "The report is still being written.")
break
print(f"{latest['status']}: {latest['completed_subforecast_count']} subforecasts done")
time.sleep(retry_after)Question types
Every question has a type and a title of up to 500 characters. These fields are optional on every type: description, resolutionCriteria, finePrint, tags (up to 20 labels for your own filing, which the forecaster never sees) and visibility (private or organization).
| Type | Asks | Needs | Must not have |
|---|---|---|---|
binary |
Yes or no | Nothing more | options, bounds |
multiple_choice |
Which of several outcomes | 2 to 200 different options |
Bounds |
numeric |
A number | lowerBound, upperBound and scaleType (linear or logarithmic; logarithmic needs positive bounds) |
options |
discrete |
A whole number | Whole-number bounds, at least 2 and at most 500 apart | scaleType |
date |
When something happens | Bounds as whole Unix timestamps in seconds | scaleType, options |
A numeric or discrete question can also state its unit, so a reader can tell a fraction from a percent and dollars from barrels:
| Field | Meaning |
|---|---|
valueFormat |
plain (a number followed by unitLabel), currency (an amount of money in currencyCode), percent (percentage points, so 3.5 means 3.5%) or fraction (parts of one, so 0.10 means 10%) |
unitLabel |
What the number counts, up to 60 characters, such as "barrels per day" |
currencyCode |
An ISO 4217 code such as USD. Required for currency, refused for every other format |
period |
The period the value covers, up to 60 characters, such as "per month" or "in 2026" |
All four are optional and null by default. A label or period needs a valueFormat. Binary, multiple choice and date questions refuse them. Leave a field out when you do not know it; never guess. Before the first forecast every unit field can change. After it, the four fields lock together: a unit can be set once, only while all four are empty.
openLowerBound and openUpperBound (default false) say whether the answer can fall outside the bounds. optionsAreMutuallyExclusive and optionsAreComprehensive (default true) describe the options of a multiple choice question.
Numeric question:
{
"type": "numeric",
"title": "What will NVIDIA report as data-center revenue for its fiscal Q4 2027, in USD billions?",
"resolutionCriteria": "The data-center segment revenue in NVIDIA's fiscal Q4 2027 earnings press release.",
"lowerBound": 20,
"upperBound": 120,
"scaleType": "linear",
"openUpperBound": true,
"valueFormat": "currency",
"currencyCode": "USD",
"unitLabel": "billion"
}
Multiple choice question:
{
"type": "multiple_choice",
"title": "Which company will have the largest market value on 1 January 2028?",
"resolutionCriteria": "The company with the highest market capitalization at the close of US trading on 31 December 2027, as reported by companiesmarketcap.com.",
"options": ["Apple", "Microsoft", "NVIDIA", "Other"]
}
Date question (the bounds are 1 January 2026 and 1 January 2034):
{
"type": "date",
"title": "When will the first crewed Artemis lunar landing take place?",
"resolutionCriteria": "The UTC date on which a crewed vehicle under NASA's Artemis program lands on the Moon, as announced by NASA.",
"lowerBound": 1767225600,
"upperBound": 2019686400
}
A question that breaks a rule answers 400 with the error type invalid_request_error. The message names the field, such as question: lowerBound: Numeric, discrete, and date questions require bounds.
Other ways to create a question
- Draft it with AI.
POST /questions/normalizewith{"title": "..."}turns a rough question into a complete one, using a model and web search. It saves nothing and takes tens of seconds. Pass the returnedquestiontoPOST /questionsorPOST /forecasts. When the answer isstatus: "unsupported", readsuggestedReframe. - Import it from a market.
POST /questions/importwith{"reference": "<a Metaculus, Kalshi or Polymarket link>"}copies the question. No forecast starts. - Start from a template. Send
templateIdandtemplateValuesand the template writes the question. See Start from a question template. - Edit it.
PATCH /questions/{questionId}changes only the fields you send. Once a question has a forecast, onlydescription,resolutionCriteria,finePrint,visibilityandtagscan change.
Forecast results
forecast.forecastData holds questionType, format and payload. The payload depends on the question type.
| Question type | format |
payload |
|---|---|---|
binary |
probability |
probability: the chance of YES, from 0 to 1. |
multiple_choice |
option_probabilities |
probabilities: each option's chance, keyed by the option text. |
numeric and date |
probability_mass_function |
percentiles (p10, p50, p90), probability_mass_function, cumulative_distribution_function, bucket_edges, above_upper_probability and scaleType. Dates are Unix timestamps in seconds. |
discrete |
discrete_probability_array |
probabilities for each value in nominal_values, percentiles (p10, p50, p90) and cell_boundaries. |
For most numeric and date questions, percentiles.p50 (the median) with p10 and p90 (the range the answer falls in 80% of the time) is all you need.
The written report
| Field | What it holds |
|---|---|
status |
pending, writing, completed or failed. |
short |
One paragraph, up to about 120 words. |
medium |
Markdown with headings, up to about 600 words. |
detailed |
Markdown with headings, up to about 2,000 words. |
The three texts are null until status is completed. The forecast.report.completed webhook tells you when the report is ready.
Smaller answers and work in progress
?fields=numericleaves out the report and the sources. Use it when you only need the number.?includeSubforecasts=trueadds each finished subforecast'sforecastData,writeUpandsources. Use it once, when you want to read them: the answer can grow past a megabyte.
List forecasts
GET /forecastslists forecasts across your questions, newest first. Filter withstatus,questionIdortaskId(repeat the parameter, or separate ids with commas), andautoforecast(watch,scheduleorany) for questions that rerun on their own.scope=all_visibleadds forecasts others shared with you. Each item'sreportholds onlystatusandshort.GET /questions/{questionId}/forecastslists the forecasts of one question, in every state unless you passstatus.
const page = await prolepsia(
`/forecasts?questionId=${question.id}&fields=numeric&limit=20`,
);
for (const item of page.data) {
console.log(item.taskId, item.status, item.forecastData?.payload);
}page = prolepsia(
"GET",
"/forecasts",
params={"questionId": question["id"], "fields": "numeric", "limit": 20},
)
for item in page["data"]:
print(item["taskId"], item["status"], (item.get("forecastData") or {}).get("payload"))curl -sS "$PROLEPSIA_BASE_URL/forecasts?questionId=2c54dab4-f4e6-439d-a35d-1b7c71205259&fields=numeric&limit=20" \
-H "Authorization: Bearer $PROLEPSIA_API_KEY"Many forecasts at once
POST /forecasts/batch starts up to 25 forecasts in one call. Each item in items is a whole POST /forecasts body. A bad item is reported on its own and the others still start, so read counts and each item's status. Always send an Idempotency-Key: without one, a retry starts every accepted item again.
const batch = await prolepsia("/forecasts/batch", {
headers: { "Idempotency-Key": crypto.randomUUID() },
body: {
items: [
{ questionId: "2c54dab4-f4e6-439d-a35d-1b7c71205259" },
{
question: {
type: "binary",
title:
"Will the Federal Reserve cut rates at its December 2026 meeting?",
resolutionCriteria:
"Resolves YES if the FOMC statement on 9 December 2026 lowers the target range.",
},
},
],
},
});
console.log(batch.counts); // { total: 2, accepted: 2, rejected: 0 }
const taskIds = batch.items
.filter((item: any) => item.status === "accepted")
.map((item: any) => item.taskId);batch = prolepsia(
"POST",
"/forecasts/batch",
headers={"Idempotency-Key": str(uuid.uuid4())},
json={
"items": [
{"questionId": "2c54dab4-f4e6-439d-a35d-1b7c71205259"},
{
"question": {
"type": "binary",
"title": "Will the Federal Reserve cut rates at its December 2026 meeting?",
"resolutionCriteria": "Resolves YES if the FOMC statement on 9 December 2026 lowers the target range.",
}
},
]
},
)
print(batch["counts"]) # {'total': 2, 'accepted': 2, 'rejected': 0}
task_ids = [item["taskId"] for item in batch["items"] if item["status"] == "accepted"]curl -sS -X POST "$PROLEPSIA_BASE_URL/forecasts/batch" \
-H "Authorization: Bearer $PROLEPSIA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: rates-batch-2026-10-12" \
-d '{"items": [{"questionId": "2c54dab4-f4e6-439d-a35d-1b7c71205259"}]}'Each item in the answer has an index, a status of accepted or rejected, and either the taskId or an error. Nothing stores the batch itself, so keep the task ids.
Cancel forecasts
POST /forecasts/{taskId}/cancel stops one queued or running forecast. POST /forecasts/cancel with {"taskIds": [...]} stops up to 25 at once and reports each one: cancelled, already_finished or rejected. Cancelling again is safe. Work already done is not refunded.
Add your own context
The forecaster researches the web on its own. Add what it cannot find: your own data, a decision you have made, or a reading of the question you want it to follow. Context applies to the next forecast, not to forecasts already made.
Notes
POST /questions/{questionId}/context adds a note. treatment says how the forecaster uses it:
treatment |
The forecaster |
|---|---|
consider |
Weighs it as evidence. This is the default. |
look_into |
Researches it further. |
assume_true |
Takes it as given. |
ignore |
Is told not to rely on it, for a claim or source you know is wrong. |
const note = await prolepsia(`/questions/${question.id}/context`, {
body: {
text: "Our economics desk expects UK CPI for November to print at 2.4%.",
treatment: "consider",
},
});note = prolepsia(
"POST",
f"/questions/{question['id']}/context",
json={
"text": "Our economics desk expects UK CPI for November to print at 2.4%.",
"treatment": "consider",
},
)curl -sS -X POST "$PROLEPSIA_BASE_URL/questions/2c54dab4-f4e6-439d-a35d-1b7c71205259/context" \
-H "Authorization: Bearer $PROLEPSIA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"text": "Our economics desk expects UK CPI for November to print at 2.4%.", "treatment": "consider"}'You can also send notes with the forecast itself: POST /forecasts takes a context list of the same note objects.
Files
POST /questions/{questionId}/files uploads a document as multipart/form-data. A file sent through the API can be up to 10 MiB, and a question can hold 10 files and 500 MiB in all. Files added in the app count toward the 500 MiB and can be larger. Optional form fields:
artifactType: what the file is.user_context(the default, for something you wrote),background,evidence_source(a report or filing),dataset(a spreadsheet) orassumption.treatment:consider,look_intoorassume_true. A file cannot be ignored; remove it instead.userNote: a short note about the file, up to 2,000 characters.
import { readFile } from "node:fs/promises";
const form = new FormData();
form.append(
"file",
new Blob([await readFile("cpi-preview.pdf")], { type: "application/pdf" }),
"cpi-preview.pdf",
);
form.append("artifactType", "evidence_source");
form.append("userNote", "Internal preview of the November CPI release");
const file = await prolepsia(`/questions/${question.id}/files`, { body: form });
console.log(file.id, file.status); // "processing"with open("cpi-preview.pdf", "rb") as handle:
file = prolepsia(
"POST",
f"/questions/{question['id']}/files",
files={"file": ("cpi-preview.pdf", handle, "application/pdf")},
data={
"artifactType": "evidence_source",
"userNote": "Internal preview of the November CPI release",
},
)
print(file["id"], file["status"]) # "processing"curl -sS -X POST "$PROLEPSIA_BASE_URL/questions/2c54dab4-f4e6-439d-a35d-1b7c71205259/files" \
-H "Authorization: Bearer $PROLEPSIA_API_KEY" \
-F "file=@cpi-preview.pdf" \
-F "artifactType=evidence_source" \
-F "userNote=Internal preview of the November CPI release"Every file answer also carries dataTables and publicData. dataTables lists the tables Prolepsia found in a data file (CSV, TSV, XLSX or XLS): the sheet, the number of rows, the columns with their units, the date column, how often it repeats and the first and last date. It is empty for a file with no table, and you cannot change it. publicData says whether the question's creator marked the file as public data. A marked file shows its data to everyone who can see the question, and forecasts that used only marked files stay readable. Set it with PATCH /questions/{questionId}/files/{fileId} and {"publicData": true}. Only the question's creator may change it, a competitor file cannot carry it, and the change makes no new question version.
A new file has the status processing while Prolepsia reads its text. It reaches a forecast only once it is ready. Check with GET /questions/{questionId}/context/ready, which answers {"ready": true} when every file is ready. Starting a forecast while a file is still processing answers 409 with the error type context_not_ready, so you never get a forecast that quietly skipped your file. Send allowIncompleteContext: true with the forecast to run without it anyway. A file whose status is failed never becomes ready: remove it with DELETE /questions/{questionId}/files/{fileId}.
Start from a question template
A question template is the shape of a question you ask again and again, with blanks for the parts that change. GET /question-templates lists the templates you can use: Prolepsia's built-in templates, your organization's and your own. Add ?scope=builtin, organization or personal to list one kind. Each template lists its blanks, and each blank has a key, a label and a hint.
To make a question from a template, send its templateId and a value for each blank in templateValues. The template writes the title, the description, the resolution criteria and every other field you leave out. It also adds its notes, files, tags and forecast preset. A field you send wins over the template's. This example uses the built-in template "Company earnings above a target":
const question = await prolepsia("/questions", {
body: {
templateId: "12463a68-aa03-5f51-9fde-b80f94d5fa07",
templateValues: {
company: "Nvidia",
metric: "revenue",
target: "$60 billion",
period: "the fourth quarter of fiscal 2027",
},
},
});
console.log(question.title);
// "Will Nvidia report revenue above $60 billion for the fourth quarter of fiscal 2027?"question = prolepsia(
"POST",
"/questions",
json={
"templateId": "12463a68-aa03-5f51-9fde-b80f94d5fa07",
"templateValues": {
"company": "Nvidia",
"metric": "revenue",
"target": "$60 billion",
"period": "the fourth quarter of fiscal 2027",
},
},
)
print(question["title"])curl -sS -X POST "$PROLEPSIA_BASE_URL/questions" \
-H "Authorization: Bearer $PROLEPSIA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"templateId": "12463a68-aa03-5f51-9fde-b80f94d5fa07", "templateValues": {"company": "Nvidia", "metric": "revenue", "target": "$60 billion", "period": "the fourth quarter of fiscal 2027"}}'The new question names its template in sourceTemplate. Editing or deleting the template later never changes the question.
- Fill the blanks with AI.
POST /question-templates/{templateId}/fillwith a shortprompt, such as"Nvidia, next quarter, revenue above $60 billion", answerstemplateValuesfor every blank. Blanks you send intemplateValueskeep their value. Nothing is saved: check the values, then create the question as above. - Save a question as a template.
POST /question-templates/from-questionwithquestionId,scope(personalororganization) andname. - Make your own.
POST /question-templateswithscope,name,questionType, the questionfieldsand ablankslist. Write each blank as{key}in the fields, and declare each key once inblanks. A blank that is used but not declared, or declared but not used, is refused. Write{{or}}for a brace that is not a blank. An organization template needs an organization admin.
POST /forecasts takes templateId and templateValues in its inline question too. When people besides you can read the new question and the template holds notes or files, the answer carries warnings with the code template_content_readable_by_others.
Choose a forecast preset
A forecast preset is a saved set of choices for a forecast: the connections it should not use, standing notes and files. GET /forecast-presets lists your presets and your organization's.
Every call that starts a forecast takes two optional fields:
presetId: a preset id, or"none"for no preset. Leave it out to use the defaults: the question's own preset, then your default preset, then your organization's.connections:{"off": [...], "on": [...]}with connection ids. It switches single connections off, or back on, for this forecast only.
const task = await prolepsia(`/questions/${question.id}/forecasts`, {
body: {
presetId: "e2b86642-72c4-4490-981e-aa9474f24e38",
connections: { off: ["dce1bdbc-1a94-4570-a6b5-66e7a26839fc"] },
},
});
console.log(task.id, task.preset, task.connections);task = prolepsia(
"POST",
f"/questions/{question['id']}/forecasts",
json={
"presetId": "e2b86642-72c4-4490-981e-aa9474f24e38",
"connections": {"off": ["dce1bdbc-1a94-4570-a6b5-66e7a26839fc"]},
},
)
print(task["id"], task["preset"], task["connections"])curl -sS -X POST "$PROLEPSIA_BASE_URL/questions/2c54dab4-f4e6-439d-a35d-1b7c71205259/forecasts" \
-H "Authorization: Bearer $PROLEPSIA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"presetId": "e2b86642-72c4-4490-981e-aa9474f24e38", "connections": {"off": ["dce1bdbc-1a94-4570-a6b5-66e7a26839fc"]}}'A forecast read carries preset. It is null when the forecast used no preset. Otherwise it holds used: true, and the preset's id and name when you may see them.
- Add a preset.
POST /forecast-presetswithscope(personalororganization),name,disabledConnectionIdsandnotes, each note withtextandtreatment. An organization preset needs an organization admin. A connection you add later is on in every preset until a preset switches it off. - Add a file.
POST /forecast-presets/{presetId}/filestakes amultipart/form-dataupload, or JSON withfilenameandcontentBase64. While a preset file is processing, starting a forecast with that preset answers409 context_not_ready, as a question's own file does. - Set a default.
PUT /forecast-presets/defaultswithuserPresetIdsets your own default. An organization admin can setorganizationPresetIdtoo. - Keep a pick.
PUT /questions/{questionId}/scheduleandPUT /questions/{questionId}/watchtake the samepresetId, and every forecast they start uses it.
When people besides you can read the forecast and its preset holds notes or files, the answer carries warnings with the code preset_content_may_show_in_forecast.
Connect your own servers
A connection is a remote MCP server that your forecasts can call, such as a company wiki or a data service. Every forecast you start can use your connections and your organization's. Managing them needs an API key with the connections:read and connections:write permissions.
POST /connections adds a server and tests it straight away:
const connection = await prolepsia("/connections", {
body: {
scope: "personal",
name: "Research notes",
url: "https://notes.example.com/mcp",
authKind: "api_key",
api_key: "nk_live_...",
},
});
console.log(
connection.status,
connection.tools.map((tool) => tool.name),
);connection = prolepsia(
"POST",
"/connections",
json={
"scope": "personal",
"name": "Research notes",
"url": "https://notes.example.com/mcp",
"authKind": "api_key",
"api_key": "nk_live_...",
},
)
print(connection["status"], [tool["name"] for tool in connection["tools"]])curl -sS -X POST "$PROLEPSIA_BASE_URL/connections" \
-H "Authorization: Bearer $PROLEPSIA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"scope": "personal", "name": "Research notes", "url": "https://notes.example.com/mcp", "authKind": "api_key", "api_key": "nk_live_..."}'scopeispersonal, for forecasts you start, ororganization, for every member's forecasts. An organization connection needs an organization admin.nameis the name forecasts see. Theurlmust start withhttps://and reach a public address.authKindisnone,api_keyoroauth. Prolepsia sends a key asAuthorization: Bearer <key>, or as it is in the header you name inapiKeyHeader. It never returns the key: reads showhasApiKeyinstead.- For
oauth, callPOST /connections/{connectionId}/sign-inand open theauthorizationUrlit returns in a browser. Only a person in a browser can finish a sign-in. - The answer lists the server's
tools, each withallowed. Tools the server marks as changing data start withallowed: false.PATCH /connections/{connectionId}with{"tools": {"add_note": true}}switches one on, and{"enabled": false}switches the whole connection off. POST /connections/{connectionId}/testtests the server again.PUT /connections/{connectionId}/member-switchwith{"enabled": false}leaves an organization connection out of your own forecasts.
A forecast read lists the connections the forecast could call in connections, each with its name, its owner, the number of calls and a status: used, not_used, failed or unavailable. A connection that fails never stops a forecast. When people besides you can read the question, the answer that starts a forecast with connections carries warnings with the code connection_results_readable_by_others.
Keep forecasts current
- Schedule.
PUT /questions/{questionId}/schedulereruns a question on a fixed cadence, such as every 7 days until an end date. The cadence can be no faster than every 3 hours, and one schedule can make at most 50 forecasts. Each of them is a paid forecast. - Automatic reruns.
PUT /questions/{questionId}/watchwith{"enabled": true}starts a new forecast when the news changes the picture by more thanchangeThreshold, in the question's own units. Automatic reruns cost a monthly fee (monitoring.question_monthin the prices) plus each forecast they start.
const schedule = await prolepsia(`/questions/${question.id}/schedule`, {
method: "PUT",
body: {
intervalCount: 7,
intervalUnit: "days",
endAt: "2026-12-16T00:00:00Z",
},
});
console.log(schedule.nextDueAt, schedule.occurrenceCount);schedule = prolepsia(
"PUT",
f"/questions/{question['id']}/schedule",
json={"intervalCount": 7, "intervalUnit": "days", "endAt": "2026-12-16T00:00:00Z"},
)
print(schedule["nextDueAt"], schedule["occurrenceCount"])curl -sS -X PUT "$PROLEPSIA_BASE_URL/questions/2c54dab4-f4e6-439d-a35d-1b7c71205259/schedule" \
-H "Authorization: Bearer $PROLEPSIA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"intervalCount": 7, "intervalUnit": "days", "endAt": "2026-12-16T00:00:00Z"}'intervalUnit is hours, days or months. startAt defaults to now. Sending the schedule again replaces it. DELETE on the same path stops it and leaves a forecast already running alone. GET /schedules lists schedules and GET /watches lists automatic reruns across your questions.
Ask about a forecast
POST /forecasts/{taskId}/chat with {"message": "..."} asks a question about a completed forecast, such as why the number is what it is or what would change it. The answer comes from the reasoning stored with that forecast. Send back the returned id as threadId to continue the same conversation. The last assistant message in messages is the answer. Each message costs a model call and takes several seconds.
Conventions
Paths
Paths have no trailing slash: /questions, not /questions/. A path that ends in / answers 404.
Times
Times are ISO 8601 in UTC with milliseconds, such as 2026-10-12T07:45:41.413Z. Date bounds on questions are Unix timestamps in seconds.
Pages
Most lists answer one page at a time:
{
"type": "list",
"data": [],
"hasMore": true,
"nextCursor": "MjAyNi0xMC0xMlQwNzo0..."
}
Pass limit (1 to 100, default 20) and, for the next page, cursor set to the nextCursor you got. GET /questions/browse and GET /decisions page with limit (up to 200, default 50) and offset instead.
async function* allQuestions() {
let cursor: string | null = null;
do {
const query = new URLSearchParams({ limit: "100" });
if (cursor) query.set("cursor", cursor);
const page = await prolepsia(`/questions?${query}`);
yield* page.data;
cursor = page.hasMore ? page.nextCursor : null;
} while (cursor);
}
for await (const item of allQuestions()) console.log(item.id, item.title);def all_questions():
params = {"limit": 100}
while True:
page = prolepsia("GET", "/questions", params=params)
yield from page["data"]
if not page["hasMore"]:
return
params["cursor"] = page["nextCursor"]
for item in all_questions():
print(item["id"], item["title"])# The first page.
curl -sS -G "$PROLEPSIA_BASE_URL/questions" \
-H "Authorization: Bearer $PROLEPSIA_API_KEY" \
--data-urlencode "limit=100"
# The next page: set NEXT_CURSOR to the nextCursor of the answer above.
curl -sS -G "$PROLEPSIA_BASE_URL/questions" \
-H "Authorization: Bearer $PROLEPSIA_API_KEY" \
--data-urlencode "limit=100" \
--data-urlencode "cursor=$NEXT_CURSOR"Safe retries
Four calls accept an Idempotency-Key header: POST /forecasts, POST /forecasts/batch, POST /decisions and POST /decisions/{decisionId}/forecasts. The key is any text up to 128 characters, kept for 24 hours for each API key.
- The same key with the same body returns the first answer again, with the header
Idempotent-Replayed: true. Nothing new starts. - The same key with a different body answers
400. - A second request while the first is still being handled answers
409with the error typeidempotency_in_progress. Wait a second and try again. - When the first request failed, the key is free again, so a retry runs normally.
Other calls ignore the header. POST /questions/{questionId}/forecasts is safe to retry for another reason: while the first forecast runs, a second request answers 409 forecast_already_running with the running task's id.
Rate limits
Each API key may make 60 requests a minute unless Prolepsia set a different limit for it. Some calls also count against a shared limit:
| Calls | Limit |
|---|---|
Drafting questions with AI (normalize, normalize/revise, normalize/clarify) |
20 a minute for each key |
| Forecast chat messages | 30 a minute for each key |
| Decision calls that use a model: creating a decision, drafting or keeping questions, conditional links, refreshing the summary | 10 a minute for each key |
| Testing and replaying webhook deliveries | 20 a minute for each user |
Going over a limit answers 429 with a Retry-After header in seconds. Separately, one user can have at most 50 forecasts started through the API running at once; past that, starting another answers 429 with the error type too_many_active_forecasts.
Errors
Every error has the same shape:
{
"type": "error",
"error": {
"type": "invalid_request_error",
"message": "question: lowerBound: Numeric, discrete, and date questions require bounds."
}
}
| Status | error.type |
What to do |
|---|---|---|
| 400 | invalid_request_error |
Fix the request. The message names the field. |
| 401 | invalid_api_key |
The key is missing, wrong, revoked or expired. |
| 403 | permission_denied |
The key lacks a scope, or its owner cannot do this. |
| 403 | uploads_disabled |
Your organization has turned file uploads off. |
| 404 | not_found |
The id is wrong or not visible to this key. Also check for a trailing slash. |
| 405 | invalid_request_error |
This path does not take that method. The Allow header lists the ones it takes. |
| 409 | forecast_already_running |
Wait for the task in error.activeTaskId, or cancel it. |
| 409 | context_not_ready |
A file is still processing. error.files lists them. Wait and try again. |
| 409 | idempotency_in_progress |
The first request with this key is still running. Try again in a second. |
| 409 | invalid_request_error |
Not enough credits, or another conflict. Read the message. |
| 409 | Decision conflicts, such as generation_in_progress or decision_archived |
Wait, or change the request. |
| 429 | rate_limit_error, too_many_active_forecasts |
Wait for the seconds in Retry-After. |
| 503 | overloaded_error |
Try again shortly. |
Make a decision
A decision is a choice between 2 to 4 options, with a map of questions that bear on it. The /decisions calls create a decision, draft questions for it with AI, keep or discard the drafts, link questions, price and start their forecasts, and write a summary that weighs each option. Price a batch of forecasts with POST /decisions/{decisionId}/forecasts/estimate before you start it, and pass the approved total as maxTotalCredits and the estimate's price per forecast as expectedPricePerForecast. Live mode has its own estimate (POST /decisions/{decisionId}/live/estimate). Every decision call is listed in the endpoint reference.
Endpoint reference
Every path below starts with https://app.prolepsia.com/api/v1/external. The interactive API reference shows each call's fields and answers, and lets you try it with your key.
Forecasts
| Method | Path | Scope | What it does |
|---|---|---|---|
| GET | /run-modes |
forecasts:read |
List run modes |
| GET | /forecasts |
forecasts:read |
List forecasts |
| POST | /forecasts |
forecasts:write |
Create a forecast |
| GET | /forecasts/{task_id} |
forecasts:read |
Get a forecast task |
| POST | /forecasts/{task_id}/cancel |
forecasts:write |
Cancel a forecast task |
| POST | /forecasts/batch |
forecasts:write |
Create several forecasts |
| POST | /forecasts/cancel |
forecasts:write |
Cancel several forecasts |
| GET | /questions/{question_id}/forecasts |
forecasts:read |
List a question's forecasts |
| POST | /questions/{question_id}/forecasts |
forecasts:write |
Run a forecast on a question |
Questions
| Method | Path | Scope | What it does |
|---|---|---|---|
| GET | /questions |
questions:read |
List questions |
| POST | /questions |
questions:write |
Create a question |
| GET | /questions/{question_id} |
questions:read |
Get a question |
| PATCH | /questions/{question_id} |
questions:write |
Edit a question |
| GET | /questions/browse |
questions:read |
Search and filter questions |
| POST | /questions/import |
questions:write |
Import a question from a market |
| POST | /questions/normalize |
questions:write |
Normalize a question |
| POST | /questions/normalize/clarify |
questions:write |
Answer a requested clarification |
| POST | /questions/normalize/revise |
questions:write |
Revise a normalized question with guidance |
| GET | /tags |
questions:read |
List tags |
Question context
| Method | Path | Scope | What it does |
|---|---|---|---|
| GET | /questions/{question_id}/context |
questions:read |
List a question's context notes |
| POST | /questions/{question_id}/context |
questions:write |
Add a context note to a question |
| PATCH | /questions/{question_id}/context/{note_id} |
questions:write |
Edit a context note |
| DELETE | /questions/{question_id}/context/{note_id} |
questions:write |
Remove a context note |
| GET | /questions/{question_id}/context/ready |
questions:read |
Check whether a question's files are ready |
| GET | /questions/{question_id}/files |
questions:read |
List a question's context files |
| POST | /questions/{question_id}/files |
questions:write |
Upload a context file |
| GET | /questions/{question_id}/files/{file_id} |
questions:read |
Get a context file's status |
| PATCH | /questions/{question_id}/files/{file_id} |
questions:write |
Edit a context file's settings |
| DELETE | /questions/{question_id}/files/{file_id} |
questions:write |
Remove a context file |
| POST | /questions/{question_id}/files/{file_id}/replace |
questions:write |
Replace a context file's bytes |
Forecast chat
| Method | Path | Scope | What it does |
|---|---|---|---|
| GET | /forecasts/{task_id}/chat |
forecasts:read |
List conversations about a forecast |
| POST | /forecasts/{task_id}/chat |
forecasts:write |
Ask a question about a forecast |
| GET | /forecasts/{task_id}/chat/{thread_id} |
forecasts:read |
Read one conversation |
| POST | /forecasts/chat/messages/{message_id}/accept-context |
questions:write |
Save a chat suggestion as context |
Schedules and automatic reruns
| Method | Path | Scope | What it does |
|---|---|---|---|
| GET | /questions/{question_id}/schedule |
forecasts:read |
Get a question's forecast schedule |
| PUT | /questions/{question_id}/schedule |
forecasts:write |
Create or replace a forecast schedule |
| DELETE | /questions/{question_id}/schedule |
forecasts:write |
Delete a forecast schedule |
| GET | /questions/{question_id}/watch |
forecasts:read |
Get a question's auto-reforecast watch |
| PUT | /questions/{question_id}/watch |
forecasts:write |
Set a question's auto-reforecast watch |
| DELETE | /questions/{question_id}/watch |
forecasts:write |
Stop a question's auto-reforecast watch |
| GET | /schedules |
forecasts:read |
List forecast schedules |
| GET | /watches |
forecasts:read |
List auto-reforecast watches |
Sharing and notifications
| Method | Path | Scope | What it does |
|---|---|---|---|
| GET | /questions/{question_id}/share-links |
questions:write |
List a question's share links |
| POST | /questions/{question_id}/share-links |
questions:write |
Create a share link |
| GET | /questions/{question_id}/share-links/{link_id} |
questions:write |
Get a share link |
| PATCH | /questions/{question_id}/share-links/{link_id} |
questions:write |
Edit a share link |
| DELETE | /questions/{question_id}/share-links/{link_id} |
questions:write |
Delete a share link |
| POST | /questions/{question_id}/share-links/{link_id}/revoke |
questions:write |
Revoke a share link |
| GET | /questions/{question_id}/subscription |
questions:read |
Get your notification subscription |
| PUT | /questions/{question_id}/subscription |
questions:write |
Follow a public question |
| DELETE | /questions/{question_id}/subscription |
questions:write |
Unfollow a public question |
Webhooks
| Method | Path | Scope | What it does |
|---|---|---|---|
| GET | /webhook-deliveries |
forecasts:read |
List webhook deliveries |
| GET | /webhook-deliveries/{delivery_id} |
forecasts:read |
Get a webhook delivery |
| GET | /webhook-deliveries/{delivery_id}/attempts |
forecasts:read |
List a delivery's attempts |
| POST | /webhook-deliveries/{delivery_id}/replay |
forecasts:write |
Replay a webhook delivery |
| GET | /webhooks |
forecasts:read |
List webhooks |
| POST | /webhooks |
forecasts:write |
Register a webhook |
| GET | /webhooks/{webhook_id} |
forecasts:read |
Get a webhook |
| PATCH | /webhooks/{webhook_id} |
forecasts:write |
Edit a webhook |
| DELETE | /webhooks/{webhook_id} |
forecasts:write |
Delete a webhook |
| POST | /webhooks/{webhook_id}/rotate |
forecasts:write |
Rotate a webhook's signing secret |
| POST | /webhooks/{webhook_id}/test |
forecasts:write |
Send a test webhook |
Connections
| Method | Path | Scope | What it does |
|---|---|---|---|
| GET | /connections |
connections:read |
List connections |
| POST | /connections |
connections:write |
Add a connection |
| GET | /connections/{connection_id} |
connections:read |
Get a connection |
| PATCH | /connections/{connection_id} |
connections:write |
Edit a connection |
| DELETE | /connections/{connection_id} |
connections:write |
Delete a connection |
| POST | /connections/{connection_id}/test |
connections:write |
Test a connection |
| PUT | /connections/{connection_id}/member-switch |
connections:write |
Switch an organization connection for yourself |
| POST | /connections/{connection_id}/sign-in |
connections:write |
Start a connection sign-in |
Forecast presets
| Method | Path | Scope | What it does |
|---|---|---|---|
| GET | /forecast-presets |
forecasts:read |
List forecast presets |
| POST | /forecast-presets |
forecasts:write |
Add a forecast preset |
| GET | /forecast-presets/defaults |
forecasts:read |
Get the default forecast presets |
| PUT | /forecast-presets/defaults |
forecasts:write |
Set the default forecast presets |
| GET | /forecast-presets/{preset_id} |
forecasts:read |
Get a forecast preset |
| PATCH | /forecast-presets/{preset_id} |
forecasts:write |
Edit a forecast preset |
| DELETE | /forecast-presets/{preset_id} |
forecasts:write |
Delete a forecast preset |
| POST | /forecast-presets/{preset_id}/files |
forecasts:write |
Add a file to a forecast preset |
| GET | /forecast-presets/{preset_id}/files/{file_id} |
forecasts:read |
Get a forecast preset file |
| PATCH | /forecast-presets/{preset_id}/files/{file_id} |
forecasts:write |
Edit a forecast preset file |
| DELETE | /forecast-presets/{preset_id}/files/{file_id} |
forecasts:write |
Delete a forecast preset file |
Question templates
| Method | Path | Scope | What it does |
|---|---|---|---|
| GET | /question-templates |
questions:read |
List question templates |
| POST | /question-templates |
questions:write |
Add a question template |
| POST | /question-templates/from-question |
questions:write |
Save a question as a template |
| GET | /question-templates/{template_id} |
questions:read |
Get a question template |
| PATCH | /question-templates/{template_id} |
questions:write |
Edit a question template |
| DELETE | /question-templates/{template_id} |
questions:write |
Delete a question template |
| POST | /question-templates/{template_id}/apply |
questions:read |
Show what a question template gives |
| POST | /question-templates/{template_id}/fill |
questions:write |
Fill a template's blanks with the AI |
| POST | /question-templates/{template_id}/files |
questions:write |
Add a file to a question template |
| GET | /question-templates/{template_id}/files/{file_id} |
questions:read |
Get a question template file |
| PATCH | /question-templates/{template_id}/files/{file_id} |
questions:write |
Edit a question template file |
| DELETE | /question-templates/{template_id}/files/{file_id} |
questions:write |
Delete a question template file |
Decisions
| Method | Path | Scope | What it does |
|---|---|---|---|
| GET | /decisions |
questions:read |
List decisions |
| POST | /decisions |
questions:write |
Create a decision |
| GET | /decisions/{decision_id} |
questions:read |
Read a decision |
| PATCH | /decisions/{decision_id} |
questions:write |
Edit a decision |
| POST | /decisions/{decision_id}/archive |
questions:write |
Archive a decision |
| POST | /decisions/{decision_id}/unarchive |
questions:write |
Unarchive a decision |
| POST | /decisions/{decision_id}/generate |
questions:write |
Draft more questions |
| PATCH | /decisions/{decision_id}/drafts/{draft_id} |
questions:write |
Edit a question draft |
| POST | /decisions/{decision_id}/drafts/discard |
questions:write |
Discard question drafts |
| POST | /decisions/{decision_id}/drafts/keep |
questions:write |
Keep question drafts |
| POST | /decisions/{decision_id}/forecasts |
forecasts:write |
Start forecasts for a decision |
| POST | /decisions/{decision_id}/forecasts/estimate |
forecasts:read |
Price forecasts for a decision |
| POST | /decisions/{decision_id}/nodes |
questions:write |
Put a question on a decision |
| DELETE | /decisions/{decision_id}/nodes/{node_id} |
questions:write |
Take a question off a decision |
| POST | /decisions/{decision_id}/links |
questions:write |
Link two questions on a decision |
| PATCH | /decisions/{decision_id}/links/{link_id} |
questions:write |
Edit a failed conditional link |
| DELETE | /decisions/{decision_id}/links/{link_id} |
questions:write |
Remove a link |
| POST | /decisions/{decision_id}/notes |
questions:write |
Add a note to a decision |
| PATCH | /decisions/{decision_id}/notes/{note_id} |
questions:write |
Edit a decision note |
| DELETE | /decisions/{decision_id}/notes/{note_id} |
questions:write |
Remove a decision note |
| GET | /decisions/{decision_id}/comments |
questions:read |
List the comments on a decision |
| POST | /decisions/{decision_id}/comments |
questions:write |
Comment on a decision |
| PATCH | /decisions/{decision_id}/comments/{comment_id} |
questions:write |
Edit a comment |
| DELETE | /decisions/{decision_id}/comments/{comment_id} |
questions:write |
Delete a comment |
| GET | /decisions/{decision_id}/conditions |
questions:read |
List the conditions on a decision |
| POST | /decisions/{decision_id}/conditions |
questions:write |
Add a condition to a decision |
| PATCH | /decisions/{decision_id}/conditions/{condition_id} |
questions:write |
Change a condition or arm it again |
| DELETE | /decisions/{decision_id}/conditions/{condition_id} |
questions:write |
Delete a condition |
| GET | /decisions/{decision_id}/criteria |
questions:read |
List the criteria of a decision |
| POST | /decisions/{decision_id}/criteria |
questions:write |
Add a criterion to a decision |
| PUT | /decisions/{decision_id}/criteria |
questions:write |
Put the criteria in order |
| PATCH | /decisions/{decision_id}/criteria/{criterion_id} |
questions:write |
Change or confirm a criterion |
| DELETE | /decisions/{decision_id}/criteria/{criterion_id} |
questions:write |
Remove a criterion |
| GET | /decisions/{decision_id}/items |
questions:read |
List the evidence items of a decision |
| POST | /decisions/{decision_id}/items |
questions:write |
Add an evidence item to a decision |
| PATCH | /decisions/{decision_id}/items/{item_id} |
questions:write |
Change an evidence item, its kind or its status |
| DELETE | /decisions/{decision_id}/items/{item_id} |
questions:write |
Remove an evidence item |
| POST | /decisions/{decision_id}/drafts/{draft_id}/convert |
questions:write |
Turn a question draft into an evidence item |
| POST | /decisions/{decision_id}/items/{item_id}/convert |
questions:write |
Turn an evidence item into a question draft |
| POST | /decisions/{decision_id}/question-checks |
questions:write |
Check which questions on the map are not forecasts |
| PATCH | /decisions/{decision_id}/question-flags/{flag_id} |
questions:write |
Dismiss a question flag or convert the question's role |
| GET | /decisions/{decision_id}/assessments |
questions:read |
List how the options stand against the criteria |
| PUT | /decisions/{decision_id}/assessments/{option_id}/{criterion_id} |
questions:write |
Set how one option stands against one criterion |
| PATCH | /decisions/{decision_id}/assessments/{option_id}/{criterion_id} |
questions:write |
Confirm a state a model proposed |
| DELETE | /decisions/{decision_id}/assessments/{option_id}/{criterion_id} |
questions:write |
Clear a state so it reads as unknown |
| GET | /decisions/{decision_id}/review-updates |
questions:read |
Read the changes in the evidence that call for a review |
| PUT | /decisions/{decision_id}/criteria/{criterion_id}/review-trigger |
questions:write |
Confirm when a hard constraint calls for a review |
| DELETE | /decisions/{decision_id}/criteria/{criterion_id}/review-trigger |
questions:write |
Remove the review rule of a hard constraint |
| GET | /decisions/{decision_id}/ownership |
questions:read |
Read who owns a decision and who it can go to |
| PUT | /decisions/{decision_id}/ownership |
questions:write |
Hand a decision to another member |
| POST | /decisions/{decision_id}/ownership/acknowledge |
questions:write |
Dismiss the notice of a hand-over |
| GET | /decisions/{decision_id}/payoff-model |
questions:read |
Read the payoffs of a decision |
| PUT | /decisions/{decision_id}/payoff-model |
questions:write |
Save the payoffs of a decision |
| DELETE | /decisions/{decision_id}/payoff-model |
questions:write |
Remove the payoffs of a decision |
| GET | /decisions/{decision_id}/comparison |
questions:read |
Compare the options against the criteria |
| GET | /decisions/attention |
questions:read |
List the decisions that need attention |
| POST | /decisions/{decision_id}/attention |
questions:write |
Put reasons away on the attention list |
| GET | /decisions/intake-sessions |
questions:read |
List unfinished conversations |
| POST | /decisions/intake-sessions |
questions:write |
Start an intake conversation |
| GET | /decisions/intake-sessions/{session_id} |
questions:read |
Read an intake conversation |
| PATCH | /decisions/intake-sessions/{session_id} |
questions:write |
Save changes to an intake conversation |
| DELETE | /decisions/intake-sessions/{session_id} |
questions:write |
Discard an intake conversation |
| POST | /decisions/intake-sessions/{session_id}/reply |
questions:write |
Get the model's answer in an intake conversation |
| POST | /decisions/intake-sessions/{session_id}/prepare |
questions:write |
Prepare the decisions of an intake conversation |
| POST | /decisions/intake-sessions/{session_id}/create |
questions:write |
Create the decisions of an intake conversation |
| GET | /decisions/{decision_id}/research-plan |
questions:read |
Read the research plan |
| POST | /decisions/{decision_id}/research-plan/defer |
questions:write |
Put plan entries aside or bring them back |
| POST | /decisions/{decision_id}/research-plan/remove |
questions:write |
Remove plan entries |
| POST | /decisions/{decision_id}/research-plan/approve |
questions:write |
Approve the research plan |
| GET | /decisions/{decision_id}/evidence |
questions:read |
Read the evidence behind a claim |
| GET | /decisions/{decision_id}/briefs/{summary_id} |
questions:read |
Read a dated brief |
| GET | /decisions/{decision_id}/choices |
questions:read |
Read the recorded choices and reviews of a decision |
| POST | /decisions/{decision_id}/choices |
questions:write |
Record the author's choice |
| POST | /decisions/{decision_id}/choices/reopen |
questions:write |
Take the current choice back |
| POST | /decisions/{decision_id}/next-actions |
questions:write |
Add a next action to the current choice |
| POST | /decisions/{decision_id}/next-actions/{item_id}/complete |
questions:write |
Mark a next action done |
| POST | /decisions/{decision_id}/reviews |
questions:write |
Schedule a review of the current choice |
| POST | /decisions/{decision_id}/reviews/{review_id}/complete |
questions:write |
Complete a review |
| PATCH | /decisions/{decision_id}/reviews/{review_id} |
questions:write |
Set what happened after a review |
| DELETE | /decisions/{decision_id}/reviews/{review_id} |
questions:write |
Cancel an open review |
| PUT | /decisions/{decision_id}/reminders |
questions:write |
Switch the reminder emails of a decision |
| GET | /decisions/{decision_id}/summary |
questions:read |
Read a decision summary |
| POST | /decisions/{decision_id}/summary/refresh |
questions:write |
Write a new decision summary |
| POST | /decisions/{decision_id}/live/estimate |
forecasts:read |
Price live mode for a decision |
| PUT | /decisions/{decision_id}/live |
forecasts:write |
Turn live mode on or off |
| PUT | /decisions/{decision_id}/live/continuation |
forecasts:write |
Keep live mode on or turn it off |
| PUT | /decisions/{decision_id}/nodes/{node_id}/monitoring |
forecasts:write |
Include a question in live mode or leave it out |
| PUT | /decisions/{decision_id}/follow |
questions:write |
Follow or unfollow a decision |
Account
| Method | Path | Scope | What it does |
|---|---|---|---|
| GET | /credits |
forecasts:read |
Get organization credits |