# Unified mappings build

> Source: https://truto.one/docs/cli/unified-mappings/

`truto unified-mappings build` authors the **JSONata mapping rows** that connect one integration's proxy API to a [unified model](/docs/guides/unified-apis/what-are-unified-apis) (for example `crm`, `ats`, `hris`). It runs an **agentic** build, writes a local **MappingFile** JSON (for example `acme.crm.mappings.json`), and pushes nothing to Truto until you run `truto unified-mappings apply`.

```bash
# Build mappings for an integration against a unified model
truto unified-mappings build acme crm

# Audit the result without pushing
truto unified-mappings validate acme.crm.mappings.json

# Push when you are satisfied
truto unified-mappings apply acme.crm.mappings.json --target base
```

For each unified **resource + method** (for example `contacts.list`, `accounts.get`, `deals.create`) the build generates the JSONata that transforms the integration's raw proxy response into the unified schema shape — plus `query_mapping`, `request_body_mapping`, and `error_mapping` where the proxy method needs them.

This is the mapping-side companion to [`truto integrations build`](/docs/cli/integrations-build): build the integration first (so it has proxy resources), then build its unified mappings.

## Prerequisites

| Requirement              | Notes                                                                                                                            |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------- |
| **Anthropic API key**    | Required for all LLM work. Flag → `$ANTHROPIC_API_KEY` → `truto profiles set-key anthropic` → interactive prompt.               |
| **Truto login**          | The build loads the integration config, the unified model schema, and any existing mapping rows from your environment.          |
| **An installed integration** | The integration must already exist with proxy resources (build it with [`truto integrations build`](/docs/cli/integrations-build)). |
| **Integrated account** (recommended) | Pass `--account` to ground mappings on **live proxy samples**. Without one, the build falls back to documentation samples. |
| **Firecrawl API key**    | Only when you pass a `--source-url` docs site that needs crawling (not raw OpenAPI / local files).                              |
| **OpenAI API key**       | Optional. Embeds the `--source-url` index for hybrid search; omit with `--no-embeddings` for BM25-only.                        |

## What happens during a build

The default flow (no flags) is **agentic**: Claude (Opus) orchestrates the build through tools, routing each unified resource/method to a proxy endpoint, sampling it, generating JSONata, validating, and committing the cell. A fixed per-cell pipeline still exists behind `--structured` for parity and cheaper runs — it still uses the LLM (Sonnet) to route and generate each mapping; only the orchestration is fixed, not the JSONata output.

### 1. Pre-flight

- Resolve the Anthropic key (and Firecrawl / OpenAI when a `--source-url` is crawled).
- Fetch the integration config (`config.resources`), the unified model schema (env override merged when `--environment-id` is set), and any existing mapping rows.
- Inventory the proxy resources/methods and **plan the cells** — the unified resource × method pairs that a proxy endpoint can serve.
- Load the mapping **corpus** (exemplars from other integrations) used for cribbing and leak detection.

### 2. Routing

The agent routes each unified resource/method to a proxy resource/method — including cross-name routes (unified `accounts` → proxy `companies`), multi-proxy-resource routes, and conditional (when-guarded) routes. Routing decisions are recorded so later steps and resumes stay consistent.

### 3. Ground the sample

Per cell, the build grounds generation on the richest sample it can find, in order:

1. **Live proxy** (`fetch_proxy_sample`) when an `--account` is connected. Sampling is **read-only** — it only issues a `GET` for `list`/`get` and never runs create/update/delete against the connected account.
2. **DB documentation** examples (`read_doc_response_example`).
3. The **`--source-url` index** (`search_source_docs`), and as a last resort a single-page scrape (`scrape_doc_page`, needs Firecrawl).

Write cells (`create` / `update` / `delete`) are built from the proxy's documented request body even without a response example — the cell does its job through `request_body_mapping`.

### 4. Generate and validate

