Read one response and you will know.
Every high-risk answer returns the same standardised contract. It is unusually long for an API response, and every field in it is there because leaving it out would let a client present something as exact when it is not.
The answer contract
This is a real response for the documented golden UK computation — the same £11,432 every other surface on this site shows. Two fields decide what your interface is allowed to do with it: coverage_status, which says whether the figure may be presented as exact, and verification_status, which is the field to branch on.
{
"answer_type": "calculation",
"jurisdictions": [
"GB"
],
"tax_years": [
"2024/25"
],
"tax_types": [
"individual_income"
],
"coverage_status": "beta",
"law_as_of": "2024-04-06",
"source_last_checked": "2024-11-03",
"corpus_release_id": "uk-2024-11-03.a41f9c",
"facts_used": [
{
"key": "employment_income",
"value": "60000.00",
"currency": "GBP"
},
{
"key": "sub_region",
"value": "rest_of_uk"
},
{
"key": "filing_status",
"value": "individual"
}
],
"missing_facts": [],
"calculations": [
{
"calculator": "uk_income_tax",
"calculator_version": "2024.3.1",
"parameter_dataset": "UK/2024-25",
"taxable_income": "47430.00",
"total_tax": "11432.00",
"effective_rate": "0.1905",
"marginal_rate": "0.40",
"bands": [
{
"label": "Personal allowance",
"amount": "12570.00",
"rate": "0.00",
"tax": "0.00"
},
{
"label": "Basic rate",
"amount": "37700.00",
"rate": "0.20",
"tax": "7540.00"
},
{
"label": "Higher rate",
"amount": "9730.00",
"rate": "0.40",
"tax": "3892.00"
}
]
}
],
"claims": [
{
"id": "c1",
"type": "figure",
"text": "Income tax due is £11,432.00.",
"support": "calculator",
"calculation_ref": "uk_income_tax",
"verified": true
},
{
"id": "c2",
"type": "rate",
"text": "The basic rate is 20% on taxable income up to £37,700.",
"support": "citation",
"citation_ref": "s1",
"verified": true
}
],
"citations": [
{
"id": "s1",
"authority": "UK Parliament",
"authority_rank": "statute",
"title": "Income Tax Act 2007",
"locator": "s.10",
"effective_from": "2007-04-06",
"stale": false
},
{
"id": "s2",
"authority": "HMRC",
"authority_rank": "guidance",
"title": "Income Tax rates and Personal Allowances",
"locator": "2024 to 2025",
"effective_from": "2024-04-06",
"stale": false
}
],
"assumptions": [
"Rest-of-UK rates. Scotland has different income tax bands.",
"No other income, reliefs or allowances beyond the personal allowance."
],
"risks": [
"A Scottish taxpayer would produce a different figure from the same income."
],
"confidence": 0.97,
"verification_status": "verified",
"human_review_required": false,
"request_classification": "high_risk",
"answer_mode": "grounded",
"conflicts": [],
"next_actions": [
"Review before filing."
]
}Note what is not optional. missing_facts, conflicts and assumptions are present on every high-risk answer, empty on a clean one. A client that treats them as sometimes-absent will break on the interesting case rather than on a release.
A real request
The same call in three forms. Whichever you pick, the branch is on verification_status — not on whether a number came back, because an abstention is a successful response with no number in it.
curl -X POST https://api.taxorch.com/api/v1/tax-assistant/answer \
-H "Authorization: Bearer $TAXORCH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"question": "What is the income tax on £60,000 in the UK for 2024/25?",
"jurisdiction": "GB",
"tax_year": "2024/25",
"site": 1
}'const response = await fetch(
"https://api.taxorch.com/api/v1/tax-assistant/answer",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.TAXORCH_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
question: "What is the income tax on £60,000 in the UK for 2024/25?",
jurisdiction: "GB",
tax_year: "2024/25",
site: 1,
}),
},
);
const answer = await response.json();
// Branch on verification_status, never on the presence of a figure.
switch (answer.verification_status) {
case "verified":
render(answer.calculations[0], answer.citations);
break;
case "needs_facts":
ask(answer.missing_facts);
break;
default:
escalate(answer);
}import os
import httpx
response = httpx.post(
"https://api.taxorch.com/api/v1/tax-assistant/answer",
headers={"Authorization": f"Bearer {os.environ['TAXORCH_API_KEY']}"},
json={
"question": "What is the income tax on £60,000 in the UK for 2024/25?",
"jurisdiction": "GB",
"tax_year": "2024/25",
"site": 1,
},
timeout=60,
)
answer = response.json()
# Branch on verification_status, never on the presence of a figure.
if answer["verification_status"] == "verified":
render(answer["calculations"][0], answer["citations"])
elif answer["verification_status"] == "needs_facts":
ask(answer["missing_facts"])
else:
escalate(answer)Response conventions
These are worth reading before you write a client. Two of them are unusual, and both exist so that a failure tells you what to do rather than only that something went wrong.
- The answer contract
- Every high-risk answer returns the same standardised object, whatever the endpoint. You parse one shape rather than one per surface.
- 422 with a blocker list
- An operation is blocked by an unmet precondition, and the response says exactly which. Not a generic validation failure — a list of what to fix.
- 409 Conflict
- The target record is immutable. A finalised case is the common one: reopening it is a deliberate, audited action rather than a permitted write.
- verification_status
- Eight values, covering the outcomes nobody wants as well as the good one. This is the field to branch on.
- X-Content-SHA256
- Returned on audit-proof downloads, so a client can verify integrity itself rather than trusting the transfer.
- Contract headers
- Each surface pins its API contract version, so a client can detect a breaking change rather than discovering one.
The shape of the API
Approximately 151 routes, counted from the URL configuration rather than estimated. Every endpoint is authenticated, tenant-scoped, permission-checked and audit-logged — those are not four features, they are four things you cannot turn off.
- Authenticated
- Tenant-scoped
- Permission-checked
- Audit-logged
The base prefix is /api/. Versioned actions are mirrored under /api/v1/, and each surface pins its own contract version in a response header — so a breaking change is something a client can detect rather than something it discovers. Most endpoints accept ?site=<id> and enforce per-site permission on top of the tenant scope.