ManticDocs
Get started

Read your first forecast

Find a forecast, read its latest run and retrieve the reasoning behind it with a short Python script.

In the API, a forecast is a question and each run is a forecast object; see Forecasts in the API.

The snippets come from quickstart.py.

1. Set up Python and credentials

Use Python 3.11 or newer. Ask your Mantic contact or support@mantic.com for a client ID and client secret.

In Bash on macOS or Linux, create an environment and install httpx:

python3 -m venv .venv
source .venv/bin/activate
python -m pip install httpx==0.28.1

Set your client ID and client secret in your terminal, replacing the placeholders:

export MANTIC_CLIENT_ID="YOUR_CLIENT_ID"
export MANTIC_CLIENT_SECRET="YOUR_CLIENT_SECRET"

2. Exchange credentials for an access token

Request an access token from the authentication endpoint:

def get_access_token(http: httpx.Client) -> str:
    response = http.post(
        "https://app.mantic.com/auth/oauth2/token",
        auth=(os.environ["MANTIC_CLIENT_ID"], os.environ["MANTIC_CLIENT_SECRET"]),
        data={"grant_type": "client_credentials", "resource": "https://api.mantic.com"},
    )
    response.raise_for_status()
    return response.json()["access_token"]

resource must be exactly https://api.mantic.com, with no trailing slash. The token expires after expires_in seconds; Get and refresh an access token shows how to renew it.

3. Find a forecast

Search by topic and print each returned forecast's id (its question_id) and title:

def search_questions(http: httpx.Client, search: str) -> list[dict[str, Any]]:
    response = http.get("/questions", params={"search": search})
    response.raise_for_status()
    questions = response.json()["questions"]
    for question in questions:
        print(question["id"], question["title"])
    return questions

Search ranks forecasts by meaning and returns up to 25 results in a single page. The output looks like this:

123 Will UK CPI inflation be below 2.5% in December 2026?

4. Read the selected forecast

Retrieve the forecast you chose with GET /questions/{question_id}. The response includes its latest run as latest_forecast:

def read_question(http: httpx.Client, question_id: int) -> dict[str, Any] | None:
    response = http.get(f"/questions/{question_id}", params={"show_forecasts": "true"})
    if response.status_code == 404:
        return None  # Not found, or no completed run yet
    response.raise_for_status()
    return response.json()

A 404 means the forecast doesn't exist or has no completed run yet.

5. Interpret the latest run

Read the run's values according to its forecast type:

def show_forecast(forecast: dict[str, Any]) -> None:
    match forecast["type"]:
        case "event":
            print(f"Probability: {forecast['probability']:.0%} by {forecast['by']}")
        case "numerical" | "date":
            median = forecast["percentiles"].get("50")
            print(f"Median: {median if median is not None else 'unavailable'}")
        case "categorical":
            for category in forecast["categories"]:
                print(f"{category['label']}: {category['probability']:.0%}")
        case _:
            print("Forecast values unavailable.")

    print(f"Run ID: {forecast['id']} · Updated: {forecast['date']}")
  • For an event, probability is a fraction: 0.64 means 64%. by is the event deadline. date is when the run completed, not the deadline.
  • For numerical and date forecasts, percentiles["50"] is the median; it can be missing.
  • Categorical forecasts give a probability for each labelled outcome.
  • An unknown run has no values, so the script skips its reasoning. Without show_forecasts=true, runs can come back redacted, with no values, so the script sends it.

For an event forecast, the output looks like this:

Probability: 64% by 2026-12-31T23:59:59Z
Run ID: 987 · Updated: 2026-09-21T08:30:00Z

See Read a forecast's latest run for each forecast type and its fields.

6. Retrieve the reasoning for the same run

Pass the run's id to GET /forecasts/{forecast_id} to retrieve the detailed run, including its reasoning:

def show_reasoning(http: httpx.Client, forecast_id: int) -> None:
    response = http.get(f"/forecasts/{forecast_id}")
    response.raise_for_status()
    print(response.json().get("reasoning") or "No reasoning is available for this run.")

reasoning is Markdown: the same reasoning you see on the forecast in the Mantic web app.

7. Put it together

run() requests the access token, searches, asks for a forecast's question_id and prints its latest run and reasoning:

def run(http: httpx.Client, search: str) -> None:
    http.headers["Authorization"] = f"Bearer {get_access_token(http)}"
    questions = search_questions(http, search)
    if not questions:
        print("No matching forecasts. Try a different search.")
        return

    choice = input("Enter a question_id from these results: ").strip()
    if choice not in {str(question["id"]) for question in questions}:
        print("Choose an ID from the search results above.")
        return

    question = read_question(http, int(choice))
    if question is None or question["latest_forecast"] is None:
        print("This forecast has no completed run yet. Choose another forecast.")
        return

    print(question["title"])
    forecast = question["latest_forecast"]
    show_forecast(forecast)
    if forecast["type"] not in {"redacted", "unknown"}:
        show_reasoning(http, forecast["id"])

The script runs it with an httpx client pointed at the Mantic API:

with httpx.Client(base_url="https://api.mantic.com", timeout=30.0) as http:
    run(http, args.search)

Run the complete example

In the environment from step 1, run:

python quickstart.py --search "UK inflation"

Enter a forecast's question_id when prompted to see its latest run and reasoning.

Examples on other pages

On this page