# Neumann Agent Quickstart

Turns a guessed number into a sampled distribution — percentiles, NPV/IRR/payback, sensitivity.

This page is a fully self-contained cold-start guide for AI agents. Follow it
end-to-end and you will be running Monte Carlo simulations in under 60 seconds.

> **Base URL:** this page was served from `https://neumann.kromatic.com`. Use it as your base URL;
> every path below is host-relative. Accounts are environment-scoped (localhost /
> staging / prod) — don't mix them.

## What Neumann is

Neumann runs Monte Carlo simulations over a **graph**:

- **root nodes** sample a value from a probability distribution each time period;
- **calc nodes** compute a value from their incoming edges via a formula;
- the engine runs thousands of trials and returns each requested node/edge as a
  distribution summarised into percentiles + basic stats (optionally a histogram).

**Use Neumann when you need** the distribution of an uncertain quantity that is a
function of several other uncertain quantities — revenue models, cost roll-ups,
risk analysis, capacity planning — anywhere a point estimate hides the spread.

## Authentication

Three ways to authenticate — and one common mistake to avoid.

1. **Raw API key** (recommended for agents) — `POST /api/v1/accounts` returns a
   raw `api_key`. Pass it as the `api_key` **MCP tool argument** or the
   **`X-API-Key` REST header**. The raw `api_key` is NOT a bearer token: putting
   it in an `Authorization: Bearer` header will be rejected.
2. **Opaque bearer** — only tokens prefixed `nmn_oat_` / `nmn_pat_` are valid
   `Authorization: Bearer <token>` values (used on the MCP endpoint). Mint one
   with `POST https://neumann.kromatic.com/api/v1/tokens`; it is the only thing that belongs in a
   `Bearer` header.
3. **Anonymous** — omit credentials entirely. Free, but bounded: at most
   3 runs per UTC calendar day, each at most
   1,000 trials over at most 20
   nodes, with no credit accounting and no saved runs tied to an account. Good
   for a first experiment before signup; **anything larger or more frequent
   needs an account** (`POST https://neumann.kromatic.com/api/v1/accounts`).

> **Bearer vs api_key, in one line:** raw signup `api_key` → `X-API-Key` /
> `api_key` arg; opaque `nmn_oat_`/`nmn_pat_` token → `Authorization: Bearer`.
> Never the reverse.

### Cold-start signup

```
curl -X POST https://neumann.kromatic.com/api/v1/accounts
```

Response (`201`):

```json
{
  "account_id": "…",
  "api_key": "<raw key — shown only once>",
  "credit_balance": 5,
  "created_at": "…"
}
```

**Save the `api_key` immediately** — it is hashed server-side and never shown
again. Pass it on every subsequent call.

## Running a simulation

### Option A — MCP (MCP-native clients)

Connect to the Streamable-HTTP endpoint and call `run_simulation`:

```
claude mcp add neumann --transport http https://neumann.kromatic.com/mcp
```

Then call the tool with a `graph` and (optionally) your `api_key`:

```json
{
  "graph": {
    "contract_version": "1.0",
    "root_nodes": [
      {
        "id": "units_sold",
        "name": "Units sold per month",
        "distribution_type": "normal",
        "distribution_params": {
          "mean": 1000.0,
          "std": 150.0
        }
      },
      {
        "id": "price_per_unit",
        "name": "Price per unit (USD)",
        "distribution_type": "triangle",
        "distribution_params": {
          "min_val": 18.0,
          "mode_val": 25.0,
          "max_val": 40.0
        }
      }
    ],
    "calc_nodes": [
      {
        "id": "revenue",
        "name": "Monthly revenue (USD)",
        "formula": "{e_units} * {e_price}"
      }
    ],
    "edges": [
      {
        "id": "e_units",
        "source": "units_sold",
        "target": "revenue"
      },
      {
        "id": "e_price",
        "source": "price_per_unit",
        "target": "revenue"
      }
    ],
    "run_params": {
      "trials": 5000,
      "time_periods": 1
    }
  },
  "seed": 42
}
```

