Developer docs

API

Create questions, start forecasts and read results from your own code, with examples in TypeScript, Python and curl.

Read as Markdown · llms.txt

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

  1. In Prolepsia, open Settings and choose API and AI clients.
  2. Under API keys, choose Create new key.
  3. Name the key, tick the permissions it needs, pick when it expires and choose Create key.
  4. 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
}
  • id is the task id. forecast.id is a different id: the forecast's own.
  • forecast.forecastData is the number. Its shape depends on the question type; see Forecast results.
  • forecast.report is the written report at three lengths. It is written after the number, so it can still be pending or writing when the task is already completed.
  • forecast.sources lists what the forecast relied on. Each source has a kind and a display_name, and usually a url.
  • pollingUrl is 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/normalize with {"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 returned question to POST /questions or POST /forecasts. When the answer is status: "unsupported", read suggestedReframe.
  • Import it from a market. POST /questions/import with {"reference": "<a Metaculus, Kalshi or Polymarket link>"} copies the question. No forecast starts.
  • Start from a template. Send templateId and templateValues and 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, only description, resolutionCriteria, finePrint, visibility and tags can 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=numeric leaves out the report and the sources. Use it when you only need the number.
  • ?includeSubforecasts=true adds each finished subforecast's forecastData, writeUp and sources. Use it once, when you want to read them: the answer can grow past a megabyte.

List forecasts

  • GET /forecasts lists forecasts across your questions, newest first. Filter with status, questionId or taskId (repeat the parameter, or separate ids with commas), and autoforecast (watch, schedule or any) for questions that rerun on their own. scope=all_visible adds forecasts others shared with you. Each item's report holds only status and short.
  • GET /questions/{questionId}/forecasts lists the forecasts of one question, in every state unless you pass status.
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) or assumption.
  • treatment: consider, look_into or assume_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}/fill with a short prompt, such as "Nvidia, next quarter, revenue above $60 billion", answers templateValues for every blank. Blanks you send in templateValues keep their value. Nothing is saved: check the values, then create the question as above.
  • Save a question as a template. POST /question-templates/from-question with questionId, scope (personal or organization) and name.
  • Make your own. POST /question-templates with scope, name, questionType, the question fields and a blanks list. Write each blank as {key} in the fields, and declare each key once in blanks. 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-presets with scope (personal or organization), name, disabledConnectionIds and notes, each note with text and treatment. 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}/files takes a multipart/form-data upload, or JSON with filename and contentBase64. While a preset file is processing, starting a forecast with that preset answers 409 context_not_ready, as a question's own file does.
  • Set a default. PUT /forecast-presets/defaults with userPresetId sets your own default. An organization admin can set organizationPresetId too.
  • Keep a pick. PUT /questions/{questionId}/schedule and PUT /questions/{questionId}/watch take the same presetId, 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_..."}'
  • scope is personal, for forecasts you start, or organization, for every member's forecasts. An organization connection needs an organization admin.
  • name is the name forecasts see. The url must start with https:// and reach a public address.
  • authKind is none, api_key or oauth. Prolepsia sends a key as Authorization: Bearer <key>, or as it is in the header you name in apiKeyHeader. It never returns the key: reads show hasApiKey instead.
  • For oauth, call POST /connections/{connectionId}/sign-in and open the authorizationUrl it returns in a browser. Only a person in a browser can finish a sign-in.
  • The answer lists the server's tools, each with allowed. Tools the server marks as changing data start with allowed: false. PATCH /connections/{connectionId} with {"tools": {"add_note": true}} switches one on, and {"enabled": false} switches the whole connection off.
  • POST /connections/{connectionId}/test tests the server again. PUT /connections/{connectionId}/member-switch with {"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}/schedule reruns 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}/watch with {"enabled": true} starts a new forecast when the news changes the picture by more than changeThreshold, in the question's own units. Automatic reruns cost a monthly fee (monitoring.question_month in 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 409 with the error type idempotency_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