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
| Method | Path | Auth | What it does |
|---|---|---|---|
POST | /api/v1/evaluate | API key | Evaluate an expression on every engine |
POST | /api/v1/demo | none | Same, capped at 256 bytes, rate-limited per IP |
GET | /api/v1/functions | none | The function catalogue |
POST | /api/v1/share | none | Mint a permalink for an expression |
GET | /api/v1/share/{id} | none | The expression behind a permalink |
POST | /api/auth/instant-key | none | Mint a free key, no sign-up |
GET | /api/credits | none | The 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
| Field | Type | Default | Meaning |
|---|---|---|---|
expression | string | — | Required. Up to 4 KB. Numbers, constants, operators and functions only — no casts, no type constructors. |
precision | integer | account default | Significant digits, 1–999. Above your plan's
ceiling it is refused, never silently clamped. Omit it and the account
default applies, capped by the plan. |
closed_form | boolean | false |
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. |
format | string | fixed |
fixed, scientific, or auto to let
each engine choose. |
mode | string | full |
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.
| Field | Meaning |
|---|---|
"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.
| Field | Meaning |
|---|---|
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
| Status | When |
|---|---|
400 | Malformed body, unparseable expression, precision outside 1–999 |
401 | Missing or unknown X-API-Key |
403 | Precision above your plan, or monthly quota spent |
429 | Rate limited — retry after a moment |
503 | Capacity 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
- 4 KB per expression (256 bytes on the public demo).
- 1–999 significant digits, bounded by your plan.
- 5,000 calls a month while the beta is free.
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.