The API.

One POST. Eight engines. Every reading they returned.

Every endpoint answers over HTTPS with JSON. One expression goes out; the reading of each engine comes back, so you can see where they agree and where they don't. Authentication is a single header — X-API-Key — on /evaluate; the rest is public. If you are wiring this to an AI assistant, skip to MCP and let it call the engines itself.

Endpoints

MethodPathAuthWhat it does
POST/api/v1/evaluateAPI keyEvaluate an expression on every engine
POST/api/v1/demononeSame, capped at 256 bytes, rate-limited per IP
GET/api/v1/functionsnoneThe function catalogue
POST/api/v1/sharenoneMint a permalink for an expression
GET/api/v1/share/{id}noneThe expression behind a permalink
POST/api/auth/instant-keynoneMint a free key, no sign-up
GET/api/creditsnoneThe engines, their versions and licences

Getting a key

One POST, no sign-up, no e-mail. The key comes back once and is never shown again — store it before you close the terminal.

curl -X POST https://ultimath.ai/api/auth/instant-key \
  -H "Content-Type: application/json" \
  -d '{"name": "my-laptop"}'
{
  "success": true,
  "api_key": "utev_…",
  "api_key_id": "…",
  "user_id": "…"
}

201 on success. The body is optional; name is a label for your own benefit, up to 64 characters. The route is rate-limited per IP, so script it once, not in a loop. Prefer an account you can come back to? Sign up instead — same key, plus a page to manage it.

Evaluate

curl -X POST https://ultimath.ai/api/v1/evaluate \
  -H "X-API-Key: $ULTIMATH_KEY" \
  -H "Content-Type: application/json" \
  -d '{"expression": "(1e16 + 1) - 1e16"}'

Request

FieldTypeDefaultMeaning
expressionstring Required. Up to 4 KB. Numbers, constants, operators and functions only — no casts, no type constructors.
precisionintegeraccount default Significant digits, 1999. Above your plan's ceiling it is refused, never silently clamped. Omit it and the account default applies, capped by the plan.
closed_formbooleanfalse Ask for the exact form when an engine can prove one — symbolic and symbolic_latex appear beside the digits. Proving it costs more than reading the digits, so it is opt-in.
formatstringfixed fixed, scientific, or auto to let each engine choose.
modestringfull full keeps the per-engine breakdown. simple returns one reading and no breakdown.

Response

engines is keyed by engine name, not an array — read engines.calcium, never engines[0]. An engine that failed carries error in place of result; the others still answer.

{
  "expression": "(1e16 + 1) - 1e16",
  "result": "1",
  "accuracy": { "source": "calcium", "exact": true, "agree": 7, "engines": 8 },
  "engines": {
    "calcium":   { "result": "1", "exact": true },
    "flint":     { "result": "1", "exact": true },
    "mpfi":      { "result": "1", "exact": true },
    "libbf":     { "result": "1" },
    "pari":      { "result": "1" },
    "ntl":       { "result": "1" },
    "symengine": { "result": "1" },
    "cpp":       { "result": "0", "diverges": true }
  },
  "meta": {
    "id": "a110b5645dd38380",
    "ms": 5,
    "engines_ms": { "calcium": 5, "cpp": 4, "flint": 2, "libbf": 1,
                    "mpfi": 2, "ntl": 2, "pari": 2, "symengine": 2 },
    "precision": 64
  }
}

That example is the whole product in eight lines: seven engines say 1, the hardware double says 0. Nothing reconciles them — a divergence is a finding, not a bug to paper over.

Reading an answer

Engines print what they can prove, so a reading has a shape as well as a value. A plain number is a value. x +/- r is a ball: the value is within r. [lo, hi] is an interval: the value is inside it, and containment — not equality — is how it should be judged.

How far a reading is underwritten

Every engine says one of three things about its own reading — and they are not three points on one scale. No engine is ever asked about another.

FieldMeaning
"exact": true The engine holds the value. The reading is a truncation of it: ask for more digits and you get digits of the number, not a closer approximation. This is not a very large digits — it is a different statement, which is why it is a different field.
"digits": N The engine's own error bound covers the first N significant digits of its reading. 0 is a real answer: the bound covers none of them, which is what catastrophic cancellation does to a value.
both absent The engine carries no error bound. It printed as many digits as you asked for and can say nothing about any of them. This is not 0. Five of the eight engines are here by construction — and that is the reason the other three exist. Do not fill the gap with a zero.

They are never both set. Read exact first, then digits, then treat the absence as the answer it is.

What the answer itself is worth

Those three fields are each one engine talking about itself. accuracy is the other question: how far the reading in result survives being checked by someone else. It never comes from the engine that produced the reading.

FieldMeaning
source The engine result was taken from, so you never re-derive it.
exact An engine that agrees holds the value. Every digit printed is a digit of the number.
proved Leading significant digits of result covered by a different engine's own error bound. Past that point the digits were printed at the precision you asked for and no one underwrites them — quoting them is quoting one implementation. 0 is a real answer; absent means no other engine carries a bound at all.
agree / engines Engines returning the same reading, over engines asked. The denominator counts refusals: three engines declining a division by zero is part of the answer, and 2 of 2 would hide them.

Here exact and proved can stand together — unlike per engine, where they exclude each other. One engine holding the value and another proving N of its digits are two facts of different kinds, and the second is the one that stops at N. An engine carrying diverges contradicts that consensus outright; it is a finding, not a formatting difference.

Errors

StatusWhen
400Malformed body, unparseable expression, precision outside 1999
401Missing or unknown X-API-Key
403Precision above your plan, or monthly quota spent
429Rate limited — retry after a moment
503Capacity shedding; the request was not evaluated, retry

The body is always {"error": "…"}. A message names only the engine that produced it, never another one.

Limits

MCP — let the assistant call the engines

The same eight engines, reachable as a tool. An assistant that would otherwise guess at a number calls out to them instead and gets every reading back — so a disagreement lands in the conversation rather than in your work. Two tools: evaluate runs an expression on all eight engines, list_functions lists what they can do.

Claude Desktop

One click: download the extension, paste your key, done. Install guide — or grab the bundle directly.

Any other MCP client

{
  "mcpServers": {
    "ultimath": {
      "command": "npx",
      "args": ["-y", "ultimath-mcp"],
      "env": { "ULTIMATH_API_KEY": "utev_…" }
    }
  }
}

Node 18 or later; npx fetches the server on demand. The key is the one from Getting a key — it travels in the environment, never in the expression. Source: github.com/Flupke68/ultimath-mcp.

Two things worth knowing before you read its answers. The exact form is asked for on every call, so sqrt(2)+1 comes back with Add(Sqrt(2), 1) beside the digits — quoting an exact value is what the tool is for. And a comparison does not mean the same thing on every engine: on the enclosure engines !=, < and > are true only once proven, while ==, <= and >= are true as soon as they are not disproven. A true == there is not a proof of equality — the exact engine decides that one — but a true != is a proof of difference.

Something missing here? contact@ultimath.ai.