Neumann

Neumann turns rough guesses into a realistic range of outcomes for any plan built on uncertain numbers: project cost and budget estimates, schedules and delivery dates, capacity, revenue, and investment returns (NPV, IRR, payback). It runs a Monte Carlo simulation and reports percentiles, the chance of hitting a target or staying within budget, and which input matters most. Free tier, no sign-in: 3 free runs per day. Signed in with a free Kromatic account: 10 free runs per day, each a small model of up to 10,000 trials and 50 nodes. After that, a run costs 1 credit ($0.50), or 5 for a large one.

Hosted MCP Server

npx add-mcp 'https://neumann.kromatic.com/mcp'

Installs into Claude Code, Codex, Cursor and more

Documentation

Neumann

Monte Carlo ranges for uncertain plans: project cost, budget, timeline, capacity, NPV/IRR/payback. 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_monte_carlo_simulation tool. Authenticate by sending your raw signup api_key as Authorization: Bearer <key> (an opaque nmn_oat_ / nmn_pat_ token from POST https://neumann.kromatic.com/api/v1/tokens works too). Never pass the key as a tool argument: a tools/call carrying api_key is refused. Omit the header to run on the free anonymous allowance (3 runs/UTC day, ≤1,000 trials, ≤20 nodes per run; no credit accounting).
  • 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_monte_carlo_simulation. Send your raw signup key as the X-API-Key header (the /mcp/tools mirror also accepts it as Authorization: Bearer <key>), never in the body.

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_monte_carlo_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
}'

Step 3 — Read the result. A small run returns the summarised SimulationResult inline:

{
  "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": { "…": "…" }
      }
    ]
  }
}

The MCP tool (and its REST twin) returns a designed summary by default (output nodes with P10/P50/P90, mean, min, max and a per-period series, plus requested metrics); pass detail: "full" for the shape above. A run that takes longer than about 40 seconds keeps running in the background and the tool returns status: "running" with a run_id: call get_simulation_results(run_id), which waits up to 30 seconds per call and reports percent done, until it returns the result. The same tool returns specific numbers from a finished run (nodes, periods, percentiles, a threshold probability, a histogram). On the dedicated POST https://neumann.kromatic.com/api/v1/runs endpoint, large runs (trials × nodes × periods over the threshold) can stream live progress as Server-Sent Events (set stream: true).