`generateMappingField` produces each field's JSONata; `validate_mapping_output` compiles it, evaluates it against the sample, and checks the result against the unified JSON Schema. A deterministic pass fixes proxy array paths, `get`→`list` aliases, and list wrappers, and rejects cross-integration leaks (verbatim copies of another integration's expressions).

### 5. Refinement loop (interactive)

In an interactive terminal the build ends in a **refinement loop**. Type a free-form instruction (for example `engagements.create: strip the trailing Z from start times`) and the agent re-builds the affected cell. Press **Enter** on an empty line to finish. The loop is skipped with `--no-refine`, `--yes`, `--only-missing`, or when output is piped.

### 6. Output

The final MappingFile is written to `--out` or `<integration>.<model>.mappings.json`, with a **build summary** printed to the terminal and embedded in the file. The summary reports built / skipped / flagged counts and the next `validate` / `apply` commands.

:::callout{type="info"}
**Resume:** If a working file (`<integration>.<model>.mappings.working.json`) already exists with built cells, the build resumes from it instead of starting over. Pass `--fresh` to ignore it.

**Only missing cells:** On an existing file, `--only-missing` builds only the cells not yet present — it does not retry failures or modify cells already on disk.
:::

## MappingFile shape

The output is a **MappingFile**: integration + unified-model identifiers, a `write_target`, the generated `cells[]`, and a `build_summary`.

| Field                  | Purpose                                                                                                          |
| ---------------------- | --------------------------------------------------------------------------------------------------------------- |
| `integration_name` / `unified_model_name` | What this file maps, by slug.                                                                       |
| `write_target`         | `base` (team-owned rows) or `env` (per-environment override rows).                                              |
| `cells[]`              | One entry per unified resource/method, each with a `config` (the mapping fields) and `db_info` (apply action). |
| `build_summary`        | `status`, `counts` (planned / built / skipped / flagged), `built`, `skipped` (with reasons), `unbuilt_routed`, and `warnings`. |

Each cell's `config` carries the relevant mapping fields: `response_mapping`, and for read/write methods `query_mapping`, `request_body_schema` + `request_body_mapping` (required fields are marked), and `error_mapping`.

The `build_summary` is built for human review — it explains every gap. `skipped` cells list **why** no mapping was produced (for example "no proxy endpoint serves this method"), and `warnings` flag cells worth a second look (hardcoded `custom_fields`, create/update transform drift, single-key guards) grouped by cell and kind.

## Useful flags

| Flag                                  | Purpose                                                                                                          |
| ------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `-a, --account <id>`                  | Integrated account ID for **live proxy samples** (read-only GET).                                               |
| `--source-url <url>`                  | API documentation URL for response examples (sample ladder: live → DB → source).                               |
| `--resources <list>`                  | Comma-separated unified resources to build (default: all planned).                                              |
| `--methods <list>`                    | Comma-separated methods (`list,get,create,update,delete,…`).                                                    |
| `--structured`                        | Run a fixed per-cell pipeline instead of the agentic loop (still LLM-generated; only the orchestration is fixed). |
| `--no-web-search`                     | Disable the `web_search` / `web_fetch` tools and the source-sample web-search rung (no outbound web calls).     |
| `--fresh`                             | Ignore an existing working/final MappingFile and start from scratch.                                            |
| `--only-missing`                      | Build only cells absent from the existing file; do not retry failures or modify existing cells.                 |
| `--no-refine`                         | Skip the interactive post-build refinement loop.                                                                |
| `--fail-fast`                         | Stop on the first cell error (default: skip failed cells and continue).                                          |
| `--environment-id <id>`               | Resolve schema overrides and target env override rows.                                                          |
| `--target <base\|env>`                | Write-target hint recorded in the file for `apply` (default `base`).                                            |
| `--out <file>`                        | Output path (default `<integration>.<model>.mappings.json`).                                                    |
| `--unified-mapping-dir <path>`        | Corpus of unified mapping exemplars (`$TRUTO_UNIFIED_MAPPING_DIR` or profile `unifiedMappingDir`).             |
| `--integration-config-dir <path>`     | Local proxy-config corpus for exemplars.                                                                         |
| `--debug-log <path>` / `--no-debug-log` | JSONL transcript of the build (default on, under `~/.truto/logs/`).                                          |
| `--anthropic-model <model>`           | Override the model for all LLM tiers.                                                                            |
| `--agent`                             | Deprecated no-op — the agentic loop is already the default.                                                     |

