> ## Documentation Index
> Fetch the complete documentation index at: https://docs.truemath.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Calculate

> Run a calculation with natural language or structured input via POST /v1/calculate, and read the result, provenance, and scenario it produces.

```http theme={null}
POST /v1/calculate
```

Runs a calculation against a published [domain](/concepts/domains). A calculation happens inside a [conversation](/concepts/scenarios): omit `conversation_id` to start a new conversation, or pass one to continue an existing one.

## Request

| Field             | Type   | Required | Description                                                                                                                                                                       |
| ----------------- | ------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `domain_id`       | UUID   | Yes      | The domain to calculate against.                                                                                                                                                  |
| `input_format`    | string | No       | `natural_language`, `structured`, `json`, or `automatic`. Optional — omit it, or pass `null` or `""`, to default to `automatic`. See [Automatic detection](#automatic-detection). |
| `prompt`          | string | Yes      | The input — prose for `natural_language`, `key: value` lines for `structured`, or a JSON string for `json`. See [Input formats](/concepts/input-formats).                         |
| `conversation_id` | UUID   | No       | Continue an existing conversation. Omit to start a new one.                                                                                                                       |

Send an `Idempotency-Key` header to make a request safe to retry and to poll. Since natural language requests return immediately with 202 Accepted, an Idempotency Key is required to retrieve the final result. See [Limits](/api/limits#idempotency).

### Natural language input

```json theme={null}
{
  "domain_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
  "input_format": "natural_language",
  "prompt": "What is the monthly payment on a $400,000 loan at 6% over 30 years?"
}
```

### Structured input

Structured input is a set of `key: value` lines — one [variable](/concepts/variables) per line — plus a `calculate:` line naming the variable to solve for. For the full grammar — values, units, tables, scenario selectors, and how omitted lines are inferred — see [Input formats](/concepts/input-formats#structured-text).

```json theme={null}
{
  "domain_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
  "input_format": "structured",
  "prompt": "home_price: $500,000\ndown_payment: $100,000\ninterest_rate: 6.5%\nloan_term: 30 yr\ncalculate: monthly_payment"
}
```

### JSON input

A JSON request sets `input_format` to `json` and passes a JSON **string** as `prompt`. Like structured text, it involves no language model and runs synchronously — the result returns in the same response. For the full object — `inputs`, `calculate`, `scenario`, and value conventions — see [Input formats](/concepts/input-formats#json).

```json theme={null}
{
  "domain_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
  "input_format": "json",
  "prompt": "{\"inputs\":[{\"key\":\"home_price\",\"value\":\"500000 USD\"},{\"key\":\"down_payment\",\"value\":\"100000 USD\"},{\"key\":\"interest_rate\",\"value\":\"6.5%\"},{\"key\":\"loan_term\",\"value\":\"30 yr\"}],\"calculate\":{\"key\":\"monthly_payment\",\"unit\":null},\"scenario\":\"new\"}"
}
```

A `prompt` that parses as JSON but does not match the schema returns [`input.invalid_input_schema`](/api/errors#calculation-error-codes); a `prompt` that is not valid JSON returns the transport error [`error_invalid_json`](/api/errors#transport-errors).

### Automatic detection

Set `input_format` to `automatic` — or leave it out, or pass `null` or `""` — to let TrueMath determine from the `prompt` whether it is natural language, structured text, or JSON, and process it accordingly. This is the default when the field is absent.

```json theme={null}
{
  "domain_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
  "prompt": "What is the monthly payment on a $400,000 loan at 6% over 30 years?"
}
```

Because the detected format governs processing, the same request behavior applies once it is resolved — a prompt detected as `natural_language` is processed asynchronously, while one detected as `structured` or `json` runs synchronously. Specify `input_format` explicitly when you already know the format and want to skip detection.

## Response

The response echoes the conversation, message, and request metadata, and reports its outcome with `status`. A `completed` or `error` outcome returns `200 OK`; an asynchronous request that is still running returns `202 Accepted` with `status: "in_progress"`.

| Field                       | Type           | Description                                                                                                                                                                                                                                             |
| --------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `conversation_id`           | UUID           | The conversation this calculation belongs to.                                                                                                                                                                                                           |
| `message_id`                | UUID           | This request's message.                                                                                                                                                                                                                                 |
| `idempotency_key`           | string \| null | The key you sent, echoed back.                                                                                                                                                                                                                          |
| `status`                    | string         | `completed`, `in_progress`, or `error`.                                                                                                                                                                                                                 |
| `source`                    | string         | How the request was made — `api` or `chat`.                                                                                                                                                                                                             |
| `input_format`              | string         | `natural_language`, `structured`, or `json`.                                                                                                                                                                                                            |
| `natural_language`          | string         | The natural-language reading. Present only when `input_format` is `natural_language` and `status` is `completed`; omitted otherwise.                                                                                                                    |
| `parsed_json`               | object         | The parsed input object — the same shape as a [JSON request](/concepts/input-formats#json). Present only when `input_format` is `natural_language` or `json` and `status` is `completed`; omitted otherwise.                                            |
| `structured_text`           | string \| null | The result as [structured text](/concepts/input-formats#structured-text). Always a key on a completed response: it carries the result for a `structured` request and is `null` for `natural_language` and `json`. `null` until `status` is `completed`. |
| `domain`                    | object \| null | The domain these results belong to — `id`, `title`, `description`.                                                                                                                                                                                      |
| `created_at` / `updated_at` | ISO 8601       | When the message was created and last updated.                                                                                                                                                                                                          |
| `results`                   | object         | The calculation outcome. Present when `status` is `completed`.                                                                                                                                                                                          |
| `errors`                    | array          | Calculation errors. Present when `status` is `error`.                                                                                                                                                                                                   |
| `version`                   | integer        | Response data-structure version, separate from the `/v1` API version.                                                                                                                                                                                   |

The echo of your input depends on the format. A `structured` request returns its result as `structured_text`. A `natural_language` request returns the `natural_language` reading plus the parsed input as `parsed_json`. A `json` request returns the parsed input as `parsed_json`. `structured_text` is always a key on a completed response — `null` for `natural_language` and `json` — while `natural_language` and `parsed_json` are present only for the formats noted above and omitted otherwise.

### Completed

A completed calculation returns `status: "completed"` and a `results` object: the [action](/concepts/scenarios) taken, the scenario indexes, the activities applied, and every variable with its value, [provenance](/concepts/provenance), and display format. `results.calculated` is the id of the variable that was solved for (`null` when `action` is `fetch`).

```json theme={null}
{
  "conversation_id": "550e8400-e29b-41d4-a716-446655440000",
  "message_id": "0b7e...e1",
  "idempotency_key": "a1b2c3",
  "status": "completed",
  "source": "api",
  "input_format": "structured",
  "structured_text": "loan_amount: 400000 USD\nmonthly_payment: 2398.20 USD",
  "domain": { "id": "...", "title": "Mortgage", "description": "..." },
  "created_at": "2026-06-05T18:30:00Z",
  "updated_at": "2026-06-05T18:30:00Z",
  "results": {
    "action": "new",
    "scenario": { "original": 0, "current": 1 },
    "activities": [
      {
        "id": "...",
        "title": "Monthly payment",
        "description": "...",
        "equation": "...",
        "state": "published",
        "variables": [{ "id": "...", "calculable": true }]
      }
    ],
    "variables": [
      {
        "id": "...",
        "key": "loan_amount",
        "title": "Loan amount",
        "description": "...",
        "source": "user_input",
        "historical": false,
        "value": "400000 USD",
        "display": { "data_type": "number", "display_type": "number", "decimal_format": "decimals_2" }
      },
      {
        "id": "...",
        "key": "monthly_payment",
        "title": "Monthly payment",
        "description": "...",
        "source": "calculated",
        "historical": false,
        "value": "2398.20 USD",
        "display": { "data_type": "number", "display_type": "number", "decimal_format": "decimals_2" }
      }
    ],
    "calculated": "..."
  },
  "version": 1
}
```

Each variable's `key` is its creator-defined [variable](/concepts/variables) name. `source` is `user_input`, `calculated`, or `default_value`, and `historical` indicates whether the value was carried forward from a prior scenario. `value` is a combined `"value unit"` string at full precision — the unit travels inside the value (see [Units and precision](/concepts/units-and-precision)). For a table, `value` is an array of such strings (nested for a 2-D table); see [Tables](/math/types/tables#as-a-stored-value).

The `scenario` object reports the `original` scenario the calculation started from (`0` starts a new scenario) and the `current` scenario the results belong to. `action` is one of `new`, `extend`, `discard`, or `fetch` — see [Scenarios](/concepts/scenarios#what-if-exploration). When `action` is `fetch`, retrieve that scenario with [`GET /v1/conversations/:id/context`](/api/scenario-context).

#### Display formats

Each variable carries a `display` object. `data_type` selects its shape — `number`, `table`, `bar_chart`, or `pie_chart` — and the remaining fields depend on it. Two fields recur across shapes:

| Field            | Type   | Values                                      | Description                                                                   |
| ---------------- | ------ | ------------------------------------------- | ----------------------------------------------------------------------------- |
| `display_type`   | string | `number`, `no_separator`, `percent`         | How a value is formatted. Currency is conveyed by the value's unit, not here. |
| `decimal_format` | string | `decimals_0`–`decimals_9`, `decimals_float` | Decimal-precision code applied when formatting.                               |

**`number`** — a scalar or unit value.

```json theme={null}
{ "data_type": "number", "display_type": "number", "decimal_format": "decimals_2" }
```

**`table`** — a grid with per-column formats and optional row names.

```json theme={null}
{
  "data_type": "table",
  "start_row_index": 1,
  "columns": [
    { "name": "Payment", "display_type": "number", "decimal_format": "decimals_2" }
  ],
  "rows": { "names": ["Year 1", "Year 2"] }
}
```

| Field             | Type      | Description                                                                |
| ----------------- | --------- | -------------------------------------------------------------------------- |
| `start_row_index` | integer   | Index of the first row ([tables](/math/types/tables) are 1-indexed).       |
| `columns[]`       | array     | Per-column format — each has `name`, `display_type`, and `decimal_format`. |
| `rows.names`      | string\[] | Optional row labels.                                                       |

**`bar_chart`** — a table presented as a bar [chart](/authoring/charts).

```json theme={null}
{
  "data_type": "bar_chart",
  "series_by": "columns",
  "stacked": false,
  "x_axis": { "title": "Year" },
  "labels": { "source": "index", "start_series_index": 1 },
  "series": [
    { "name": "Payment", "display_type": "number", "decimal_format": "decimals_2" }
  ]
}
```

| Field                       | Type    | Values                  | Description                                                                |
| --------------------------- | ------- | ----------------------- | -------------------------------------------------------------------------- |
| `series_by`                 | string  | `columns`, `rows`       | Whether each series is a column or a row of the table.                     |
| `stacked`                   | boolean |                         | Whether series are stacked.                                                |
| `x_axis.title`              | string  |                         | X-axis label.                                                              |
| `labels.source`             | string  | `index`, `first_series` | Where bar labels come from.                                                |
| `labels.start_series_index` | integer |                         | First series used for labels.                                              |
| `series[]`                  | array   |                         | Per-series format — each has `name`, `display_type`, and `decimal_format`. |

**`pie_chart`** — a table presented as a pie [chart](/authoring/charts).

```json theme={null}
{
  "data_type": "pie_chart",
  "display_type": "number",
  "decimal_format": "decimals_2",
  "slices": { "names": ["Principal", "Interest"] }
}
```

| Field          | Type      | Description            |
| -------------- | --------- | ---------------------- |
| `slices.names` | string\[] | Optional slice labels. |

#### Chart scaling

When a `bar_chart` or `pie_chart` is built from a table column whose cells carry **different units of the same dimension** — for example `3 ft`, `4 in`, `5 yd` — the chart variable carries an optional **scale companion**: the same magnitudes restated in one common unit, so a client can draw bars and slices to honest geometry. `value` is unchanged — it remains the authoritative data and still drives labels and tooltips. These fields sit on the variable, alongside `value` and `display`, and are present only when relevant.

| Field                | Type      | When present                                              | Description                                                                                                                                        |
| -------------------- | --------- | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `scale`              | number\[] | Chart cells share a dimension but span two or more units. | The magnitudes restated in one common unit, in the same shape as `value`. Use for bar heights and slice angles; `null` entries mirror empty cells. |
| `scale_unit`         | string    | With `scale`.                                             | The common unit the `scale` magnitudes are in — use it as the axis unit.                                                                           |
| `scale_incompatible` | boolean   | Chart cells span different dimensions.                    | `true` when the column mixes dimensions — for example length and mass — and has no common axis, so it cannot be charted.                           |
| `scale_types`        | string\[] | With `scale_incompatible`.                                | The conflicting [unit](/concepts/units-and-precision) type names, such as `["Length", "Mass"]`.                                                    |

A chart variable is in exactly one of three states:

1. **Scaled** — `scale` and `scale_unit` are present. Plot from `scale`, label the axis with `scale_unit`, and show the original `value` cell in tooltips.
2. **Incompatible** — `scale_incompatible` and `scale_types` are present. There is no common axis; render a short message instead of a chart.
3. **Neither** — no scale fields. The column is a single unit or unitless; plot the numeric part of each `value` cell directly, with the first cell's unit as the axis unit.

The common unit is the first cell's unit. `scale` values are full precision — format them with the variable's `display` decimal settings, the same as `value`.

```json theme={null}
{
  "key": "chart",
  "value": ["3 ft", "4 in", "5 yd"],
  "display": { "data_type": "bar_chart" },
  "scale": [3, 0.3333333333333333, 15],
  "scale_unit": "ft"
}
```

The bars sit at `3`, `0.333`, and `15` on a `ft` axis, while the second bar's tooltip reads `4 in` — the original authored value, not `0.333 ft`. When the cells span different dimensions, no chart is drawn:

```json theme={null}
{
  "key": "chart",
  "value": ["3 ft", "4 kg"],
  "display": { "data_type": "bar_chart" },
  "scale_incompatible": true,
  "scale_types": ["Length", "Mass"]
}
```

### In progress

Natural-language requests will be processed asynchronously; structured-text and JSON requests run synchronously and never return `in_progress`. When a natural-language request is still running, the API responds with `202 Accepted`, `status: "in_progress"` and no `results`:

```json theme={null}
{
  "conversation_id": "550e8400-e29b-41d4-a716-446655440000",
  "message_id": "0b7e...e1",
  "status": "in_progress",
  "source": "api",
  "input_format": "natural_language",
  "domain": { "id": "...", "title": "Mortgage", "description": "..." },
  "created_at": "2026-06-05T18:30:00Z",
  "updated_at": "2026-06-05T18:30:00Z",
  "version": 1
}
```

To get the result for natural language requests, re-post the same body with the same `Idempotency-Key` until `status` is `completed` or `error`. Leave a 1–2 second gap between retries. This could take multiple retries but rarely more than 20 total. You can also poll the conversation with [`GET /v1/conversations/:id`](/api/conversations).

### Error

A calculation that cannot complete returns `status: "error"` with an `errors` array. Each entry has a `code` and `message`, plus conditionally included `args`, `offset`, and `missing` fields (present only when the error uses them).

```json theme={null}
{
  "conversation_id": "550e8400-e29b-41d4-a716-446655440000",
  "message_id": "0b7e...e1",
  "status": "error",
  "errors": [
    { "code": "input.incompatible_units", "message": "The unit '%arg0%' and the unit '%arg1%' are not compatible", "args": ["m", "in²"] }
  ],
  "version": 1
}
```

See [Errors](/api/errors#calculation-errors) for what `args`, `offset`, and `missing` contain, and the full list of calculation error codes.

## Transport errors

Problems with the request itself — rather than with the calculation — return a `4xx` or `5xx` status and a single `error` object:

```json theme={null}
{ "error": { "code": "...", "message": "...", "params": null } }
```

| Status | Meaning                                                       |
| ------ | ------------------------------------------------------------- |
| `400`  | Invalid JSON or malformed structure.                          |
| `401`  | Missing or invalid credentials.                               |
| `403`  | API key disabled.                                             |
| `409`  | Idempotency conflict — same key reused with a different body. |
| `422`  | Semantically invalid input, such as a missing required field. |
| `5xx`  | Unexpected server failure.                                    |