Tools

  • run_monte_carlo_simulation — Runs a Monte Carlo simulation of a model and returns the full range of possible outcomes, not one guessed number. A model is a set of uncertain inputs, the formulas that combine them, and the links between them. The simulation draws random values for every input many times; each draw is a trial, and one call is one run. Use this when you need to know how likely different outcomes are for a plan built on uncertain inputs: a project's total cost or budget (for example from low / most likely / high estimates per line item), a schedule or delivery date, capacity, revenue, or an investment's NPV, IRR or payback. It returns a summary: for each output node (the nodes nothing else depends on, or the ones you list in run_params.output_request.nodes) the mean, the 10th, 50th and 90th percentiles (the value that share of trials fall below), the minimum and maximum in the last time period, and the same per period; plus any metrics you asked for, such as NPV, IRR, payback or the chance of beating a target or budget; and a run_id. Pass that run_id to get_simulation_results for other nodes, periods or percentiles. A run that takes longer than about 40 seconds keeps running in the background: you get status running and a run_id, and get_simulation_results gives you progress and then the result.
  • rank_inputs_by_impact — Ranks a model's uncertain inputs by how much each one moves a chosen outcome, and returns the data for a tornado chart. A tornado chart is a bar chart with one horizontal bar per input, sorted with the biggest swing at the top. This is a one-at-a-time (OAT) sensitivity analysis. Every input is held at its median while one input at a time is set to low, middle and high values from its own distribution (by default its 5th, 50th and 95th percentiles). The tool records how the outcome's average changes. Use this when you need to know which assumption matters most, so you know what to research or de-risk first. It returns one entry per input, ranked so rank 1 has the biggest impact. Each entry lists the input values tried, the outcome at each, and an elasticity score. Elasticity means how strongly the outcome reacts: roughly, how many percent the outcome moves when the input moves 1 percent. It returns numbers, not an image. Inputs whose distribution produces a list of values are skipped. It always returns a run_id. It runs the model once per input per value tried, so on a big model it can take minutes: if it is not done in about 40 seconds you get status running, and get_simulation_results gives you progress and then the ranking.
  • summarize_outcome_scenarios — Runs a model once and reports one outcome's worst, median and best values. By default, worst is the lowest value in any trial, median is the middle value, and best is the highest value. Those labels assume higher is better. For a cost or a duration, where lower is better, swap them when you tell the user: the scenario labelled worst is the cheapest or fastest case, which is the good outcome, and the one labelled best is the most expensive or slowest. You can ask for other percentiles instead, such as the 10th and 90th. Use this when you want a short worst, typical and best case story rather than the full distribution. For the full distribution, use run_monte_carlo_simulation -- and use that one, not this, when the user asks for a realistic total or range rather than for named worst, typical and best cases. To find which input matters most, use rank_inputs_by_impact. It returns each scenario's outcome value and every input's own value at the same percentile. Those input values are an estimate, not the exact inputs of that trial. It also returns a run_id; a long run comes back as status running, and get_simulation_results gives you progress and then the result.
  • check_simulation_model — Checks a model for mistakes before you run it and lists every problem it finds. It looks for loops with no delay, nodes that are not connected, unknown distributions, missing or invalid distribution parameters, and broken formulas. A loop, including an edge from a node to itself, is allowed and is not an error when at least one edge in it has delay 1 or more. Use this when you have built or edited a model and want to fix it without spending a run. To see what a run will cost, use estimate_simulation_size_and_cost instead. It returns is_valid and a list of issues. Each issue gives the rule broken, a severity (error, warning or info, where info is only a note), a message, and the node and edge ids involved. If an edge points at a node that does not exist, you get an error message instead of the list.
  • estimate_simulation_size_and_cost — Predicts how large a run of a model will be and how many credits it will cost, without running it. Use this before running a large or unfamiliar model, or to check that an account can afford it. It does not look for mistakes; use check_simulation_model for that. It returns the number of trials, time periods, nodes and edges, the estimated peak memory in MB, a complexity score, the size tier (small or large), the credit cost for an account, and a rough run time in seconds for run_monte_carlo_simulation and for rank_inputs_by_impact (which runs the model many times). Runs over about 40 seconds continue in the background and are collected with get_simulation_results. A run is small when trials × nodes × time periods is at most 100,000, and large otherwise. Accounts have no fixed trial or node limit. That cost is what run_monte_carlo_simulation, summarize_outcome_scenarios and rank_inputs_by_impact each charge for this model. A model with mistakes returns an error instead of an estimate.
  • convert_range_to_distribution — Turns a low-to-high guess, such as "between 80 and 120, 90% sure", into the exact parameters a model input needs. A distribution is the shape of the uncertainty, for example normal (a bell curve), log_normal (skewed and never negative), triangle or uniform. The low and high values are the edges of the range you are that sure of, not hard limits. Use this when you have a rough estimate and need distribution parameters instead of guessing them. You do not need it for a three-point low / most likely / high estimate: that goes straight into the model as a triangle input (min_val, mode_val, max_val), so call run_monte_carlo_simulation directly. Its reverse is convert_distribution_to_range. It returns the distribution type and its parameters, ready to use as a model input's distribution_params. The distribution_type parameter explains which type fits which kind of quantity. Supported types: normal, log_normal, uniform, triangle, beta, gamma, exponential, binomial, discrete_normal, discrete_lognormal, discrete_gamma, constant and bernoulli. Not supported: poisson, categorical, dirichlet, multivariate_normal and constant_array. For beta, give low and high as fractions (2% is 0.02).
  • convert_distribution_to_range — Turns a distribution's parameters into the low and high ends of the range that holds a chosen share of outcomes, by default the middle 90%. It also gives the mean, median, mode, standard deviation and any percentiles you ask for. If you give a threshold, it gives the chance of landing below and above it. Use this when you have distribution parameters, from a model or from fermi (a companion forecasting server from Kromatic, the company behind Neumann, whose estimates carry next_steps.simulate.root_node), and want to know what they mean in plain numbers. Its reverse is convert_range_to_distribution, and the range it returns can be passed straight back to it. Values that do not apply, such as a uniform distribution's mode, come back as null with a note explaining why.
  • get_simulation_results — Checks on, and reads, a run you started with run_monte_carlo_simulation, rank_inputs_by_impact or summarize_outcome_scenarios, using its run_id. Use this when a run tool returned status running or queued: it waits up to 30 seconds for the run to finish, then returns its status and percent done, or the finished result, so call it again until status is completed. Use it also to get specific numbers from a finished simulation without running it again (a finished run keeps every node's and edge's statistics for every period): pass nodes (node, edge or metric ids), periods, percentiles such as 0.05 or 0.95, a threshold to get the chance a value is at least or at most some number, or histogram for the distribution's shape. Without those it returns the summary (or the ranking, or the scenarios). A failed run returns its error and is not charged. Only the caller who started the run can read it, and only with the run_id, which is returned to that caller alone. A run started without an account can be read for 24 hours and only from the same connection, because a caller without an account is recognized by network address and client; sign in with a free Kromatic account for runs that are not tied to one connection. Credentials go in the Authorization header as a Bearer credential; no Neumann tool takes an api_key argument.
  • check_account_and_credits — Shows who Neumann thinks you are and what you can still do: your free runs left today and when they reset, and your credit balance if you have an account. Use this when you need to know, before or after a run, whether the next call will be free or spend credits, how much is left, or whether you are signed in -- and when a run was refused for want of runs or credits, so you can tell the user which it was. There are three tiers. Without an account you get 3 free runs per UTC day; you are recognized by network address and client, so on a hosted platform such as Claude.ai or ChatGPT many callers share one identity and today's free runs may already be spent by someone else. Signed in with a free Kromatic account you get your own 10 free runs per UTC day, spent before any credits. After those, runs cost credits. An account created with POST https://neumann.kromatic.com/api/v1/accounts authenticates you and starts with 5 free credits, but it carries no free daily runs -- only signing in with a Kromatic account does. It returns whether you are anonymous or an account, your account id and status, credit balance, free runs left today with the reset time and the size limits that apply to you, the price of one credit in US dollars, and your last 10 credit transactions. Credentials go in the Authorization header as a Bearer credential and nowhere else; no Neumann tool takes an api_key argument, and a credential that is not recognized returns an unauthorized error. This tool itself needs no account -- it is how a caller without one finds that out. Setup docs: https://kromatic.com/mcp/neumann.
  • send_feedback — Sends a short report about Neumann to the team that builds it. They read every report and use it to fix problems and decide what to build, so honest feedback, good or bad, is useful. Use this when:
  • a Neumann tool returned an error (send it right away), behaved unexpectedly or gave a result that looks wrong;
  • the user needs something no Neumann tool can do, or an option that is missing;
  • a description, output or error message was confusing;
  • a session in which you used Neumann is ending: send one short report even if everything worked. Knowing which tools did the job for which kind of task is useful data, not noise. Do not send one after a routine successful call mid-session. Put several points in one report: a signed-in account may send 5 per hour and 20 per day, and callers without an account share 60 per hour and 300 per day. Only message is required. Fill in the other fields that apply so the team can act without guessing. Write in your own words. Never include personal data (names, email addresses, company names), the user's own numbers (inputs or results), secrets or keys, or the conversation transcript; describe the task and any wrong result in general terms instead. Tell the user you are sending it. Add sender_email only if the user wants a reply. If you are signed in, your account id is included. No account is needed. It returns status "sent".
  • buy_credits — Buys Neumann credits by charging the payment card already saved on your account. Credits pay for runs once the free runs for the day are used up, or for a model too large to run free. Use this when an account is low on credits and the user has agreed to the purchase; to see the balance and the live price first, call check_account_and_credits. No saved card is needed to call it: if none is saved yet, it buys nothing and charges nothing: it returns payment_status checkout_required and a checkout_url, which the user opens in a browser to save a card before you call this tool again. It returns the credits bought, the new balance, the payment status and, when a card is needed, the checkout_url. It needs a Neumann account: send your key, or your signed-in token, in the Authorization header as a Bearer credential. No Neumann tool takes an api_key argument. Buying credits needs the neumann:billing scope when you are signed in rather than using a key.
  • create_result_share_link — Creates a public web link to one of your results, so you can hand the numbers to someone who has no Neumann account. Use this when the user wants to send, publish or show a result to another person; to put the numbers in the conversation instead, call get_simulation_results. It works for any run you own: a run from run_monte_carlo_simulation, rank_inputs_by_impact or summarize_outcome_scenarios, and a run of a graph you saved. There is one share tool for every kind of run -- do not look for a separate one. Pass the run_id the run tool returned when the run was made, earlier in the conversation; no tool lists runs, and you do not need a graph_id. That holds for a run of a saved model too: when the user asks to share the run of a saved graph, call this tool directly with that run's run_id -- list_graphs and get_graph return graph_ids, not run_ids, so they are not a step here. Calling it again for the same run returns the same link rather than a second one. Anyone who has the link can open the result without signing in, so ask the user before you create one. delete_run deletes the stored result, which also stops its share link working. There is no way to turn off a link and keep the result. A run made without an account takes two calls, and the run_id is what carries it from one to the other. First call: the caller has no account, so call this tool with the run_id anyway; nothing is shared, and it returns a message asking the user to sign in with a free Kromatic account -- tell the user that. Second call: once the user has signed in, call this tool again with the SAME run_id. Only that second call shares the run and keeps it on the account; signing in alone shares nothing, and a different run_id, or a new run, does not carry the claim. Do it within 24 hours, because a run made without an account is deleted after that. When you tell the user what happens next, say both steps: sign in, then you will share it using the same run. It returns the share_url. It needs a Neumann account: send your key, or your signed-in token, in the Authorization header as a Bearer credential. No Neumann tool takes an api_key argument.
  • save_graph — Saves a model to your Neumann account so you can come back to it instead of describing it again. A saved model is called a graph: its uncertain inputs, the formulas that combine them, and the links between them. Use this when the user will revisit this model -- a budget, a plan, a business case they will update -- or when you want to re-analyse it later without resending it; for a one-off question, pass the model inline to run_monte_carlo_simulation and save nothing. Omit graph_id to save a new graph. Pass the graph_id of a graph you saved before to replace that same graph with this version; the earlier versions are kept, so replacing is not losing anything. It returns the graph_id to use later, the version_id of the version it just stored, the name, and when it was last updated. Use that graph_id with get_graph to read the model back, with rank_inputs_by_impact or summarize_outcome_scenarios in place of an inline graph, and with delete_graph to remove it. run_monte_carlo_simulation does not take a graph_id: read the graph with get_graph and pass it inline. It needs a Neumann account: send your key, or your signed-in token, in the Authorization header as a Bearer credential. No Neumann tool takes an api_key argument.
  • list_graphs — Lists the graphs saved on your Neumann account, the most recently updated first. Use this when the user refers to a model they saved earlier and you do not know its graph_id; it is how you find the id that get_graph, rank_inputs_by_impact, summarize_outcome_scenarios and delete_graph need. It does not list runs or results -- it lists the models themselves. It returns one entry per graph with its graph_id, name, when it was last updated and how many input and formula nodes it has, plus next_offset when there are more to page through. It needs a Neumann account: send your key, or your signed-in token, in the Authorization header as a Bearer credential. No Neumann tool takes an api_key argument.
  • get_graph — Reads one graph you saved earlier, by its graph_id, and returns the whole model in the same shape the run tools accept. Use this when you want to show the user what is in a saved model, edit it before running it again, or run it with run_monte_carlo_simulation, which takes the model inline and not a graph_id. To find a graph_id first, call list_graphs. It returns the graph_id, the name, when it was last updated, and the graph itself: its uncertain inputs with their distributions, the formula nodes, the links between them and the run settings. It reads the model, not any result of running it. It needs a Neumann account: send your key, or your signed-in token, in the Authorization header as a Bearer credential. No Neumann tool takes an api_key argument.
  • delete_graph — Deletes one graph you saved, together with the runs of it. Use this when the user has explicitly asked for that model to be removed, and not otherwise. Confirm which one first with list_graphs or get_graph, because the graph_id is the only thing that says which model goes. Neumann purges it for good after 30 days, and until then nothing on this connector can restore it -- no tool here can put it back -- so treat it as final and do not tell the user it can be recovered. Deleting the same graph twice changes nothing and does not extend the 30 days. To stop using a graph without removing it, just leave it saved and do not pass its graph_id. It returns the graph_id, what kind of thing was deleted, the time it was deleted and the date it is purged for good. It needs a Neumann account: send your key, or your signed-in token, in the Authorization header as a Bearer credential. No Neumann tool takes an api_key argument.
  • delete_run — Deletes one of your own runs -- one result -- and its share link, leaving the model it came from alone. It works on any run_id you can read, whether the run came from a model you passed inline or from one you saved; afterwards get_simulation_results can no longer read it. Use this when the user has explicitly asked for that result to be removed, and not otherwise. To remove a saved model and all of its runs at once, use delete_graph. Neumann purges it for good after 30 days, and until then nothing on this connector can restore it -- no tool here can put it back -- so treat it as final and do not tell the user it can be recovered. Deleting the same run twice changes nothing and does not extend the 30 days. It returns the run_id, what kind of thing was deleted, the time it was deleted and the date it is purged for good. It needs a Neumann account: send your key, or your signed-in token, in the Authorization header as a Bearer credential. No Neumann tool takes an api_key argument.

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/<name>, and (for runs) as POST https://neumann.kromatic.com/api/v1/runs. List them with GET https://neumann.kromatic.com/mcp/tools.

