# Neumann > Turns a guessed number into a sampled distribution — percentiles, NPV/IRR/payback, sensitivity. Give it a graph of probabilistic root nodes (each sampling > from a distribution) and calculated nodes (formulas over their inputs); it > runs thousands of trials and returns the resulting distribution as summary > statistics. REST + MCP, built for AI agents and humans. Neumann supports agent-to-agent cold start: any agent can POST to `/api/v1/accounts` to create an account, receive an API key, and immediately run simulations using starter credits — no human in the loop. > **Base URL (important for cold-start agents):** this document was served from > `https://neumann.kromatic.com`. Use that as your base URL. Every path below is host-relative — > prepend `https://neumann.kromatic.com`. An account created on one environment (localhost / > staging / prod) is not valid on another; don't mix them. ## Cold-start quickstart (3 steps) **Step 1 — Create an account.** No request body. Returns `{account_id, api_key, credit_balance, created_at}`. Save the `api_key` — it is shown only once. ``` curl -X POST https://neumann.kromatic.com/api/v1/accounts ``` **Step 2 — Run a simulation.** Two equivalent surfaces: - **MCP** (for MCP-native clients): connect to `https://neumann.kromatic.com/mcp` over the Streamable-HTTP transport, then call the `run_simulation` tool. Authenticate by passing your **raw signup `api_key` as the `api_key` tool argument**. Omit it to run on the free anonymous allowance (3 runs/UTC day, ≤1,000 trials, ≤20 nodes per run; no credit accounting). The raw `api_key` is NOT a bearer token — do not put it in an `Authorization` header. Only opaque tokens prefixed `nmn_oat_` / `nmn_pat_` are valid `Authorization: Bearer` values; mint one with `POST https://neumann.kromatic.com/api/v1/tokens`. - **REST** (plain HTTP): POST the same arguments to `https://neumann.kromatic.com/api/v1/runs`, or invoke any tool directly at `https://neumann.kromatic.com/mcp/tools/run_simulation`. Send your raw signup key as the `X-API-Key` header (or the `api_key` body field) — again, the raw key is NOT a `Bearer` token. > **`POST https://neumann.kromatic.com/mcp` is the canonical URL and answers directly:** no > trailing slash, no redirect (neumann#241). `POST https://neumann.kromatic.com/mcp/` also works. > (Older builds answered the bare path with a `308`, and older ones still with > a `307` that naive clients turned into a bodyless GET; neither happens now.) Copy-pasteable example — a tiny business case (`revenue = units_sold * price_per_unit`, two probabilistic root nodes feeding one calc node): ``` curl -X POST https://neumann.kromatic.com/mcp/tools/run_simulation \ -H "Content-Type: application/json" \ -H "X-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 }' ``` **Step 3 — Read the result.** A small run returns the summarised `SimulationResult` inline: ```json { "run_id": "…", "status": "completed", "tier": "small", "total_trials": 5000, "credits_debited": 1, "result": { "contract_version": "1.0", "trials": 5000, "time_periods": 1, "summary_statistics": [ { "time_period": 0, "nodes": { "revenue": { "percentiles": {"p10": "…", "p50": "…", "p90": "…"}, "basic_stats": {"mean": "…", "std": "…", "min": "…", "max": "…"} } }, "edges": { "…": "…" } } ] } } ``` Large runs (trials × nodes × periods over the threshold) return a `run_id` with `status: "accepted"` and `result: null` — stream live progress as Server-Sent Events from `POST https://neumann.kromatic.com/api/v1/runs` (set `stream: true`), or poll `get_results(run_id)`. ## 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"} These tools are reachable three ways and cannot drift: as MCP tools at `https://neumann.kromatic.com/mcp`, as REST twins at `https://neumann.kromatic.com/mcp/tools/`, and (for runs) as `POST https://neumann.kromatic.com/api/v1/runs`. List them with `GET https://neumann.kromatic.com/mcp/tools`. ## The graph contract - **root_nodes** — sample from a distribution each period. Valid `distribution_type` values: `bernoulli`, `beta`, `binomial`, `categorical`, `constant`, `constant_array`, `dirichlet`, `discrete_gamma`, `discrete_lognormal`, `discrete_normal`, `exponential`, `gamma`, `log_normal`, `multivariate_normal`, `normal`, `poisson`, `triangle`, `uniform`. See `GET https://neumann.kromatic.com/api/v1/contract` for each distribution's parameters. - **calc_nodes** — computed from incoming edges. `formula` is an expression over incoming edge ids, e.g. `"{e_units} * {e_price}"`; `null` sums all inputs. - **edges** — directed `source` → `target` connections (carry a node's value). - **run_params** — `trials`, `time_periods`, and an optional `output_request`. Pre-flight a graph for free with the `validate_graph` and `estimate` tools before spending credits on a run. Turn a human "between X and Y" estimate into distribution params with `solve_distribution_parameters`. ## Time periods are 1-BASED in formulas (read this — it bites everyone) - Inside ANY per-period formula, `{time_period}` is **1-based**: the first period is `{time_period} == 1`, the second is `2`, and so on. There is no period 0 in a formula. - **Silent-zero trap:** a mask written as `{time_period} == 0` NEVER fires — it evaluates to false (0) for every real period, so the term it gates silently contributes zero the whole run, with no error. To target the first period write `{time_period} == 1`. - The post-simulation **aggregation** series index (`npv`, `irr`, `payback`, …) is by contrast **0-based**: the first period is undiscounted (period 0). Don't mix the two conventions on one series — e.g. discounting a cash flow with `pv(rate, value, {time_period})` (1-based, first period discounted by ^1) AND then requesting an `npv` aggregation (0-based) double-shifts the first period. Worked time-phased example — a 3-period ramp where revenue only starts in period 2 (a launch in the second period): ``` "calc_nodes": [ { "id": "rev", "formula": "{e_base} * if({time_period} >= 2, 1, 0)" } ] ``` Here period 1 yields 0 (pre-launch) and periods 2–3 pass `e_base` through. If you had written `if({time_period} == 0, 0, 1)` to "zero out the first period", it would do nothing — period numbering starts at 1, so the `== 0` test is dead and every period would be multiplied by 1. ## Formula functions & aggregations Functions usable inside a calc-node `formula` (per-period) and post-simulation `aggregation` metrics (series-aware). The full machine-readable list — with each function's `kind`, description, and arity — is at `GET https://neumann.kromatic.com/api/v1/contract` (the `formula_functions` block): - `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 - New accounts get starter credits on signup (`POST /api/v1/accounts`). - A **small** run costs 1 credit; a **large** run costs more. `estimate` tells you the tier and exact `credit_cost` before you run. - **Free anonymous allowance:** no signup needed for up to 3 runs per UTC calendar day, each at most 1,000 trials over at most 20 nodes. Anonymous runs get no credit accounting. Over-size requests return `403` (`anonymous_trial_limit_exceeded` / `anonymous_node_limit_exceeded`) without using up a run; a run past that daily count returns `429` (`anonymous_daily_run_limit_exceeded`) naming the reset time. **Larger or more frequent use needs an account** — `POST https://neumann.kromatic.com/api/v1/accounts`. Note the example graph below runs 5,000 trials, so it needs an account. - Out of credits → `402` with `error.code = "insufficient_credits"`. Read your balance and ledger at `GET https://neumann.kromatic.com/api/v1/accounts/me/ledger`. ## Buying credits - Check the current per-credit price with `GET https://neumann.kromatic.com/billing/pricing` (no authentication) — returns `unit_price_cents` and `currency`. Discovery copy never hardcodes the price; read it from this endpoint. - **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. - **Off-session top-up (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 you may meet: `402 insufficient_credits` (balance too low — buy more), `402 card_declined` (the saved card was declined), `422 no_payment_method` (no saved card yet — run browser checkout first), and `503 billing_unavailable` (Stripe is not configured on this host). ## Sharing a result Turn a PERSISTED run (one created via the stored-graph flow, not an inline `run_simulation` result) into a public link anyone can open with no credential: call the `share_result` tool, or `POST https://neumann.kromatic.com/api/v1/runs/{run_id}/share`. Either returns `share_url` — a `https://neumann.kromatic.com/s/` link that renders a branded HTML result page; append `.json` for the machine-readable summary or `.svg` for the histogram alone. Revoke it with `DELETE https://neumann.kromatic.com/api/v1/runs/{run_id}/share`. ## Links All paths are host-relative — prepend `https://neumann.kromatic.com`. - [`/agents.md`](https://neumann.kromatic.com/agents.md) — longer, fully self-contained quickstart - [`/.well-known/mcp.json`](https://neumann.kromatic.com/.well-known/mcp.json) — machine-readable MCP descriptor - [`/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) - [`/mcp/tools`](https://neumann.kromatic.com/mcp/tools) — list the agent tools (REST) - [`/docs`](https://neumann.kromatic.com/docs) — Swagger UI (interactive) - [`/openapi.json`](https://neumann.kromatic.com/openapi.json) — machine-readable OpenAPI schema - [`/privacy`](https://neumann.kromatic.com/privacy) — privacy policy - [`/terms`](https://neumann.kromatic.com/terms) — terms of service