Run `truto unified-mappings build --help` for the complete list.

## Bring-your-own keys

| Key                 | Required when                                              |
| ------------------- | --------------------------------------------------------- |
| `ANTHROPIC_API_KEY` | Always                                                    |
| `FIRECRAWL_API_KEY` | A `--source-url` docs site needs crawling                 |
| `OPENAI_API_KEY`    | Optional embeddings for the `--source-url` index (unless `--no-embeddings`) |

Resolution order for each: CLI flag → environment variable → `truto profiles set-key` → interactive prompt on a TTY.

## Web tools

By default the agent can call the Anthropic-side `web_search` / `web_fetch` server tools to ground a low-confidence route or an undocumented field against the provider's official docs (capped by `max_uses`). Pass `--no-web-search` to disable both those tools and the source-index web-search rung — useful for builds where you want no outbound web calls beyond the proxy and Truto APIs.

## Validate

Audit a MappingFile without applying — deterministic, no LLM, no writes:

```bash
truto unified-mappings validate acme.crm.mappings.json
truto unified-mappings validate acme.crm.mappings.json -v   # list advisory warnings
```

Validation re-checks every cell's JSONata and schema conformance and exits non-zero on errors. Advisory warnings (the per-cell review flags from `build_summary.warnings`) never fail validation; they are summarized by default and listed in full with `-v`. Pass `--environment-id` to resolve env schema overrides.

## Apply

Push the reviewed MappingFile to the platform:

```bash
# Preview the payloads without writing
truto unified-mappings apply acme.crm.mappings.json --dry-run

# Apply to base rows (or env override rows)
truto unified-mappings apply acme.crm.mappings.json --target base
truto unified-mappings apply acme.crm.mappings.json --target env
```

Apply upserts each cell as a `unified_model_resource_method` row (or the `environment_unified_model_resource_method` override when `--target env`), using the `db_info` action recorded per cell (create / update / skip).

| Flag             | Effect                                            |
| ---------------- | ------------------------------------------------- |
| `--target <base\|env>` | Write base rows or per-environment overrides |
| `--dry-run`      | Validate and print payloads; no API calls         |
| `--yes`          | Skip confirmation prompts                         |

## Examples

```bash
# Live-grounded build for a connected account, then validate + apply
export ANTHROPIC_API_KEY=sk-ant-...
ACCOUNT=<integrated-account-id>
truto unified-mappings build acme crm --account $ACCOUNT
truto unified-mappings validate acme.crm.mappings.json
truto unified-mappings apply acme.crm.mappings.json --target base

# Doc-grounded build with a source URL (no account)
truto unified-mappings build acme crm \
  --source-url https://docs.acme.com/openapi.json --yes

# Only build the cells you are missing, just for two resources
truto unified-mappings build acme crm --resources contacts,accounts --only-missing

# Fixed pipeline, no web calls
truto unified-mappings build acme crm --structured --no-web-search
```

## Next steps

- [Managing integrations](/docs/cli/integrations) — build the integration first so it has proxy resources
- [AI-powered integration build](/docs/cli/integrations-build) — the integration-side companion flow
- [What are Unified APIs?](/docs/guides/unified-apis/what-are-unified-apis) — the models you are mapping to
- [JSONata Binding Reference](/docs/guides/unified-apis/mapping-bindings) — the expression language used in every mapping field
- [Data plane](/docs/cli/data-plane) — `unified test-mapping` to iterate on a single `response_mapping` locally