MCP resources and prompts

An MCP client can read two documents without calling a tool (resources/list, resources/read):

  • neumann://catalog — every distribution and formula function this service accepts, with their parameters. The same answer the contract endpoint gives.
  • neumann://examples/graph — one complete, valid model to copy and edit.

And one prompt (prompts/list, prompts/get):

  • model_business_idea(idea) — walks a business idea from Fermi estimates through a checked, run and sensitivity-analysed simulation.

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}". There is no implicit sum: write "{e1} + {e2}" to add two edges; a node with no formula is 0.
  • 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 check_simulation_model and estimate_simulation_size_and_cost tools before spending credits on a run. Turn a human "between X and Y" estimate into distribution params with convert_range_to_distribution, or go the other way — turn distribution params back into plain numbers (mean, quantiles, a central interval) — with convert_distribution_to_range.

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_simulation_size_and_cost tells you the tier and exact credit_cost before you run.
  • Free signed-in runs: an account created by signing in with Kromatic on the MCP endpoint gets 10 free runs per UTC calendar day, each at most 10,000 trials over at most 50 nodes, used before any credits. API-key accounts get none.
  • 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. With no saved card, the buy_credits tool charges nothing and returns payment_status: "checkout_required" plus a checkout_url instead.
  • 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 a run_monte_carlo_simulation result) into a public link anyone can open with no credential: POST https://neumann.kromatic.com/api/v1/runs/{run_id}/share. (There is no MCP sharing tool yet, because runs made through MCP cannot be shared.) It returns share_url — a https://neumann.kromatic.com/s/<token> 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.