`POST https://neumann.kromatic.com/mcp` (no trailing slash) is the canonical URL and is answered
directly — no redirect (neumann#241). `POST https://neumann.kromatic.com/mcp/` works too, so you
can POST to either.

### Option B — REST

The same operation over plain HTTP. Either invoke the tool twin directly:

```
curl -X POST https://neumann.kromatic.com/mcp/tools/run_simulation \
  -H "Content-Type: application/json" \
  -H "X-API-Key: <your api_key>" \
  -d '{
  "graph": {
    "contract_version": "1.0",
    "root_nodes": [
      {
        "id": "units_sold",
        "name": "Units sold per month",
        "distribution_type": "normal",
        "distribution_params": {
          "mean": 1000.0,
          "std": 150.0
        }
      },
      {
        "id": "price_per_unit",
        "name": "Price per unit (USD)",
        "distribution_type": "triangle",
        "distribution_params": {
          "min_val": 18.0,
          "mode_val": 25.0,
          "max_val": 40.0
        }
      }
    ],
    "calc_nodes": [
      {
        "id": "revenue",
        "name": "Monthly revenue (USD)",
        "formula": "{e_units} * {e_price}"
      }
    ],
    "edges": [
      {
        "id": "e_units",
        "source": "units_sold",
        "target": "revenue"
      },
      {
        "id": "e_price",
        "source": "price_per_unit",
        "target": "revenue"
      }
    ],
    "run_params": {
      "trials": 5000,
      "time_periods": 1
    }
  },
  "seed": 42
}'
```

…or use the dedicated run endpoint `POST https://neumann.kromatic.com/api/v1/runs` (same `graph`
shape; set `"stream": true` to receive `text/event-stream` progress for large
runs).

### The example graph

The body above is a tiny monthly-revenue business case:

- `units_sold` — a **normal** distribution (mean 1000, std 150);
- `price_per_unit` — a **triangle** distribution (min 18, mode 25, max 40 USD);
- `revenue` — a calc node, `units_sold * price_per_unit`.

A small run returns the full summarised result inline. Expect `revenue.mean`
around 27,000–28,000 USD with a spread captured in the percentiles.

## Tools

### `run_simulation`

Runs a Monte Carlo simulation over a graph of probabilistic root and calculated nodes, returning the full output distribution instead of one guessed number. Use this when you need the range and likelihood of outcomes for a plan driven by uncertain inputs, not just its average case. Both tiers run to completion and return the full result in this same call, never a run_id to poll. Example: {"graph":{"root_nodes":[{"id":"root","distribution_type":"normal","distribution_params":{"mean":100,"std":15}}],"calc_nodes":[{"id":"out","formula":"{e1}"}],"edges":[{"id":"e1","source":"root","target":"out"}],"run_params":{"trials":1000,"time_periods":12}}}

### `oat_sensitivity`

Runs a one-at-a-time tornado sensitivity analysis: sweeps each root node over a set of quantiles (others held at baseline) and measures how the target node responds, ranked by impact. Use this when you need to know which uncertain input most drives an outcome, so you know what to de-risk or estimate more carefully first. Debits one credit for authenticated callers. Example: {"graph":{"root_nodes":[{"id":"root","distribution_type":"normal","distribution_params":{"mean":100,"std":15}}],"calc_nodes":[{"id":"out","formula":"{e1}"}],"edges":[{"id":"e1","source":"root","target":"out"}],"run_params":{"trials":1000,"time_periods":12}}}

### `scenario_analysis`

Runs a simulation once and summarises a target node's worst/median/best outcomes (any percentiles), each paired with a best-effort driving input per root node. Use this when you need a quick worst-case/median/best-case narrative for a plan rather than the full distribution. Debits one credit for authenticated callers. Example: {"graph":{"root_nodes":[{"id":"root","distribution_type":"normal","distribution_params":{"mean":100,"std":15}}],"calc_nodes":[{"id":"out","formula":"{e1}"}],"edges":[{"id":"e1","source":"root","target":"out"}],"run_params":{"trials":1000,"time_periods":12}}}

### `validate_graph`

Checks a graph for structural problems (cycles, dangling edges, unknown distributions, missing refs) before you run it, returning a list of issues by rule and severity. Use this when you built or edited a graph by hand and want to catch mistakes before spending a run on it. Free; no credits debited. Example: {"graph":{"root_nodes":[{"id":"root","distribution_type":"normal","distribution_params":{"mean":100,"std":15}}],"calc_nodes":[{"id":"out","formula":"{e1}"}],"edges":[{"id":"e1","source":"root","target":"out"}],"run_params":{"trials":1000,"time_periods":12}}}

### `estimate`

Pre-flight-sizes a graph without running it: estimated peak memory, engine complexity, which tier (small/large) it would run as, and the credit cost. Use this when you want to know what a run will cost or how heavy it is before committing to it, especially for a large or unfamiliar graph. Free; no credits debited. Example: {"graph":{"root_nodes":[{"id":"root","distribution_type":"normal","distribution_params":{"mean":100,"std":15}}],"calc_nodes":[{"id":"out","formula":"{e1}"}],"edges":[{"id":"e1","source":"root","target":"out"}],"run_params":{"trials":1000,"time_periods":12}}}

### `solve_distribution_parameters`

Converts a human-style estimate (a min/max range at a stated confidence, or a most-likely value) into the distribution_params a root node needs. Use this when you have a rough verbal estimate like 'between 80 and 120, 90% confident' and need engine-ready parameters rather than guessing them yourself. Free; no credits debited. Example: {"distribution_type":"normal","min_estimate":80,"max_estimate":120,"percentile_range":0.9}

### `get_results`

Fetches the status and, once available, the summarised result for a previously-submitted run by its run_id. Use this when you want to re-check or re-fetch a run you already submitted (e.g. via run_simulation) without resubmitting the graph -- run_simulation itself already blocks until done and returns the full result directly, so this is not a poll-until-ready step for it. Example: {"run_id":"run_8f3c2a"}

### `feedback`

Sends feedback, a feature request, or a bug report straight to the Neumann team, CC'ing your sender_email so you keep a copy. Use this when something about the service is wrong, missing, or confusing and you want a human to see it directly rather than guessing at a workaround. Anonymous submissions are allowed. Example: {"feedback_type":"feature_request","message":"Add CSV export for run results.","sender_email":"agent@example.com"}

### `buy_credits`

Purchases credits using a payment method already saved from a prior Stripe Checkout session. Use this when an authenticated account is running low on credits and needs more to keep submitting runs or analyses, without a human re-entering card details. See GET /billing/pricing for the current per-credit price. Example: {"credits":100}

### `share_result`

Creates (or fetches the existing) public share link for a PERSISTED run from the stored-graph flow, renderable as HTML/JSON/SVG. Use this when you want to hand a result to someone without giving them API access -- only for a run saved via the stored-graph flow: a run_simulation result is ephemeral and has no usable run_id. Requires authentication (api_key argument or an Authorization: Bearer token) to CREATE the link; the resulting share_url itself needs no credential to view. Example: {"run_id":"run_8f3c2a"}


List the live tool descriptors (name, description, input/output JSON schema) with
`GET https://neumann.kromatic.com/mcp/tools`. The MCP `tools/list` and these REST descriptors are
single-sourced — they cannot drift.

## The graph contract

Fetch the machine-readable contract — version marker + the full distribution
catalog with each distribution's parameters — at:

```
curl https://neumann.kromatic.com/api/v1/contract
```

Validate and size a graph for **free** before spending credits:

- `validate_graph` — structural + simulation-readiness check, returns issues.
- `estimate` — peak memory, complexity, the tier it would run as, and the credit
  cost.
- `solve_distribution_parameters` — turn a "between X and Y, 90% confident"
  estimate into engine-ready `distribution_params`.

### Time periods are 1-based in formulas (the #1 gotcha)

`{time_period}` inside ANY per-period formula is **1-based** — the first period
is `{time_period} == 1`. The post-simulation aggregation series index
(`npv`/`irr`/`payback`/…) is **0-based** (first period undiscounted). These two
conventions coexist; don't mix them on one series.

**Silent-zero trap:** a mask written `{time_period} == 0` NEVER matches a real
period, so the term it gates silently contributes 0 for the whole run with no
error. Use `{time_period} == 1` to target the first period.

Worked time-phased example — a 3-period model where revenue launches in period 2:

```
"calc_nodes": [
  { "id": "rev", "formula": "{e_base} * if({time_period} >= 2, 1, 0)" }
]
```

Period 1 → 0 (pre-launch); periods 2–3 → `e_base`. Writing
`if({time_period} == 0, 0, 1)` to "skip the first period" would be inert: there
is no period 0, so every period is multiplied by 1.

### Formula functions & aggregations

Per-period formula functions and post-simulation aggregation metrics. The full
machine-readable catalog (with `kind`, description, and arity per function) is the
`formula_functions` block of `GET https://neumann.kromatic.com/api/v1/contract`:

- `abs` (formula) *(args: 1)* — Absolute value.
- `and` (formula) *(args: 1..N)* — Logical AND of its arguments.
- `average` (formula) *(args: 1..N)* — Arithmetic mean of its arguments.
- `binomial` (formula) *(args: 2)* — Sample a binomial outcome.
- `ceil` (formula) *(args: 1)* — Round up to the nearest integer.
- `discount` (formula) *(args: (rate, n))* — Discount factor 1 / (1 + rate) ** n. n is the 1-based {time_period}. Pairs with the 1-based per-period convention; the post-sim 'npv' aggregation is 0-based — don't combine them on one series.
- `exp` (formula) *(args: 1)* — e raised to the argument.
- `floor` (formula) *(args: 1)* — Round down to the nearest integer.
- `if` (formula) *(args: 3)* — Ternary: if(cond, then, else).
- `index` (formula) *(args: 2)* — Element at a position in an array argument.
- `length` (formula) *(args: 1)* — Number of elements in an array argument.
- `ln` (formula) *(args: 1)* — Natural logarithm.
- `log` (formula) *(args: 1)* — Base-10 logarithm.
- `max` (formula) *(args: 1..N)* — Largest of its arguments.
- `mean` (formula) *(args: 1..N)* — Arithmetic mean of its arguments (alias of average).
- `min` (formula) *(args: 1..N)* — Smallest of its arguments.
- `not` (formula) *(args: 1)* — Logical negation.
- `or` (formula) *(args: 1..N)* — Logical OR of its arguments.
- `pow` (formula) *(args: 2)* — Raise the first argument to the power of the second.
- `product` (formula) *(args: 1..N)* — Product of its arguments.
- `pv` (formula) *(args: (rate, value, period))* — Present value of a cash flow discounted by 'rate' over 'period' periods: value / (1 + rate) ** period. period is the 1-based {time_period}; the first period is discounted by ^1. Do NOT also request an aggregation 'npv' on the same series (it is 0-based and would double-shift period 1).
- `round` (formula) *(args: 1..2)* — Round to the nearest integer (or to N decimals).
- `sqrt` (formula) *(args: 1)* — Square root.
- `sum` (formula) *(args: 1..N)* — Sum of its arguments.
- `cagr` (aggregation) *(args: series)* — Compound annual growth rate from the first to the last period of the series.
- `cumsum` (aggregation) *(args: series)* — Terminal value of the cumulative sum across periods (sum_t series[t]).
- `discounted_payback` (aggregation) *(args: series, rate)* — Like payback, but on the rate-discounted cumulative series. Requires a 'rate' param.
- `irr` (aggregation) *(args: series)* — Internal rate of return per trial (the per-period rate where NPV is zero; series index 0-based).
- `mirr` (aggregation) — Aggregation metric 'mirr'.
- `npv` (aggregation) *(args: series, rate)* — Net present value per trial: sum_t series[t] / (1+rate)**t with a 0-BASED series index (period 0 undiscounted). Requires a 'rate' param. Model an up-front outlay as a negative period-0 cash flow.
- `payback` (aggregation) *(args: series)* — Number of periods until the cumulative (undiscounted) series turns non-negative.
- `prob` (aggregation) — Aggregation metric 'prob'.
- `terminal_value` (aggregation) — Aggregation metric 'terminal_value'.

## Credit model

- Starter credits on signup. A **small** run costs 1 credit; **large** runs cost
  more (`estimate` reports the exact cost and tier up front).
- Anonymous runs are free but unaccounted AND bounded:
  3/UTC day, ≤1,000 trials,
  ≤20 nodes. Over-size → `403`
  (`anonymous_trial_limit_exceeded` / `anonymous_node_limit_exceeded`, no run
  consumed); over the daily count → `429`
  (`anonymous_daily_run_limit_exceeded`, with the reset time). The example
  graph here runs 5,000 trials, which needs an account.
- `402 insufficient_credits` when you run out. Check balance + history at
  `GET https://neumann.kromatic.com/api/v1/accounts/me/ledger`.

## Buying credits

- **Check the price** — `GET https://neumann.kromatic.com/billing/pricing` (no auth) returns
  `unit_price_cents` and `currency`. Nothing in these docs hardcodes the
  price; always read it from this endpoint.
- **Step 1 — browser checkout (once):** `POST https://neumann.kromatic.com/billing/checkout`
  (authenticated) with `{"credits": N}` returns
  `{"checkout_url": "..."}`. Send a human to that URL; completing it saves
  a card on the account for later off-session use.
- **Step 2 — off-session purchases (repeatable, no browser):** once a card is
  saved, either `POST https://neumann.kromatic.com/billing/purchase-credits` or the MCP
  `buy_credits` tool, both with `{"credits": N}`, charge the saved card
  directly and return `credits_purchased`, `new_balance`, and
  `payment_status`.
- **Errors:** `402 insufficient_credits` (balance too low — buy more
  credits), `402 card_declined` (the saved card was declined), `422
  no_payment_method` (no saved card yet — run the browser checkout first),
  `503 billing_unavailable` (Stripe is not configured on this host).

## Sharing a result

A PERSISTED run (created via the stored-graph flow, not an inline
`run_simulation` result) can be turned into a public link that anyone can
open with NO credential: call the `share_result` tool, or
`POST https://neumann.kromatic.com/api/v1/runs/{run_id}/share` directly. Both return
`share_url` — a `https://neumann.kromatic.com/s/<token>` link. That link:

- renders a branded HTML result page (percentile summary, histogram, run
  metadata) when opened in a browser;
- serves the same summary as JSON at `<share_url>.json` for agents;
- serves the histogram alone as `<share_url>.svg`.

`DELETE https://neumann.kromatic.com/api/v1/runs/{run_id}/share` revokes it — the link then
404s with no clue whether the token was ever valid.

## Discovery artifacts

- [`/llms.txt`](https://neumann.kromatic.com/llms.txt) — the short cold-start guide
- [`/.well-known/mcp.json`](https://neumann.kromatic.com/.well-known/mcp.json) — MCP descriptor (machine-readable)
- [`/server.json`](https://neumann.kromatic.com/server.json) — MCP-registry manifest
- [`/api/v1/contract`](https://neumann.kromatic.com/api/v1/contract) — distribution + formula-function catalogs
- [`/api/v1/tokens`](https://neumann.kromatic.com/api/v1/tokens) — mint an opaque `nmn_oat_`/`nmn_pat_` bearer (POST)
- [`/billing/pricing`](https://neumann.kromatic.com/billing/pricing) — per-credit price (GET, no auth)
- [`/billing/checkout`](https://neumann.kromatic.com/billing/checkout) — browser Stripe Checkout session (POST)
- [`/billing/purchase-credits`](https://neumann.kromatic.com/billing/purchase-credits) — off-session
  credit purchase with a saved card (POST)
- [`/openapi.json`](https://neumann.kromatic.com/openapi.json) — OpenAPI schema
- [`/docs`](https://neumann.kromatic.com/docs) — Swagger UI
