Most probable number (MPN)
Turn a row of positive and negative tubes into a density: a 5-3-1 result from three dilutions of 5 tubes each becomes an MPN per 100 mL, with the classic Thomas estimate and a confidence interval beside it. Free in your browser.
- Free
- Runs in your browser
- No sign-up
- Your data stays on your device
Run — free
Worked example
- Per volume ml
- 100
- Pattern
- 5-3-1
- Total tubes
- 15
- Total positive
- 9
- Mpn per ml
- 1.0864475085
The most probable number method estimates viable microbial density when organisms are counted by presence or absence in replicate tubes across a dilution series rather than as colonies on a plate. Water laboratories, food microbiologists, and environmental analysts still rely on MPN codes and tables for coliforms, enterococci, and other indicator organisms. This calculator accepts each level of your series as inoculum volume in millilitres, tube count, and number of positive tubes, then solves the Poisson maximum-likelihood equation for density per millilitre of original sample. You can rescale the result to MPN per 100 mL or any other report volume, compare against the classic Thomas approximation, and read an approximate Fisher-information confidence interval. All-sterile series return zero density; all-positive series are rejected as unbounded until you add a higher dilution with negatives.
What the most probable number method estimates
Most probable number analysis assumes that viable organisms are distributed randomly in the sample according to a Poisson process and that a tube becomes positive whenever it receives at least one viable unit. Under those assumptions the probability that a tube inoculated with volume v millilitres of original sample is positive equals one minus e to the power of minus lambda times v, where lambda is the unknown density in organisms per millilitre.
Replicate tubes at several dilutions give a likelihood surface for lambda; the most probable number is the density that maximises that likelihood. Practically you record how many of n tubes grew at each inoculum volume, form the multi-level pattern such as five-three-one, and look up or compute the density.
Who relies on MPN estimates
Teaching labs and exam generators need a deterministic oracle that reproduces the Poisson MPN logic without forcing students to memorize only three-tube decimal tables. Food and environmental microbiologists who run non-standard replicate counts or unusual inoculum volumes cannot use a fixed McCrady card and benefit from a continuous maximum-likelihood solver. Failed validation does not consume balance.
Keep requests small, pin rounded densities in tests, and you get reproducible MPN estimates for homework, LIMS plugins, and edge workers without standing up statistical software solely for a dilution series.
What you can do with it
Teaching and exam auto-grading
Score student dilution-series work against a deterministic maximum-likelihood MPN oracle that rejects all-positive unbounded series cleanly.
Non-standard replicate designs
Compute continuous MLE density for arbitrary tube counts and inoculum volumes when printed McCrady tables do not cover the layout.
FAQ
What does the pattern string mean?
pattern joins the positive counts in level order with hyphens (for example 5-3-1). It is a convenience label equivalent to the classic MPN code, not a table lookup key.
What happens if every tube in my series is positive?
The result is rejected, because an all-positive series has no upper limit. Add a higher dilution that leaves some tubes negative and run the estimate again.
What if no tube turns positive?
An all-sterile series returns a density of zero. That is a valid result for the sample and dilutions you tested, not an error.
Can I report the MPN per 100 mL?
Yes. The density is calculated per millilitre of the original sample and can be rescaled to MPN per 100 mL or any other report volume your method uses.
How does the result compare with the Thomas formula?
The main figure comes from the maximum-likelihood solution for your tubes. The classic Thomas approximation is shown next to it for comparison, together with an approximate confidence interval.
Related tools
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, by email and from Telegram — and soon from our app too.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/bio/most-probable-number \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"levels":[{"volume_ml":10,"tubes":5,"positive":5},{"volume_ml":1,"tubes":5,"positive":3},{"volume_ml":0.1,"tubes":5,"positive":1}]}'const res = await fetch("https://api.kit.forhosting.com/bio/most-probable-number", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"levels": [
{
"volume_ml": 10,
"tubes": 5,
"positive": 5
},
{
"volume_ml": 1,
"tubes": 5,
"positive": 3
},
{
"volume_ml": 0.1,
"tubes": 5,
"positive": 1
}
]
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/bio/most-probable-number",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"levels": [
{
"volume_ml": 10,
"tubes": 5,
"positive": 5
},
{
"volume_ml": 1,
"tubes": 5,
"positive": 3
},
{
"volume_ml": 0.1,
"tubes": 5,
"positive": 1
}
]
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/bio/most-probable-number", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"levels":[{"volume_ml":10,"tubes":5,"positive":5},{"volume_ml":1,"tubes":5,"positive":3},{"volume_ml":0.1,"tubes":5,"positive":1}]}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"levels":[{"volume_ml":10,"tubes":5,"positive":5},{"volume_ml":1,"tubes":5,"positive":3},{"volume_ml":0.1,"tubes":5,"positive":1}]}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/bio/most-probable-number", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"levels": [
{
"volume_ml": 10,
"tubes": 5,
"positive": 5
},
{
"volume_ml": 1,
"tubes": 5,
"positive": 3
},
{
"volume_ml": 0.1,
"tubes": 5,
"positive": 1
}
]
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "bio.most_probable_number",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |
Technical notes
What the most probable number method estimates
This endpoint does not look up a printed table: it solves the score equation of the log-likelihood with deterministic bisection, so arbitrary tube counts and non-decimal volumes are allowed within the stated limits. When every tube is negative the maximum is at zero density and the API returns mpn_per_ml equal to zero. When every tube is positive the likelihood increases without bound as density grows, so there is no finite MPN and the task fails with invalid_input asking for a higher dilution that includes at least one negative tube. That edge-case contract matches laboratory practice of reporting greater-than values only when the series is fully positive and matches billing clients that treat hard failure differently from a numeric upper bound.
Input contract, outputs, and how to read MPN per mL or per 100 mL
Example: five tubes at ten millilitres all positive, five at one millilitre with three positive, and five at zero point one millilitre with one positive is the classic five-three-one pattern; the MLE density is on the order of one organism per millilitre and scales to roughly one hundred per one hundred millilitres. Validation failures name the field, are not billed, and cover missing levels, non-positive volumes, positive greater than tubes, all-positive series, and out-of-range confidence levels. Pair the numeric density with your own regulatory table or method reference when a standard requires a specific published code rather than a continuous MLE. POST a JSON body whose required field levels is an array of one to twenty objects. Each object needs volume_ml, a positive finite inoculum volume of original sample per tube in millilitres; tubes, a positive integer of replicates; and positive, a non-negative integer of tubes showing growth that does not exceed tubes. Optional per_volume_ml defaults to one and multiplies the density so you can report MPN per 100 mL by sending one hundred, matching many water-quality tables. Optional confidence_level defaults to zero point nine five and must lie in the closed interval from zero point five to zero point nine nine nine. On success the body echoes the levels, a pattern string joining the positive counts with hyphens, total_tubes and total_positive, mpn_per_ml and mpn scaled by per_volume_ml, the same pair for the Thomas approximation when defined, confidence_level, method maximum_likelihood, and a short formula string. When Fisher information is positive the response also includes ci_low_per_ml, ci_high_per_ml, and the scaled ci_low and ci_high.
Where an MPN estimate API earns its keep
Quality systems for drinking water, wastewater, and bottled water still express coliform and enterococci results as MPN per one hundred millilitres; automation scripts can post the raw positive counts and store mpn with the chosen per_volume_ml rather than re-keying table rows.
Integration use cases
Water quality MPN per 100 mL
Enter decimal-series positive counts for coliform or enterococci tubes and set per_volume_ml to 100 to report classic MPN/100 mL for compliance logs.
Technical questions
How do I calculate most probable number from positive tubes?
Send levels with volume_ml, tubes, and positive for each dilution. The API returns the Poisson maximum-likelihood density per mL and optional scaled MPN via per_volume_ml (use 100 for MPN/100 mL).
What if every tube is positive?
The task fails with invalid_input because the likelihood has no finite maximum. Add a higher dilution (smaller volume_ml) that includes at least one negative tube.
How is the Thomas value different from mpn?
mpn is the maximum-likelihood estimate. thomas_mpn uses the historical approximation P over square root of (sample-mL in negatives times sample-mL in all tubes). Both are organisms scaled by per_volume_ml when Thomas is defined.
What does the most probable number calculator cost?
The browser tool is free. API requests use prepaid KIT balance at $0.002 per successful call; invalid input is not billed.