hosted api · corpus 0.39.0

One request, one cited answer.

The engine runs as a stateless HTTP service. Send the facts, get back the number, every assumption it made, the statute behind each rule, and on request the proof tree you can verify offline. Nothing you send is stored.

REST
https://opentax.invaro.ai/v1/tools/{tool}
POST the arguments, read the JSON
MCP
https://opentax.invaro.ai/mcp
the same tools for agents and IDEs
OpenAPI
https://opentax.invaro.ai/v1/openapi.json
generated from the running server

Three steps

1. Get a key

Sign in at the console with your email. A six-digit code arrives, you enter it, and your key is on the screen. The evaluation plan is free and has no expiry.

Calls without a key work too, at the anonymous rate budget (600 requests an hour). A key raises that to 6,000 and attributes usage to your account so you can see it.

2. Make the first call

A married couple, two children, $120,000 of wages, tax year 2025. Note asOf: it is the law-in-force date and it is required. The engine refuses to guess which year you mean.

bash
curl -s https://opentax.invaro.ai/v1/tools/calculate_tax \
  -H "Authorization: Bearer $OPENTAX_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "filing": {
    "filingStatus": "mfj"
  },
  "income": {
    "wages": 120000
  },
  "credits": {
    "qualifyingChildren": 2
  },
  "asOf": "2025-12-31"
}'

3. Read the answer

response
{
  "ok": true,
  "target": "us.federal.net_tax",
  "asOf": "2025-12-31",
  "answer": "$5,746.00",
  "valueCents": "574600",
  "meaning": "net federal income tax",
  "assumptions": [
    { "factId": "taxableInterest", "value": { "type": "money", "value": "0" },
      "source": "default", "rationale": "Assumed no taxable interest absent contrary input" },
    …
  ],
  "corpusMerkleRoot": "sha256:5f34e0bc…",
  "proof": { … }
}

answer is formatted for people, valueCents is the integer your code should use. A negative value is a refund. assumptions lists every fact the engine filled in with a default, each with a reason. Treat that list as the questions you still have to ask the taxpayer. proof is the full derivation, and corpusMerkleRoot is the hash of the exact rule corpus that produced it.

Conventions

  • Money is dollars in requests: 50000 or "1234.56". Responses give cents as a string and a formatted dollar amount.
  • asOf picks the tax year: 2025-12-31 for TY2025, 2026-12-31 for TY2026.
  • Schemas are strict. An unknown key is a 400, not a silent ignore. The exact input schema for every tool is on its page and in the OpenAPI document.
  • Facts are grouped on the individual tools: filing, income, credits,itemized, retirement and so on. Fill the groups that apply.
  • Nothing is stored. The facts are computed in memory and the answer returned. The usage record holds the tool name, the account, a hash of your return id, and a timestamp. Never the arguments.

Refusals

When the corpus cannot answer, the engine says so instead of estimating. The HTTP status is still 200 with ok: false, because the request was well-formed and the engine did its job. The interesting part is error.code:

codemeaningwhat to do
NEEDS_FACTSa required input is missing; error.data.missing lists each fact id, type and descriptioncollect those facts and call again
NO_APPLICABLE_RULEno rule version is valid on asOf, typically a 2026 state amount the Department has not publishedcheck the coverage matrix for the publication that unblocks it
UNHANDLED_ENUM_CASEa filing status or classification combination the corpus does not encodetreat as out of scope
ERRORa rule-level refusal with a message, e.g. a situation the rule declares out of scope, or a missing asOfread the message

Transport-level problems use HTTP status codes:

statuscodemeaning
400INVALID_ARGUMENTSthe server's own validation message, e.g. an unrecognized key or a wrong type
400BAD_JSONthe body is not a JSON object
401UNKNOWN_API_KEYthe bearer key does not exist or was rotated
404UNKNOWN_TOOLno such tool; the response lists the valid names
413BODY_TOO_LARGEover 512 KB
429RATE_LIMITEDthe hourly budget is spent; Retry-After is set

Tagging a return

Send X-OpenTax-Return-Id with any identifier you choose for the taxpayer-year the call belongs to. It is stored as a hash and shown in the console as "computed returns". One taxpayer, one tax year, any number of federal and state calls and recalculations, counts as one. This is the unit usage pricing will use later. There is no charge on the evaluation plan.

bash
curl -s https://opentax.invaro.ai/v1/tools/compute_return \
  -H "Authorization: Bearer $OPENTAX_KEY" \
  -H "X-OpenTax-Return-Id: client-4821-ty2025" \
  -H "Content-Type: application/json" \
  -d @return.json

Versioning

Every response carries corpusMerkleRoot, the content hash of the rule corpus that produced it. Rules are never edited in place. A change in the law adds a new version with its own validity window, so an old proof still verifies against the old corpus. A breaking change to a tool's inputs ships as a new tool name, never as a silently changed schema. The current corpus is 0.39.0.

The 15 tools

Eight compute, two verify, five discover. Start with calculate_tax for any federal figure, compute_return for the Form 1040 line set from documents, and compute_state_return for a state return. The full list has the rest.