Documentation
Integrate financial math that shows its work — in minutes
No API keys, no signup, no SDK required. Two surfaces — a REST API for products and an MCP server for AI assistants — over the same 61 Concordance-tested models. Three of them are free to call with attribution; the rest come with a paid plan — see pricing.
Watch the film — Anatomy of an Answer, 3½ minutes
Last reviewed
Quickstart (REST)
Three endpoints per model: the JSON contract (GET /api/v1/models/{model}), the full specification (…/spec), and execution (POST /api/v1/models/{model}). Discover models at GET /api/v1/models.
curl -s https://worthune.com/api/v1/models/relocation \
-X POST -H 'content-type: application/json' \
-d '{
"currentSalary": 95000,
"newSalary": 108000,
"currentMonthlyExpenses": 4200,
"newMonthlyExpenses": 4900,
"movingCosts": 6000,
"currentSavings": 40000,
"annualReturn": 0.07,
"yearsHorizon": 10
}'Or from JavaScript:
const res = await fetch(
"https://worthune.com/api/v1/models/relocation",
{
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(inputs), // per the model's contract
},
);
const result = await res.json();
if (result.ok) {
console.log(result.outputs.breakEvenMonths);
console.log(result.specVersion, result.assumptions);
}SDKs — JavaScript and Python
Thin official wrappers over the same REST API. Zero dependencies in both, so adding one does not pull a tree into your build. Nothing here is available only through an SDK — every call above works with fetch or curl if you would rather not add a dependency at all.
npm i worthune # JavaScript / TypeScript
pip install worthune # PythonJavaScript:
import { Worthune, verifyRecord } from "worthune";
const wy = new Worthune();
const result = await wy.run("relocation", inputs);
if (result.ok) {
console.log(result.outputs.breakEvenMonths);
console.log(result.specVersion, result.assumptions);
// Recompute the decision record's digest from the response itself.
console.log(await verifyRecord(result));
} else {
console.error(result.errors); // field-level, not a thrown exception
}Python:
from worthune import Worthune, verify_record
wy = Worthune()
result = wy.run("relocation", inputs)
if result["ok"]:
print(result["outputs"]["breakEvenMonths"])
print(result["specVersion"], result["assumptions"])
print(verify_record(result))
else:
print(result["errors"]) # field-level, not a raised exceptionBoth expose the same surface: listModels, getContract, getSpec, run, listEvals, getEvalDataset, and getFacts — snake_case in Python, camelCase in JavaScript.
Two behaviors worth knowing. A validation failure comes back as ok: false with field-level errors rather than as a thrown exception, because a rejected input is an answer and not a crash; network and server faults do throw. And verifyRecord recomputes the decision record’s digest from the response you were handed, locally — it asks us for nothing, so it still means something if we are the party you are checking.
Source: github.com/CapsteraSupport/worthune-sdk.
MCP setup (AI assistants)
Streamable-HTTP endpoint: https://worthune.com/api/mcp/mcp — listed in the official MCP registry as com.worthune/models. Twenty tools; six need no key. The tools that operate on stored households take your wk_ key either once, as an Authorization: Bearer header on the connection (recommended — the key never enters the transcript; clients that support headers, such as Claude Code and Cursor, configure it that way), or per call as the api_key argument (on a connection that sent a bearer, the bearer is the identity and the argument is ignored). Roles apply exactly as on REST, and any tool result that is not an answer is an MCP error (isError: true) whose JSON body names the reason, so an agent never mistakes an error for a result.
- Claude: Settings → Connectors → Add custom connector → paste the endpoint URL.
- ChatGPT: Settings → Connectors (developer mode) → add the same URL.
- Catalog tools:
list_models(catalog) →get_model_contract(inputs, domains, sentinels; passinclude_spec: truefor the full spec) →run_model(validated execution) →verify_claim(check a claim before stating it; see Verification). - Household tools: keyless and stateless,
list_example_householdsandtry_household_projection; on stored households,create_household,get_household,get_household_picture,list_households,patch_household,replace_household,archive_household,project_household,decide_household,list_decisions,get_decision,import_households,draft_import_mapping, andnarrate_decision— the same operations, floors, and envelopes as the REST routes below. On a sandbox key,draft_import_mappingandnarrate_decisionare REST-only, with your own model key as thex-model-keyheader. - Handoff pattern: after a run, deep-link users to the matching interactive calculator with inputs prefilled, e.g.
/scenarios/relocation?currentSavings=40000.
The response envelope
Every successful run returns the same shape — designed so a caller (human or AI) can show its work:
outputs— the model outputs, exactly as the engine computed them, untouched.specVersion/specUrl— the exact contract version that produced them.sentinels— special values explained (e.g.breakEvenMonths: 9999means “never”), each with atriggeredflag.assumptions— what the model held true for this run.facts— government constants used (value, tax year, primary source).record— a decision record: SHA-256 over the canonical JSON of{model, specVersion, inputs, outputs}(keys sorted recursively). Store it with anything built on these numbers; recompute it later to prove they came from that spec version, unaltered.inputs— echoed back;disclaimer— planning model, not advice.
Validation & errors
Inputs are validated against the spec's domains at the boundary. Out-of-domain values are rejected, never clamped — engine behavior outside a spec's domain is undocumented, and a silently adjusted answer is worse than an error. All fields are required; unknown fields are rejected.
HTTP 400
{
"ok": false,
"model": "relocation",
"errors": [
{ "field": "newSalary", "message": "must be between 30000 and 300000" },
{ "field": "yearsHorizon", "message": "required" }
]
}Unknown models return 404. Malformed JSON returns 400 with a single error message.
Conventions
- Rates are annual decimals (
0.07= 7%) unless a contract says otherwise (two models use percent points — their contracts and specs flag it prominently). - Non-finite numbers are encoded as the strings
"Infinity"/"NaN"(JSON has no representation for them). - Chart arrays are part of the published contract and returned in full.
- Model changes ship as spec version bumps — see the public changelog. Pin behavior by checking
specVersion. - Paid calls authenticate with
Authorization: Bearer wk_…(or anx-api-keyheader; the MCPrun_modeltool takes anapi_keyargument). The three sample models need no key; other models return402without one. - There is no usage meter — the keyless surface is bounded by which models you can call (emergency-fund, relocation, and side-business), never by how many runs you make; a free sandbox key opens every model with a daily ceiling, and a paid plan has none. See pricing. A burst backstop of 120 requests/minute per IP returns 429 with a
retry-afterheader; it stops runaway loops, never real users. Aggregate usage is public at /api/v1/telemetry.
Embeds — a working calculator in one script tag
Drop a calculator into your page. Prefill inputs with data-input-* attributes; the frame links its published spec and carries its own disclaimer and attribution line. Calculator embeds are free for every model. The household widgets (tenant tokens, below) render under your brand, and Growth can drop their attribution line through the tenant config.
<div data-worthune-embed="relocation"
data-height="720"
data-input-currentSavings="40000"></div>
<script async src="https://worthune.com/embed.js"></script>The household engine
Want to build the integration before you buy? A sandbox key arrives by email and opens everything below on up to five stored households for thirty days — the same key works over REST, MCP and both SDKs, and Launch keeps the organization and its households when you move up.
One versioned document per client — members, accounts by tax wrapper, incomes, liabilities, expenses — and every computation the platform runs on it. The engine page at /household describes it whole, and /demo runs it live on a labeled fictional household.
Running it needs no key. The routes below operate on a STORED household and take a key — a sandbox key within its limits, or a paid plan's — because storage is what a plan buys. The computation itself is free and always will be — there is no run meter:
POST /api/v1/project— the full projection and optional Monte Carlo on a household document you send, with no key and nothing written down: no organization, no id, no audit entry. Returns the same evidence record the paid route does, and its hash reproduces on an identical document.GETthe same path for the contract and twelve ready-to-post example households.POST /api/v1/book-scan— the same coordination sweep across up to 250 households at once, ranked, with a record hash per finding and a roll-up by whoever owns the relationship. Also keyless, also stores nothing.- Over MCP:
try_household_projectionandlist_example_households, same terms. - Human-facing: /try for one household and /try/book for a CSV of them, parsed in your browser so the file never leaves it.
What a paid key adds is the household as something we keep:
POST /api/v1/households— create the document (pass your ownimportKeyand a retried create returns the household it already made, never a twin);PATCH …/{id}applies deltas with optimistic concurrency (a stale version conflicts loudly, never overwrites).GET …/{id}/picture— one household, one picture: the balance sheet by wrapper and owner, income and spending this year, how current the last computation is against the engine’s spec, the newest decisions, and a data-quality report that names what is missing and what supplying it would unlock. Sums only, deterministic for a givenasOfdate.POST …/{id}/project— the deterministic year-by-year projection, plus an optional seeded, longevity-aware Monte Carlo. Assumptions are yours to supply, or labeled illustrative defaults; apolicy(withdrawal order, Roth conversion schedule) applies to the deterministic run and every simulated path and is hashed into the evidence record.POST …/{id}/decisions— the coordination strategies (withdrawal sequencing, Roth ladders, Social Security claiming, asset location, tax-loss harvesting, annual gifting, pension elections), each returning ranked alternatives with dollar deltas. Every stored decision keeps the hashed inputs of its evidence record, soGET /api/v1/decisions/{id}re-verifies years later with the SDK’sverifyRecord.POST /api/v1/households/import— bulk migration, up to 200 households a batch:dryRunreturns the complete per-row error worklist with zero writes, andimportKeymakes re-runs idempotent. Mapped mode takes your legacy rows plus a declarative template — draft the template with AI if you like; deterministic code executes it.- Signed webhooks —
household.computed,.updated,.archived,.decision, and.drift, which fires when a stored plan’s numbers move because a rate, rule, or balance did. Drift payloads carry structured reasons plus a plain-Englishnoticean advisor can forward as-is, and subscribed organizations get the same open items as a weekly rollup email — dates and counts only, never recomputed numbers. - The same operations are MCP tools, so an AI assistant can create, project, decide, and verify end to end. The whole surface is described by
GET /api/v1/openapi.json, and every route above is driven end to end in CI against a real database on every pull request.
White-label — tenant branding & embed tokens (Growth)
GET/PUT /api/v1/tenant-config— brand name, theme overrides (validated through the WCAG AA gate at write time, with per-field error paths), append-only firm disclosure paragraphs, the embed domain allowlist, and attribution removal (Growth). Stored configs always render because invalid ones are never stored. Embedded widgets carry “where’s this from?” provenance chips and a checkable-record footer by default;evidenceAffordances: falseswitches them off for your surfaces.POST /api/v1/embed-tokens— mints a short-lived, household-scoped signed token. Add it asdata-tokenon the embed loader (ortokenon the web component) and the frame renders that real household under your branding — only on the origins in your domain allowlist, enforced as aframe-ancestorsCSP. Tokens can never call the API; a leaked token exposes at most one household’s widgets until it expires.- Without a token, embeds always show the labeled fictional household. See the reference build for the full white-label surface.
Eval datasets & the facts API
- /api/v1/evals — eval datasets for financial AI testing: 250 deterministic input/expected-output pairs per model as JSONL, the exact cases our Concordance harness runs in CI. Three datasets are open; the rest download with your paid key. Overview at /evals.
- /api/v1/facts — the sourced-constants registry (IRS limits, brackets, SSA factors) with primary sources, effective periods, and verification dates. Human-readable at /facts.
Grading — send us your copilot’s answers
The service form of the eval datasets: POST your own answers (from an LLM, an agent, or your own engine) and get each one graded against the production engine at the Concordance harness tolerances. Up to 500 cases per request; the three sample models grade without a key, the rest with a key (sandbox or paid).
curl -s https://worthune.com/api/v1/grade/emergency-fund \
-X POST -H 'content-type: application/json' \
-d '{
"cases": [{
"inputs": { "monthlyExpenses": 4000, "targetMonths": 6,
"currentSavings": 8000, "monthlySavings": 500,
"savingsAccountRate": 0.04 },
"answer": { "targetAmount": 24000, "monthsToGoal": 29 }
}]
}'
# → { "ok": true, "passed": ..., "failed": ..., "passRate": ..., "cases": [...] }Each case comes back pass / fail (with per-key diffs) / invalid-inputs, plus a batch pass rate. A tolerance override is accepted but clamped — never looser than 1e-2 relative, never tighter than the harness itself.
Verification — check a claim before you state it
The guardrail form of grading, shaped for one moment: an assistant is about to tell a user a number. POST the model inputs and the claims the assistant wants to make; each claim comes back verified / violated / out-of-scope, with the engine’s computed value as proof and the run’s decision record tying the verdict to a spec version. Claims support == (at tolerance) and < <= > >= (with the same tolerance as grace at the boundary). Up to 50 claims per request; same key boundary as running the model. MCP assistants get the identical check as the verify_claim tool.
curl -s https://worthune.com/api/v1/verify/emergency-fund \
-X POST -H 'content-type: application/json' \
-d '{
"inputs": { "monthlyExpenses": 4000, "targetMonths": 6,
"currentSavings": 8000, "monthlySavings": 500,
"savingsAccountRate": 0.04 },
"claims": [
{ "path": "targetAmount", "value": 24000 },
{ "path": "monthsToGoal", "op": "<=", "value": 30 }
]
}'
# → { "ok": true, "verdict": "verified", "claims": [...], "record": {...} }Out-of-scope is a deliberate verdict, not an error: it means the model doesn’t compute the claimed quantity, so we don’t rule on it. Separately, anyone holding a stored response can check its decision record — digest integrity, no key, nothing stored — at /proof/record or POST /api/v1/records/verify.
Computation reports — a run as a document
POST /api/v1/models/{model}/report takes the same JSON inputs as a run and returns a print-ready HTML document instead of an envelope: the inputs, the outputs with sentinel annotations at full precision, the assumptions in force, the sourced constants consumed, and the decision record — including the exact verification payload it hashes, so the file verifies itself: anyone holding only the document can recompute the digest or paste the payload at /proof/record. Print to PDF to archive. Same key boundary as running the model; errors come back as JSON. The report documents a computation — it is not advice, and it does not by itself satisfy any regulatory obligation (it says so on its face).
curl -s https://worthune.com/api/v1/models/emergency-fund/report \
-X POST -H 'content-type: application/json' \
-d '{ "monthlyExpenses": 4000, "targetMonths": 6, "currentSavings": 8000,
"monthlySavings": 500, "savingsAccountRate": 0.04 }' \
-o emergency-fund-report.html
# → a standalone, self-verifiable document; print to PDF to archiveModel reference
Each page shows the model's input contract and a live request/response pair generated from the real engine: